Java | 「二分探索木(BST)」をもっと深く理解する

Java Java
スポンサーリンク

Javaで学ぶ「二分探索木(BST)」をもっと深く理解する(前半)

二分探索木(Binary Search Tree, BST)は、「探索を速くするための木構造」です。 配列やリストでの線形探索に比べて、うまく使えば検索・挿入・削除をとても効率よく行えます。 前半では、BSTの基本ルール、Javaでの実装、探索・挿入の流れを丁寧にかみ砕きながら、 「なぜ速くなるのか」「どこが重要なのか」を直感レベルで掴んでいくことを目標にします。 後半では、削除処理、バランス、計算量、セキュリティ・パフォーマンスの観点まで踏み込みます。

二分探索木(BST)の基本ルールを言語化する

BSTの本質は、たった一つのルールに集約されます。

「あるノードを基準にしたとき、 左の子(左部分木)にはそのノードより小さい値だけが入る。 右の子(右部分木)にはそのノードより大きい値だけが入る。」

これがすべてです。

例えば、次のような木を考えます。

8 / \ 3 10 / \ \ 1 6 14

この木では、

左側には 3, 1, 6 があり、すべて 8 より小さい 右側には 10, 14 があり、すべて 8 より大きい

さらに、部分木ごとにも同じルールが適用されています。

3 を根とする部分木では、左に 1、右に 6 があり、 1 < 3 < 6 という関係が保たれています。

この「どのノードを見ても、左は小さい・右は大きい」という自己相似的なルールが、 BSTの強さの源です。

Javaで二分探索木を表現する基本クラス

まずは、最もシンプルな BST のノードクラスを定義します。

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;

    TreeNode(int value) {
        this.value = value;
    }
}
Java

この TreeNode を使って、手動で木を作ることもできます。

TreeNode root = new TreeNode(8);
root.left = new TreeNode(3);
root.right = new TreeNode(10);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(6);
root.right.right = new TreeNode(14);
Java

この構造は、先ほどの図と同じ BST です。 ここから、「探索」「挿入」をアルゴリズムとして書いていきます。

BSTでの探索:なぜ速くなるのか

BSTの探索は、「比較しながら左右どちらかに絞り込んでいく」プロセスです。 これは二分探索と同じ発想で、「範囲を半分にしていく」ことで高速化します。

例えば、「値 6 を探したい」とします。

まず根の 8 と比較します。 6 は 8 より小さいので、「右側は絶対に違う」と分かり、左部分木だけを見ればよくなります。

次に 3 と比較します。 6 は 3 より大きいので、今度は右部分木だけを見ればよくなります。

最後に 6 と比較して、一致したので探索終了です。

このように、毎回「左右どちらか一方だけを見る」ことで、 探索範囲がどんどん半分に絞られていきます。

Javaで書く BST の探索メソッド

探索をコードにすると、次のようになります。

public static TreeNode search(TreeNode root, int target) {
    TreeNode current = root;
    while (current != null) {
        if (target == current.value) {
            return current;
        } else if (target < current.value) {
            current = current.left;
        } else {
            current = current.right;
        }
    }
    return null; // 見つからなかった場合
}
Java

このメソッドは、次のような流れで動きます。

現在のノードの値とターゲットを比較する 小さければ左へ、大きければ右へ進む 一致したらそのノードを返す 葉まで行っても見つからなければ null を返す

ここで深掘りしたいポイントは、「毎回左右どちらか一方しか見ない」ということです。 配列の線形探索では、最悪の場合すべての要素を見ますが、 BSTでは「半分ずつ捨てていく」ため、増え方が非常にゆるやかになります。

BSTへの挿入:ルールを壊さずにノードを追加する

次に、「値を BST に挿入する」処理を考えます。 挿入でも、基本的な考え方は探索と同じです。

  1. 根からスタートする
  2. 挿入したい値と現在のノードを比較する
  3. 小さければ左へ、大きければ右へ進む
  4. 進んだ先が null なら、そこに新しいノードを置く

Javaコードは次のようになります。

