Java | 「赤黒木」と「AVL木」をもっと深く理解する

Java SQL
スポンサーリンク

Javaで考える「赤黒木」と「AVL木」をもっと深く理解する(前半)

赤黒木(Red-Black Tree)や AVL 木は、 「常にバランスを保つ二分探索木」というカテゴリの代表選手です。 これらを理解できると、 ソート済みデータ構造・辞書・セット・マップの内部構造が見えるようになり、 Java の TreeMapTreeSet が「ただの黒箱」ではなくなります。

前半では、まず なぜ「バランスする二分探索木」が必要なのか 通常の二分探索木が抱える問題 その問題を解決するための発想としての「AVL木」と「赤黒木」 を、初心者向けにかみ砕いて整理します。 後半では、回転(rotation)や具体的な挿入・削除の流れまで踏み込んでいきます。

二分探索木の基本と「バランスが崩れる」という問題

まず、土台となる「二分探索木(Binary Search Tree)」を整理します。 二分探索木は、次のような性質を持つ木構造です。

左部分木のすべての値 < 自分の値 右部分木のすべての値 > 自分の値

この性質のおかげで、 探索・挿入・削除を「左か右か」を選びながら進めることができ、 理想的には O(log N) で操作できます。

Java での基本的な二分探索木のノードは、次のような形になります。

class Node {
    int key;
    Node left;
    Node right;

    Node(int key) {
        this.key = key;
    }
}
Java

例えば、次のように挿入していきます。

public Node insert(Node root, int key) {
    if (root == null) {
        return new Node(key);
    }
    if (key < root.key) {
        root.left = insert(root.left, key);
    } else if (key > root.key) {
        root.right = insert(root.right, key);
    }
    return root;
}
Java

この形は非常にシンプルで分かりやすいのですが、 一つ大きな問題を抱えています。

それは、「挿入する値の順番によって、木の形が極端に偏る」という問題です。

極端に偏った二分探索木が「ほぼリンクリスト」になる

例えば、次のような順番で値を挿入してみます。

1, 2, 3, 4, 5, 6, 7

この順番で先ほどの insert を使うと、 木は次のような形になります。

1 └ 2 └ 3 └ 4 └ 5 └ 6 └ 7

つまり、「右にだけ伸びた一本鎖」のような形になります。 このとき、探索の計算量は O(N) になってしまい、 二分探索木の「速さのメリット」がほぼ失われます。

二分探索木は「バランスが取れていれば速い」が、 「バランスが崩れると遅くなる」という性質を持っています。

ここでいうバランスとは、 「どのノードを根にしても、左部分木と右部分木の高さが極端に違わない」 というイメージです。

この問題を解決するために登場するのが、 AVL 木や赤黒木といった「自己平衡二分探索木」です。

自己平衡二分探索木という発想

自己平衡二分探索木(Self-Balancing BST)は、 「挿入や削除のたびに、木のバランスを保つように自動調整する」 という発想に基づいたデータ構造です。

ポイントは、 「バランスを保つためのルール」と 「そのルールを維持するための操作(回転)」 を定義していることです。

AVL 木と赤黒木は、 どちらも「バランスを保つ二分探索木」ですが、 バランスの定義と調整の仕方が少し違います。

AVL 木は「高さの差」に厳しいルールを持ち、 赤黒木は「色と黒高さ」に基づく、少し緩めのルールを持ちます。

この違いが、 「探索が速いか」「挿入・削除が軽いか」といった特性の差につながります。

AVL木とは何か:「高さの差」を厳しく管理する木

AVL 木は、 「どのノードについても、左部分木と右部分木の高さの差が 1 以内である」 というルールを持つ二分探索木です。

高さとは、「そのノードから一番深い葉までの距離」です。

例えば、あるノード X について、 左部分木の高さが 3、右部分木の高さが 1 だとすると、 高さの差は 2 になり、AVL 木のルールに違反します。

AVL 木では、挿入や削除のたびにこの高さの差をチェックし、 差が 2 以上になった場合には「回転(rotation)」という操作を行って バランスを回復します。

この「高さの差を厳しく管理する」という性質のおかげで、 AVL 木は非常にバランスが良く、 探索の計算量がほぼ常に O(log N) に保たれます。

ただし、バランスを保つための回転が頻繁に発生するため、 挿入・削除のコストはやや重くなります。

赤黒木とは何か:「色」と「黒高さ」でバランスを保つ木

赤黒木は、 各ノードに「赤(Red)」か「黒(Black)」の色を持たせ、 いくつかのルールを守ることでバランスを保つ二分探索木です。

代表的なルールは次のようなものです。

根は必ず黒 赤ノードの子は必ず黒(赤が連続しない) 任意のノードから葉(NILノード)までの「黒ノードの数」が同じ

この「黒ノードの数」が同じという性質を「黒高さ(black height)」と呼びます。

赤黒木は、 この色と黒高さのルールを守ることで、 木の高さが O(log N) に収まるようにバランスを保ちます。

AVL 木ほど「高さの差」に厳しくないため、 木の形は多少アンバランスになることがありますが、 その分、挿入・削除の回転回数が少なく済み、 更新操作に強いという特徴があります。

Java の TreeMapTreeSet は、 内部で赤黒木を使って実装されています。 つまり、皆さんが日常的に使っている標準ライブラリの中で、 赤黒木はすでに活躍しているのです。

「バランスする二分探索木」を直感的にイメージする

ここまでの話を、直感的なイメージでまとめてみます。

通常の二分探索木は、 「とりあえず左か右かに挿していく」だけなので、 挿入順によっては「片側に伸びた一本鎖」になってしまいます。

AVL 木は、 「どのノードでも左右の高さの差が 1 以内」という かなり厳しいルールを守ることで、 木全体をきれいにバランスさせます。

赤黒木は、 「色と黒高さ」という少し緩めのルールを使って、 木の高さを O(log N) に保ちつつ、 挿入・削除のコストを抑えます。

どちらも、「バランスを保つためのルール」と「回転」という操作を持ち、 その結果として「速い探索」を保証している、 という点では共通しています。

この「バランスする二分探索木」という発想を理解できると、 データ構造の設計を一段深いレベルで考えられるようになります。

前半のまとめと後半への橋渡し

前半では、赤黒木やAVL木を深く理解するための土台として

二分探索木の基本構造と「バランスが崩れると遅くなる」という問題 極端に偏った二分探索木が「ほぼリンクリスト」になること 自己平衡二分探索木という発想 AVL 木が「高さの差」を厳しく管理してバランスを保つ木であること 赤黒木が「色」と「黒高さ」のルールでバランスを保つ木であること Java の TreeMap / TreeSet が赤黒木を内部で使っていること

を整理しました。

後半では、ここから一歩進んで

AVL 木の具体的な回転(単回転・二重回転)の流れ 赤黒木の挿入・削除時の色反転と回転のパターン Java 風の簡易実装イメージ AVL と赤黒木の性能・用途の違い セキュリティ・パフォーマンスの観点から見た「自己平衡木」の注意点

を、より具体的なコード断片とともに深掘りしていきます。

タイトルとURLをコピーしました