二分木の in-order 走査

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

二分木の in-order 走査

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

  • 二分木 (binary tree) は、各ノードが最大 2 つの子 (左と右) を持つ階層構造です
  • この章ではまず 走査 (traversal) を扱います
  • 本コースでは、ツリーを「BFS (幅優先) 順に並べた配列」で扱います

二分木の in-order 走査 とは

二分木を配列表現で受け取り、in-order (左 → 根 → 右) で訪問した値の配列を返す関数を実装する。

二分木 (binary tree) は、各ノードが最大 2 つの子 (左と右) を持つ階層構造です。連結リストが「一直線」だったのに対し、二分木は「枝分かれするデータ構造」と思ってください。コンパイラの構文木、ファイルシステム、データベースのインデックスなど、現実のシステム設計では至るところに現れます。

この章ではまず 走査 (traversal) を扱います。走査とは「木のすべてのノードを 1 度ずつ訪問して値を順番に取り出す」操作のことです。forwhile で配列を回す感覚を、ツリーに拡張したものと思えば OK です。

二分木の走査は、配列の for ループ再帰 に置き換えた版だと考えると馴染みやすい。

配列で二分木を表現する

本コースでは、ツリーを「BFS (幅優先) 順に並べた配列」で扱います。欠損ノードは null (Python では None) で穴埋めします。例えば [1, 2, 3, null, 4] は次の木に対応します。

diagram (will load when visible)
  • index 01root
  • index 12 は root の 左の子、index 23右の子
  • index 3欠損、index 442 の右の子

インデックス i のノードに対し、左の子は 2 * i + 1、右の子は 2 * i + 2 という対応で、配列要素が null または範囲外なら子は存在しません。これは 完全二分木の配列表現 と呼ばれる古典的な手法で、heap (ヒープ) でも使われています。

配列で表現すると、ポインタを書かずに木を扱える。ただし疎な木では配列がスカスカになる弱点もある。

in-order とは

走査には主に 3 種類あります。

  • in-order 左 → 根 → 右
  • pre-order 根 → 左 → 右
  • post-order 左 → 右 → 根

このレッスンでは in-order を扱います。[1, 2, 3, null, 4] の場合、左部分木 (2 を根とする) を in-order で回ると [2, 4]、続いて根の 1、最後に右部分木の 3。結果は [2, 4, 1, 3] です。

in-order が便利なのは、二分探索木 (BST) を in-order で走査すると値が昇順に並ぶ という性質があるからです。第 3 章の BST で再登場するので、ここで体で覚えておきましょう。

in-order = 左 → 根 → 右。BST では昇順 に出てくる性質をぜひ覚えておこう。

Python での実装例

Python

def inorder(tree): result = [] def visit(i): if i >= len(tree) or tree[i] is None: return visit(2 * i + 1) result.append(tree[i]) visit(2 * i + 2) visit(0) return result

visit(i) は「index i のノードを根とする部分木を in-order で走査して result に追加する」関数です。visit(2*i+1) で左部分木へ、result.append で根を訪問、最後に visit(2*i+2) で右部分木へ。再帰の 基底ケース は「範囲外」または「None」のときです。

JavaScript での実装例

JavaScript

function inorder(tree) { const result = []; const visit = (i) => { if (i >= tree.length || tree[i] === null) return; visit(2 * i + 1); result.push(tree[i]); visit(2 * i + 2); }; visit(0); return result; }

ロジックはまったく同じです。null 判定だけ気をつけてください。JS では tree[i] == null と書くと undefined も拾えますが、明示的に === null の方が事故が減ります。

よくある間違い

1 つ目は 範囲外チェック忘れi >= tree.length を入れないと、葉のさらに先で IndexError が出ます。2 つ目は null0 の取り違えif not tree[i]: と書くと値 0 のノードを欠損扱いしてしまいます。3 つ目は 左と右の順序を逆にしてしまう。in-order は 左 → 根 → 右、テスト結果が逆順 (右 → 根 → 左) になっていたら左右逆です。

やってみよう

  • [1, null, 2, null, null, 3] (root に右の子だけがある) を手で in-order してみる。[1, 3, 2] になる。
  • 完全二分木 [1, 2, 3, 4, 5, 6, 7] を in-order すると [4, 2, 5, 1, 6, 3, 7]
  • 反復版 (stack で iterative inorder) の書き方を調べてみる。再帰の深さ制限を回避できます。

よくある質問

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

次のレッスン

次は 二分木の pre-order 走査 で、二分木を配列表現で受け取り、pre-order (根 → 左 → 右) で訪問した値の配列を返す関数を学びます。

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

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

理解度チェック (30 秒)

Q. in-order 走査 とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. tree は BFS 順の配列で、null (Python では None) は欠損ノードを表す
  2. 再帰または反復で left -> root -> right の順に訪問する
  3. 戻り値は訪問順の値の配列 (List)。null は結果に含めない

入出力例

test-cases.txt

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

ヒント

main.py
main.py
学習モード

メモ

二分木の in-order 走査

⌘S で保存