# 二分探索木 下図の二分探索木(binary search tree)は、次の条件を満たします。 1. 根について、左の部分木のすべての値 $<$ 根の値 $<$ 右の部分木のすべての値が成り立ちます。 2. どのノードの左右の部分木も二分探索木であり、同じく条件 `1.` を満たします。  ## 二分探索木の操作 クラス `BinarySearchTree` にまとめ、根ノードを指すメンバー変数 `root` を宣言します。 ### ノードの探索 目的の値 `num` が与えられたら、二分探索木の性質を使って探せます。下図のように、ノード `cur` を宣言して根 `root` から出発し、`cur.val` と `num` の大小を繰り返し比較します。 - `cur.val < num` なら、目的のノードは右の部分木にあるので、`cur = cur.right` とします。 - `cur.val > num` なら、目的のノードは左の部分木にあるので、`cur = cur.left` とします。 - `cur.val = num` なら、見つかったのでループを抜け、そのノードを返します。 === "<1>"  === "<2>"  === "<3>"  === "<4>"  二分探索木の探索は、二分探索と同じく、各反復で候補の半分を除外する仕組みです。反復回数は最大で木の高さとなり、木が平衡なら $O(\log n)$ 時間です。コード例を示します。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} ``` ### ノードの挿入 挿入する要素 `num` に対して、「左の部分木 < 根 < 右の部分木」という性質を保つため、下図の手順で挿入します。 1. **挿入位置を探す**:探索と同じく根から出発し、現在の値と `num` を比較しながら下へ進みます。葉を越えて `None` に達したらループを抜けます。 2. **その位置に挿入する**:値 `num` のノードを初期化し、`None` の位置へ置きます。  実装では、次の二点に注意します。 - 重複するノードは定義に反するため許しません。挿入するノードがすでに存在すれば、何もせずに戻ります。 - 挿入のため、ノード `pre` に前回の反復のノードを保存します。これにより、`None` に達したときに親を取得して挿入できます。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} ``` 探索と同じく、挿入には $O(\log n)$ 時間かかります。 ### ノードの削除 まず目的のノードを見つけてから削除します。挿入と同じく、削除後も「左の部分木 < 根 < 右の部分木」を保つ必要があります。そのため、子の数が 0、1、2 の三つの場合に分けて処理します。 下図のように、削除するノードの次数が $0$ なら葉なので、直接削除できます。  下図のように、次数が $1$ なら、そのノードを子に置き換えれば済みます。  次数が $2$ の場合は直接削除できず、別のノードで置き換える必要があります。「左の部分木 $<$ 根 $<$ 右の部分木」を保つため、**右の部分木の最小ノードか、左の部分木の最大ノードを使えます**。 右の部分木の最小ノード、つまり通りがけ順で次のノードを選ぶとすると、手順は下図のとおりです。 1. 削除するノードの次に通りがけ順で現れるノードを探し、`tmp` とします。 2. 削除するノードの値を `tmp` の値で上書きし、木から `tmp` を再帰的に削除します。 === "<1>"  === "<2>"  === "<3>"  === "<4>"  削除も $O(\log n)$ 時間です。削除対象の探索に $O(\log n)$、通りがけ順の後続ノードの取得に $O(\log n)$ 時間かかります。コード例を示します。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} ``` ### 通りがけ順では整列済みになる 下図のように、通りがけ順走査は「左 $\rightarrow$ 根 $\rightarrow$ 右」の順で進み、二分探索木では「左の子 $<$ 根 $<$ 右の子」の大小関係が成り立ちます。 そのため、通りがけ順では常に次に小さいノードを優先して訪問します。ここから、**二分探索木の通りがけ順走査の列は昇順になる**という重要な性質が得られます。 この性質を利用すれば、追加のソートなしに $O(n)$ 時間で整列済みのデータを取得でき、非常に効率的です。  ## 二分探索木の効率 データを配列または二分探索木で格納する場合を考えます。下表では、二分探索木の各操作の時間計算量はすべて対数時間で、安定した高い性能を示します。配列のほうが効率的なのは、追加が多く、探索と削除が少ない場合だけです。
表