二分木の平衡判定

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

二分木の平衡判定

このレッスンで分かること

  • 二分探索木 (BST) の検索計算量は 木の高さに比例 します
  • 二分木が 平衡 (balanced) であるとは、「すべてのノード左部分木の高さ右部分木の高さ の差が 1 以下」という状態を指します
  • つまり同じ BST でも、平衡しているかどうかで 桁違いの性能差 が出ます

二分木の平衡判定 とは

二分木を配列表現で受け取り、すべてのノードで左右部分木の高さの差が 1 以下なら true を返す関数を実装する。

二分木が 平衡 (balanced) であるとは、「すべてのノード左部分木の高さ右部分木の高さ の差が 1 以下」という状態を指します。一部のノードだけ満たしていても、どこか 1 か所でも 差 >= 2 になっていれば平衡ではありません。

平衡判定は 全ノード で条件を満たすかをチェックする問題。1 か所でも崩れたら false。

なぜ平衡が重要か

二分探索木 (BST) の検索計算量は 木の高さに比例 します。

  • 平衡な BSTn 個のノードに対し高さ O(log n) → 検索 O(log n)
  • 退化した BST (一直線) は高さ O(n) → 検索 O(n)

つまり同じ BST でも、平衡しているかどうかで 桁違いの性能差 が出ます。AVL 木や赤黒木、B-tree などの平衡木データ構造は、挿入のたびに自動で回転 して平衡を保つ仕掛けがあります。データベースのインデックスや std::map などの実装は基本的にこの系統です。

diagram (will load when visible)

図のポイント (テキスト併記)

  • 平衡木は 「最悪計算量を保証する」 ための CS 古典装置

平衡木は 「最悪計算量を保証する」 ための CS 古典装置。std::map / DB index の根っこにある。

[1, 2, 3, null, 4] の平衡判定をしてみます。

  • 1 左部分木 (2 → 4) の高さ 2、右部分木 (3) の高さ 1 → 差 1、OK
  • ノード 2null の高さ 0、右 (4) の高さ 1 → 差 1、OK
  • ノード 34 葉なので OK

すべて条件を満たすので 平衡 (true)

一方、[1, 2, null, 3] (root → left=2 → left=3 の一直線) は、根のところで左 2、右 0 → 差 2NG

Python での実装 (O(n^2) 版)

まずは素直な実装から見てみます。

Python

def height(tree, i): if i >= len(tree) or tree[i] is None: return 0 return 1 + max(height(tree, 2*i+1), height(tree, 2*i+2)) def isBalanced(tree): def check(i): if i >= len(tree) or tree[i] is None: return True if abs(height(tree, 2*i+1) - height(tree, 2*i+2)) > 1: return False return check(2*i+1) and check(2*i+2) return check(0)

動きはしますが、各ノードで height を再計算 しているので計算量は O(n^2) です。

O(n) 版 (post-order トリック)

効率良くするには、再帰の戻り値に 「高さ」または「unbalanced のマーカー」 を載せます。

Python

def isBalanced(tree): def visit(i): if i >= len(tree) or tree[i] is None: return 0 left = visit(2 * i + 1) if left == -1: return -1 right = visit(2 * i + 2) if right == -1: return -1 if abs(left - right) > 1: return -1 return 1 + max(left, right) return visit(0) != -1

-1 を「unbalanced を表すセンチネル値」として使うアイデアです。一度どこかで unbalanced を見つけたら、それ以降は -1 を伝播させて全体を打ち切る ことができます。これで計算量は O(n) に落ちます。

「異常を表すセンチネル」を再帰の戻り値に混ぜるテクは、tree DP / graph DP で頻出。

JavaScript での実装

JavaScript

function isBalanced(tree) { const visit = (i) => { if (i >= tree.length || tree[i] === null) return 0; const left = visit(2 * i + 1); if (left === -1) return -1; const right = visit(2 * i + 2); if (right === -1) return -1; if (Math.abs(left - right) > 1) return -1; return 1 + Math.max(left, right); }; return visit(0) !== -1; }

戻り値の流儀は同じです。-1 を見たら即座に親へ伝播させて、それ以上の計算を省略しています。

よくある間違い

1 つ目は 「根だけチェックする」。平衡は 全ノード の条件です。根の左右が差 1 以下でも、深いところで崩れていれば false。2 つ目は abs を忘れる。差は絶対値で見ます。3 つ目は O(n^2) 解を提出して timeout する。木が大きいとノード数の 2 乗に比例して遅くなるので、post-order でセンチネル -1 を使う形を覚えておきましょう。

やってみよう

  • [1, 2, 3, 4, 5, 6, 7] (完全二分木) の平衡判定をする → true
  • [1, 2, null, 3, null, null, null, 4] のような左に偏った木 → false
  • AVL 木のコードを読んでみる。挿入時に 回転 で平衡を保つ仕掛けが見える。

「全ノードで局所条件を満たすか」を post-order で O(n) で判定する、はこのレッスンで習得したい必勝パターン。

よくある質問

Q. BST はなぜ高速ですか?

A. 平衡している場合、検索・挿入・削除がいずれも O(log n) です。各ノードで「左<自分<右」を満たすため、半分ずつ探索範囲が絞れます。ただし偏ると O(n) に劣化するため、実用では赤黒木・AVL 木のような自動平衡化された実装を使います。

Q. in-order 走査で何が得られますか?

A. BST を in-order で巡回するとソート済みの順序で要素が得られます。これが BST と他のツリーの大きな違いで、ソート済みデータの逐次処理に向いています。範囲検索(lo 以上 hi 以下)も簡潔に書けます。

Q. BST に重複値を入れるとどうなりますか?

A. 実装次第ですが、通常は「等しい場合は右に入れる」「カウントを持って同じノードに記録する」のいずれかです。重複が多いユースケースなら TreeMap<Key, Integer> のように出現数を値に持たせる方が探索効率が上がります。

次のレッスン

次は 第2章まとめクイズ — 二分木 で、二分木を配列表現で受け取り、すべてのノードで左右部分木の高さの差が 1 以下なら true を返す関数を実装する を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 平衡判定 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 平衡判定 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. tree は BFS 順の配列で、null (Python では None) は欠損ノードを表す
  2. 全ノードで左右部分木の高さの差が 1 以下なら true を返す
  3. 空の木 ([]) や [null] は true として扱う

入出力例

test-cases.txt

isBalanced([1,2,3,null,4])true isBalanced([1])true isBalanced([1,2,3,4,5,6,7])true isBalanced([1,2,null,3])false isBalanced([1,null,2,null,null,null,3])false isBalanced([1,2,3,4,null,null,null,5])false

ヒント

main.py
main.py
学習モード

メモ

二分木の平衡判定

⌘S で保存