TL

平衡木の原理(赤黒木・B木・AVL木)

なぜ木を平衡に保つだけで検索が速くなるのかが腑に落ちる。AVL・赤黒木・B/B+木の平衡条件と回転・分割を、メモリ内かディスクかという用途の違いから正確に押さえる。

応用平衡木赤黒木B木アルゴリズムデータベース最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.二分探索木は放っておくと一直線に偏りO(n)化する。平衡条件で高さをO(log n)に抑えるのが平衡木の本質。
  • 2.AVLは高さ差±1で厳格・検索特化、赤黒木は色制約で緩く更新が軽い。回転で平衡を回復する。
  • 3.B/B+木はノードを多分木にして木を低くし、ディスク/SSDのI/O回数を最小化する。DB・ファイルシステムの定番。

なぜ「平衡」が要るのか

二分探索木(BST)は「左の子は自分より小さい、右の子は大きい」という順序を保つ木です。検索・挿入・削除は、根から葉に向かって比較しながら下りていくので、計算量は木の高さに比例します。理想的にバランスが取れていれば高さは O(log n) ですが、ここに落とし穴があります。

ソート済みデータを順に挿入すると、右の子だけが伸び続け、木は連結リストと同じ一直線になります。このとき高さは n、検索は O(n) に劣化します。つまりBSTの O(log n) は「たまたま形が良ければ」の話で、保証されていません

1, 2, 3, 4 を順に挿入すると…

1
 \
  2
   \
    3      ← 高さ n、ただの連結リスト
     \
      4

平衡木(self-balancing tree)は、挿入・削除のたびに形を整え、高さを常に O(log n) に抑えることでこの最悪ケースを排除します。整える操作の主役が回転と(B木系の)分割です。木構造の探索が再帰と相性が良いこと、各操作の速さを表す計算量(Big-O)の記法は前提として進めます。

回転 ― 形を変えても順序を壊さない

回転(rotation)は、平衡木の心臓部です。部分木の親子関係を組み替えて高さを調整しつつ、二分探索木の順序(中順序)は保つという、一見矛盾した要求を満たします。

右回転(x を下げ、左の子 y を上げる)

    x                y
   / \              / \
  y   C    ──→     A   x
 / \                  / \
A   B                B   C

中順序は A y B x C のまま不変。順序は壊れない。

ポイントは、移し替えるのは部分木 B だけで、A < y < B < x < C という大小関係が前後で一切変わらないことです。左回転はこの鏡像です。回転は参照(ポインタ)の付け替えだけなので O(1) で完了します。

AVL木 ― 高さ差を厳格に±1へ

AVL木(1962年, Adelson-Velsky と Landis)は、最初に発明された自己平衡BSTです。平衡条件は厳格で、すべてのノードで「左部分木の高さ − 右部分木の高さ」が -1, 0, +1 のいずれか(この値を平衡係数, balance factor と呼ぶ)。

挿入・削除でこの条件が崩れると、崩れた最も低いノードを起点に回転で回復します。崩れ方は4パターンあり、回転の打ち方が決まっています。

崩れ方状況回復操作
LL左の子の左側が重い右回転 1回
RR右の子の右側が重い左回転 1回
LR左の子の右側が重い左回転→右回転(2回)
RL右の子の左側が重い右回転→左回転(2回)
平衡係数を更新し、|bf| > 1 になったら回転
function insert(node, key):
    通常のBST挿入
    高さを更新
    bf = height(left) - height(right)
    if bf > 1 and key < node.left.key:  右回転        # LL
    if bf < -1 and key > node.right.key: 左回転        # RR
    if bf > 1 and key > node.left.key:  左回転後に右回転 # LR
    if bf < -1 and key < node.right.key: 右回転後に左回転 # RL

厳格さの代償として、高さは最大でも約 1.44 * log2(n) と非常に低く保たれます。検索が極めて速い反面、平衡条件が厳しいため挿入・削除で回転が頻発しやすいのが特徴です。

赤黒木 ― 色で緩く縛る

赤黒木(red-black tree)は、各ノードに赤か黒の色を持たせ、次の制約で平衡を保ちます。

  • 根は黒。
  • 赤ノードの子は必ず黒(赤が連続しない)。
  • 任意のノードから葉(NIL)までの経路上の黒ノード数が等しい(黒高さ一定)。

この制約から、最長経路でも最短経路の2倍を超えないことが導けます。よって高さは最大 2 * log2(n+1) 程度に収まり、O(log n) が保証されます。AVLより緩い分木はやや高くなりうるものの、平衡回復に必要な操作が少なくて済みます。

赤黒木とB木は親戚

赤黒木は2-3-4木(各ノードが2〜4個の子を持つB木の一種)を二分木で表現したものと等価です。黒ノードがB木の要素、赤ノードが「同じノード内に同居する要素」に対応します。色制約は、実はB木のノード分割ルールの言い換えなのです。

赤黒木の挿入では、まず通常のBST挿入をして新ノードをで置き、赤が連続したら「叔父ノードの色」で場合分けして色の塗り替えまたは回転で修復します。重要なのは、色の塗り替えで済むケースが多く、回転は1回の挿入で高々2回に抑えられる点です。削除も同様に回転回数が定数で抑えられます。

観点AVL木赤黒木
平衡条件高さ差 ±1(厳格)色制約(緩い)
木の高さ約 1.44 log n(低い)約 2 log n(やや高い)
検索やや有利わずかに不利
挿入・削除の回転多くなりがち定数回に抑制
向く用途検索が圧倒的に多い更新が頻繁・汎用
採用例辞書・読み取り主体C++ std::map、Linuxカーネル、Java TreeMap

