二分木の高さ

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

二分木の高さ

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

  • 木の 高さ (height) は「根から葉までで一番長い経路に含まれるノード数」のことです
  • 二分木の高さは、データ構造の 性能 に直結します
  • 後のレッスンでやる「平衡判定」も、各ノードの左右の高さの差を見て判定します

二分木の高さ とは

二分木を配列表現で受け取り、根から葉までの最大のノード数 (高さ) を返す関数を実装する。本レッスンでは、二分木の高さ の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

木の 高さ (height) は「根から葉までで一番長い経路に含まれるノード数」のことです。例えば、ノード 1 つだけの木は高さ 1、空っぽの木は高さ 0、左右に 1 段子を持つ木は高さ 2 です。

高さは「最も深い葉までの段数」。ノード数で数える流儀辺の数で数える流儀 があるが、本コースではノード数で統一する。

なぜ高さを計算したいのか

二分木の高さは、データ構造の 性能 に直結します。

  • 高さ h の二分探索木 (BST) では、検索・挿入の最悪計算量が O(h)
  • 平衡している木なら h = O(log n)、退化していると h = O(n)
  • ヒープや平衡木 (AVL, 赤黒木) は 高さを log n に保つ ことで性能を保証している

後のレッスンでやる「平衡判定」も、各ノードの左右の高さの差を見て判定します。つまり高さは 木のあらゆる解析の基礎 になる量です。

高さを O(log n) に保つのが平衡木の使命。生 BST だと挿入順次第で O(n) まで悪化する。

例で確認する

[1, 2, 3, null, 4] の高さは?

diagram (will load when visible)
  • 3 までの経路 → 1 → 3、ノード数 2
  • 4 までの経路 → 1 → 2 → 4、ノード数 3

最も長いのは 3。よって高さは 3 です。

他の例も見ておきましょう。

  • [1] → 高さ 1 (ノード 1 つ)
  • [] (空) → 高さ 0
  • 完全二分木 [1, 2, 3, 4, 5, 6, 7] → 高さ 3 (log2(8) = 3)

Python での実装

再帰で書くのが定石です。post-order の典型例で、子の高さを取得してから親の高さを決めます。

Python

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

ロジックは単純です。

  • ノードが存在しない (i が範囲外 or None) → 高さ 0
  • それ以外 → 1 + max(左部分木の高さ, 右部分木の高さ)

+ 1自分自身を 1 段としてカウント するためのものです。これを忘れると 辺の数 を返してしまいます。

JavaScript での実装

JavaScript

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

まったく同じ構造です。Math.max の引数は 2 つ で渡せば OK。Math.max(...arr) のように配列を展開する必要はありません。

反復で書く方法

反復で書くなら BFS を使い、レベルごとに進めた回数を数えます。

Python

from collections import deque def height_iter(tree): if not tree or tree[0] is None: return 0 queue = deque([0]) h = 0 while queue: h += 1 for _ in range(len(queue)): i = queue.popleft() for c in (2*i+1, 2*i+2): if c < len(tree) and tree[c] is not None: queue.append(c) return h

for _ in range(len(queue)) のテクニックを覚えておくと、レベルごとの処理がきれいに書けます。BFS で「現在のレベルの全ノードを一度に処理」する常套句です。

よくある間違い

1 つ目は + 1 を忘れる。これだと辺の数になります (空木は -1 を返すことも)。2 つ目は 基底ケースを 1 にしてしまう。空ノードは 0、葉ノードが 1 です。3 つ目は max(left, right)left + right にしてしまう。これは別の問題 (経路和) の話です。

やってみよう

  • 高さ n退化した木 (一直線) を作って計算してみる。[1, null, 2, null, null, null, 3, ...] のような形。
  • 「木のサイズ (ノード数)」を計算する関数を書く。再帰の 1 + visit(left) + visit(right) で書ける。
  • 「木の 直径 (任意の 2 つの葉間の最長経路)」を高さの計算と組み合わせて求めてみる。O(n) で書けるはず。

高さの計算は post-order DP の最小単位。これを書けないとバランス判定も BST 検証も書けない。

よくある質問

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> のように出現数を値に持たせる方が探索効率が上がります。

次のレッスン

次は 二分木の平衡判定 で、各ノードの左右部分木の高さの差を調べて木がバランスしているかを判定する方法を学びます。

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

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

理解度チェック (30 秒)

Q. 木の高さ とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. tree は BFS 順の配列で、null (Python では None) は欠損ノードを表す
  2. ノードがない場合は高さ 0、ノード 1 つの場合は高さ 1 を返す
  3. 再帰または反復で実装すること

入出力例

test-cases.txt

height([1,2,3,null,4])3 height([1])1 height([1,2,3])2 height([1,2,3,4,5,6,7])3 height([1,null,2,null,null,null,3])3 height([5,3,8,1,4,null,9])3

ヒント

main.py
main.py
学習モード

メモ

二分木の高さ

⌘S で保存