public static TreeNode insert(TreeNode root, int value) {
    if (root == null) {
        return new TreeNode(value);
    }

    TreeNode current = root;
    while (true) {
        if (value < current.value) {
            if (current.left == null) {
                current.left = new TreeNode(value);
                break;
            } else {
                current = current.left;
            }
        } else if (value > current.value) {
            if (current.right == null) {
                current.right = new TreeNode(value);
                break;
            } else {
                current = current.right;
            }
        } else {
            break;
        }
    }
    return root;
}
Java

この挿入処理は、「探索しながら空き場所を見つける」というイメージです。 重要なのは、「左は小さい・右は大きい」という BST のルールを一切壊していないことです。

深掘り:挿入の流れを具体例で追う

例えば、空の木に順番に値を挿入してみます。

最初に 8 を挿入すると、根が 8 になります。 次に 3 を挿入すると、3 は 8 より小さいので、左に入ります。 次に 10 を挿入すると、10 は 8 より大きいので、右に入ります。 次に 6 を挿入すると、6 は 8 より小さいので左へ、 さらに 3 より大きいので、3 の右に入ります。

このように、挿入は「探索の延長」として理解できます。 探索で「ここにあるはずだ」と思った場所が空いていれば、そこに置く。 それだけです。

中順(in-order)走査で「ソート済みの順番」が得られる

BSTの非常に重要な性質が、「中順走査をすると値が昇順に並ぶ」という点です。

中順走査とは、「左 → 自分 → 右」の順番でノードを訪れる DFS です。

Javaコードは次のようになります。

public static void inOrder(TreeNode node) {
    if (node == null) return;
    inOrder(node.left);
    System.out.println(node.value);
    inOrder(node.right);
}
Java

この inOrder(root) を実行すると、 BST に格納されている値が「小さい順」に表示されます。

これは、BSTのルール「左は小さい・右は大きい」が そのまま「ソート済みの順番」に対応しているからです。

この性質は、検索だけでなく「順序付きデータ構造」として BST を使うときに非常に重要です。

BSTの計算量の直感(前半の触り)

計算量の話は後半で詳しく扱いますが、 ここで直感だけ先に共有しておきます。

BSTが「ほどよくバランスしている」場合、 探索・挿入・削除の時間計算量は O(log n) になります。

これは、「毎回左右どちらか一方だけを見る」ことで、 探索範囲が半分ずつ減っていくからです。

一方で、値を「昇順にだけ」挿入していくと、 BSTは「ほぼ一本の鎖」のような形になり、 計算量が O(n) まで悪化します。

この「バランス」の問題は、後半で 平衡二分探索木(AVL木やRed-Black Tree)との関係も含めて扱います。

セキュリティ・パフォーマンスの観点(前半の導入)

セキュリティスペシャリストの視点から見ると、 BSTには次のようなポイントがあります。

入力によって木の形が変わる 攻撃者が「わざと偏ったデータ」を送ることで、 BST を「鎖状」にして性能を落とすことが理論上可能です。

探索・挿入の計算量が形に依存する バランスが崩れた BST は、最悪 O(n) になり、 大量データを扱う場面では DoS攻撃の入り口になり得ます。

そのため、実務では「自前の素朴な BST」ではなく、 Java標準の TreeMapTreeSet のような 平衡二分探索木ベースのデータ構造を使うことが多いです。

このあたりも後半で詳しく掘り下げます。

前半のまとめと後半への橋渡し

前半では、二分探索木(BST)の基本を

左は小さい・右は大きいというルール Javaでの基本クラスと構造 探索と挿入の流れ 中順走査によるソート済み順の取得 計算量とバランスの直感

という軸で整理しました。

後半では、さらに一歩進んで

削除アルゴリズム(3パターン) バランスが崩れたときの問題点 平衡二分探索木との関係(TreeMap / TreeSet の裏側イメージ) 計算量・セキュリティ・パフォーマンスの観点から見た BST の使い方

を、Javaコードとともに詳しく解説していきます。

タイトルとURLをコピーしました