二分探索木 (BST) への挿入

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

二分探索木 (BST) への挿入

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

  • BST の鉄則は「左の子 << 右の子」
  • 昇順や降順に挿入していくと、BST は片側に偏った「棒のような形」になってしまいます
  • 二分探索木 (Binary Search Tree, 略して BST) は、各 ノード が最大 2 つの子を持ち、左の子は親より小さい / 右の子は親より大きい という不変条件を守る 木構造 です

二分探索木 (BST) への挿入 とは

Node クラスを使って二分探索木を構築し、挿入後の in-order 走査 結果を返す。OOP と再帰の合わせ技。

二分探索木 (Binary Search Tree, 略して BST) は、各 ノード が最大 2 つの子を持ち、左の子は親より小さい / 右の子は親より大きい という不変条件を守る 木構造 です。本レッスンでは Node クラスを使って BST を空から構築し、複数の値を順に 挿入 していった結果を in-order (中順) 走査で返します。

BST の鉄則は「左の子 << 右の子」。挿入検索 も、この不変条件を守りながら左右どちらかへ進むだけ。

Node クラスの設計

木構造ノード は、value と「左の子 への参照」「右の子 への参照」を持ちます。

Python

class Node: def __init__(self, value): self.value = value self.left = None self.right = None

リンクリストノードnext だけでしたが、ノードleftright の 2 本に分岐します。これが「」の名前の由来でもあります。

挿入アルゴリズム

BST に新しい値 v を入れる手順は次の通りです。

  1. ルートから出発する
  2. v が現在の ノードvalue より 小さい なら左へ、大きい なら右へ進む
  3. 進んだ先が None だったら、そこに新しい Node(v) を置く

再帰 で書くと、こうなります。

Python

def insert(node, v): if node is None: return Node(v) if v < node.value: node.left = insert(node.left, v) elif v > node.value: node.right = insert(node.right, v) return node

node.left = insert(node.left, v)代入 がポイント。None に当たって新規 Node が返ってきたら、それを親の left / right に繋ぎ直しています。

挿入の流れを図解

5, 3, 8, 1, 4 を空 BST に順に挿入する流れは次のようになります。

diagram (will load when visible)

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

  • 最初に 5 がルートに置かれ、35 > 3 なので左へ、85 < 8 なので右へ、15 > 1, 3 > 13 の左へ、45 > 4, 3 < 43 の右へ、と進みます

最初に 5 がルートに置かれ、35 > 3 なので左へ、85 < 8 なので右へ、15 > 1, 3 > 13 の左へ、45 > 4, 3 < 43 の右へ、と進みます。

in-order 走査

BSTin-order (中順) で走査すると、ソート済みの配列 が得られます。これが BST の大きな利点。

Python

def inorder(node, out): if node is None: return inorder(node.left, out) out.append(node.value) inorder(node.right, out)

「左 → 自分 → 右」の順で再帰的に訪問するだけで、自然と昇順になります。先ほどの [5, 3, 8, 1, 4]in-order で取り出すと [1, 3, 4, 5, 8] になるはずです。

JavaScript での実装

JavaScript

class Node { constructor(value) { this.value = value; this.left = null; this.right = null; } } function insert(node, v) { if (node === null) return new Node(v); if (v < node.value) node.left = insert(node.left, v); else if (v > node.value) node.right = insert(node.right, v); return node; }

このレッスンでやること

関数 bstInsertAll(values) を実装してください。values を空 BST順番に 挿入 し、最後に in-order で走査した結果の 配列 を返します。重複 する値は無視 (= 何もしない) してください。例として [5, 3, 8, 1, 4][1, 3, 4, 5, 8][10, 5, 5, 15][5, 10, 15]

よくある間違い

node.left = insert(node.left, v)代入 を忘れると、新ノードがツリーに繋がらない。

  • if v < node.valueif v <= node.value にしてしまうと、重複 に入ってしまう。本問では重複は無視。
  • in-order の順序を間違える (左 → 自分 → 右 が正解)。
  • 空配列 を渡されたときに Noneinorder に渡してエラー。先に if node is None: return で弾く。

BST が崩れるとどうなるか

BST の検索/挿入が O(log n) になるのは「 が平衡している」場合だけ。最悪は O(n) まで悪化する。

昇順や降順に挿入していくと、BST は片側に偏った「棒のような形」になってしまいます。たとえば [1, 2, 3, 4, 5] を順に空 BST に挿入すると、右だけにずっと伸びる リンクリスト のような構造になり、検索が O(n) になります。これを防ぐために、実用的な実装では AVL 木赤黒木 といった 自己平衡 する BST が使われます。本コースでは基本の挿入のみを扱いますが、偏り の問題があることは覚えておきましょう。

やってみよう

再帰の insertinorder を組み合わせる感覚を身につけましょう。OOP (クラスでノードを定義) と 再帰 (関数で操作) の組み合わせは、データ構造 の実装ではほぼ必ず登場します。

よくある質問

Q. 重複した値を挿入しようとするとどうなりますか?

A. 本レッスンの実装では、重複値は無視します。v < node.value / v > node.value のどちらにも当てはまらない場合(等しい場合)は何もせずに node をそのまま返します。重複を許したい場合は「左か右のどちらか一方に入れる」ルールを決めて実装します。

Q. 再帰版と反復版はどっちが良いですか?

A. 反復版が安定して高速です。再帰版は読みやすい代わりにスタック消費があります。配列の長さが極端に大きい(数億)場合のみ反復版を選び、教育用や読みやすさ重視なら再帰版で十分です。Python は再帰深さ制限にも注意してください。

Q. lower_bound / upper_bound とは何ですか?

A. lower_bound は target 以上が最初に現れる位置、upper_bound は target より大きい値が最初に現れる位置を返します。重複要素のある配列で「何個含まれているか」を upper - lower で求める典型イディオムで使います。

次のレッスン

次は カウンタクラス(機能合成) で、状態を持つ Counter クラスを実装し、inc / dec / reset の操作列を順に適用して最終的なカウント値を返す方法を学びます。

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

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

理解度チェック (30 秒)

Q. BST insert とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. Node クラスを定義し、value / left / right を持たせる
  2. 再帰関数で BST に挿入する。None に当たったら新ノードを返し、親側で left / right に繋ぐ
  3. in-order (左 → 自分 → 右) で走査して、ソート済み配列を返す。重複は無視する

入出力例

test-cases.txt

bstInsertAll([5,3,8,1,4])[1,3,4,5,8] bstInsertAll([10,5,5,15])[5,10,15] bstInsertAll([1,2,3,4,5])[1,2,3,4,5] bstInsertAll([5,4,3,2,1])[1,2,3,4,5] bstInsertAll([42])[42] bstInsertAll([7,3,9,1,5,8,10])[1,3,5,7,8,9,10]

ヒント

main.py
main.py
学習モード

メモ

二分探索木 (BST) への挿入

⌘S で保存