B木・B+木 ― ディスクのための多分木

AVLや赤黒木はメモリ内を前提とした二分木です。しかしデータがメモリに乗り切らずディスクやSSDに置かれる場合、設計の前提が変わります。ディスクI/Oは1回が極端に遅く(メモリアクセスの数万倍)、読み取り回数=木の高さがそのまま性能を決めます。

B木(1970年, Bayer と McCreight)は、1ノードに**多数のキーと子(数百本)**を詰め込む多分木です。1ノードをディスクの1ブロック(ページ)と同じサイズにすると、1回のI/Oで一気に数百方向へ枝分かれできます。子の本数(分岐数, fan-out)が大きいほど木は低くなり、n 件でも高さは log_fanout(n) = わずか3〜4段で済みます。

平衡の保ち方は回転ではなく分割と併合です。

挿入でノードが満杯(キー数 = 2t-1 個)になったら…

[ 10 | 20 | 30 | 40 | 50 ]   ← 満杯ノード
              │ 分割(split)
        [ 30 ]               ← 中央キーを親へ押し上げ
        /     \
[10|20]       [40|50]        ← 左右に分かれる

これにより全ての葉が常に同じ深さに揃う(完全平衡)。

満杯ノードへの挿入は中央のキーを親に押し上げて2つに割る。逆に削除でノードのキーが下限(t-1 個)を割ると、隣から借りる(再分配)か併合する。これらが根に伝播することで、全ての葉が常に同一の深さに保たれます。

B+木 ― 範囲検索に最適化

実際のデータベースやファイルシステムで主流なのは、B木を改良したB+木です。違いは2点。

  • データ(値)は葉だけに置く。内部ノードはキー(道しるべ)のみ。これにより内部ノードに多くのキーを詰められ、fan-out がさらに上がる。
  • 葉どうしを連結リストでつなぐ。これにより「ある値からある値まで」の範囲検索が、葉を横に走査するだけで高速にできる。
なぜDBのインデックスはB+木なのか

SQLの WHERE age BETWEEN 20 AND 40 のような範囲問い合わせや ORDER BY は、葉が連結された B+木なら開始位置を1回探して横に辿るだけ。ハッシュインデックスは等値検索(=)は速くても範囲やソートが苦手なため、汎用インデックスにはB+木が選ばれます。MySQL(InnoDB)・PostgreSQL の標準インデックスはB+木です。

系統と用途の整理

二分探索木から各構造への分岐を、登場年代とともに文章で整理します。

  • 1962 二分探索木の最悪ケースを解く最初の解 → AVL木(高さ差±1で厳格・検索特化)。
  • 1978 AVLの回転コストを緩める色ベースの平衡 → 赤黒木(Guibas と Sedgewick。1972年のBayerの対称二分B木が原型。更新が軽く汎用、標準ライブラリの定番)。
  • 1970 ディスクI/O回数の最小化という別軸の要求 → B木(多分木で木を低く)。
  • B木の改良系 範囲検索とfan-out最大化 → B+木(データは葉のみ・葉を連結。DB/FSの主流)。

つまり分岐の根本は「どこにデータがあるか」です。メモリ内なら比較1回が安いので二分木(AVL/赤黒木)で深さを稼いでよく、ディスク/SSDならI/O1回が高いので多分木(B/B+木)で深さを徹底的に削る、という住み分けになります。

構造分岐数平衡操作主な舞台代表的な採用例
AVL木2(二分)回転メモリ内・検索主体読み取り特化の辞書
赤黒木2(二分)回転+色塗替えメモリ内・汎用std::map / TreeMap / カーネル
B木多分(数百)分割・併合ディスク・メモリ階層一部のファイルシステム
B+木多分(数百)分割・併合ディスク/SSDRDBインデックス・FS
試験・面接での頻出ポイント

「BSTの最悪計算量は?」→ O(n)(偏ったとき)。「平衡木にすると?」→ O(log n) 保証。「DBのインデックスがB+木である理由は?」→ ディスクI/O回数の削減(高い fan-out で木が低い)と、葉の連結による範囲検索の効率。「AVLと赤黒木の違いは?」→ AVLは厳格で検索が速い、赤黒木は緩く更新が軽い。この4点は定番です。

まとめ

平衡木の核心は「高さを O(log n) に抑え続ける」一点に尽きます。二分木(AVL・赤黒木)は回転で、多分木(B・B+木)は分割・併合で平衡を保ちますが、選択の決め手は性能特性ではなくデータの置き場所です。比較が安いメモリ内では二分木、I/Oが高いディスク/SSDでは多分木。検索が極端に多いなら厳格なAVL、更新も多い汎用用途なら赤黒木、永続化と範囲検索ならB+木。この対応関係を押さえれば、各構造が「なぜその場面で選ばれるか」を原理から説明できます。なお、これらはデータ構造の中でも順序付き集合・マップを支える基盤であり、std::mapTreeMap の裏側そのものです。

プログラミング Article

平衡木の原理(赤黒木・B木・AVL木)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

平衡木

比較で見る軸

難易度: advanced / カテゴリ: プログラミング / タグ数: 5

導入後に効く点

AVLは高さ差±1で厳格・検索特化、赤黒木は色制約で緩く更新が軽い。回転で平衡を回復する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
プログラミング
タグ数
5

判断チェックリスト

  • 自社の用途が「平衡木 / 赤黒木」に近いか確認する。
  • 強みである「二分探索木は放っておくと一直線に偏りO(n)化する。平衡条件で高さをO(log n)に抑えるのが平衡木の本質。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

平衡木赤黒木B木アルゴリズムデータベース平衡木赤黒木B木