0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

《アルゴリズムを学ぶ》20.2分探索木(BST)

Last updated at Posted at 2025-03-26

Overview

二分探索木(Binary Search Tree, BST) は、順序付きデータを動的に管理するための基本データ構造。
検索・挿入・削除を平均 O(log N) で実行でき、基礎として必須知識。
1.二分探索木(BST)とは?
2.二分探索木の基本操作
3.平衡二分探索木(Balanced BST)
4.Python で BST を実装しない理由(重要)
5.典型問題を解いてみる

1. 二分探索木(BST)とは?

BST(Binary Search Tree)のルール
1. 左部分木の値は親より小さい
2. 右部分木の値は親より大きい
3. 部分木も再帰的に BST

        10
       /  \
      5    15
     / \   / \
    3   7 12 18

BST のメリット

  • 検索・挿入・削除が 平均 O(log N)
  • 順序付きデータの管理に強い
  • 中順(inorder)走査するとソート済みの列が得られる

BST のデメリット

  • 「偏った木」になると O(N) の計算量になる
    → 次章の「平衡二分探索木(AVL / 赤黒木)が重要になる」

2. 二分探索木の基本操作

(1) ノード定義

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

(2) 挿入(Insert)

def insert(root, key):
    if root is None:
        return Node(key)
    if key < root.key:
        root.left = insert(root.left, key)
    else:
        root.right = insert(root.right, key)
    return root
  • 計算量: 平均 $O(log N)$、最悪 $O(N)$(偏った木の場合)

(3) 探索(Search)

def search(root, key):
    if root is None or root.key == key:
        return root is not None
    return search(root.left, key) if key < root.key else search(root.right, key)

(4) 削除(Delete)

削除には 3 パターンがある:
1. 葉ノード(子なし)の削除 → そのまま削除
2. 子が 1 つのノードの削除 → 子を親に直接つなぐ
3. 子が 2 つのノードの削除 → 中序順で次の要素(右部分木の最小値)に置き換える

def min_value_node(node):
    current = node
    while current.left:
        current = current.left
    return current

def delete(root, key):
    if root is None:
        return root

    if key < root.key:
        root.left = delete(root.left, key)
    elif key > root.key:
        root.right = delete(root.right, key)
    else:
        # 子が0 or 1
        if root.left is None:
            return root.right
        elif root.right is None:
            return root.left

        # 子が2つ
        temp = min_value_node(root.right)
        root.key = temp.key
        root.right = delete(root.right, temp.key)

    return root

3. 平衡二分探索木(Balanced BST)

BST は偏ると O(N) になるため、現代ではほとんど**平衡木(Balanced BST)**が使われる。

代表的な平衡木

  • AVL Tree:高さ差を最大 1 に維持
  • Red-Black Tree(赤黒木)
    → C++ の set / map、JavaのTreeMapに使われている
    → Python の dict/setは赤黒木ではなくハッシュテーブル

4. Python で BST を実装しない理由(重要)

Pythonには標準でBSTが存在しない

理由:

用途 Python の実装
高速検索 → dict/set(ハッシュテーブル)
優先度付き → heapq(ヒープ)
順序付き集合 → bisect(ソートリスト+二分探索)

つまり:
PythonではBSTの用途はbisectやheapで十分代替可能

アルゴリズム・CS の理解のために BST を学ぶ意味は大きいが、Pythonの実務ではほとんど使わない。

5. 典型問題を解いてみる

例題1. BST の挿入

問題: 整数列を BST に挿入し、根ノードの値を出力せよ。

回答 
class Node:
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None

def insert(root, key):
    if root is None:
        return Node(key)
    if key < root.key:
        root.left = insert(root.left, key)
    else:
        root.right = insert(root.right, key)
    return root

N = int(input())
values = list(map(int, input().split()))

root = None
for v in values:
    root = insert(root, v)

print(root.key)  # ルートを出力

例題2. 中順(Inorder)走査でソートされた配列を出力

問題: BST に N 個の数値を挿入し、ソート済みの順序で出力せよ。

回答 
def inorder(root, res):
    if root:
        inorder(root.left, res)
        res.append(root.key)
        inorder(root.right, res)

N = int(input())
A = list(map(int, input().split()))

root = None
for x in A:
    root = insert(root, x)

res = []
inorder(root, res)
print(*res)

例題3.二分探索木の最近共通祖先(LCA)

問題:BST 上の 2 ノードの LCA(Lowest Common Ancestor)を求めよ。

回答
def lca(root, a, b):
    if root is None:
        return None
    if a < root.key and b < root.key:
        return lca(root.left, a, b)
    if a > root.key and b > root.key:
        return lca(root.right, a, b)
    return root.key  # このノードが分岐点

例題4.Python の bisect を使って「BST 風の問題」を解く

参考: ABC217 D - Cutting Woods

棒を切りながら、区間長を求める有名問題。
本質は 順序付き集合(Balanced BST)。

Python では bisect で代替。

回答
import bisect

L, Q = map(int, input().split())
cuts = [0, L]

for _ in range(Q):
    c, x = map(int, input().split())
    if c == 1:
        bisect.insort(cuts, x)
    else:
        i = bisect.bisect_left(cuts, x)
        print(cuts[i] - cuts[i - 1])

例題5.K番目に小さい値(Order Statistics)

※ C++ なら policy-based data structure(木構造)
※ Python はソートリスト+bisect で代替

回答
import bisect

N, Q = map(int, input().split())
A = []
for _ in range(N):
    x = int(input())
    bisect.insort(A, x)

for _ in range(Q):
    k = int(input())
    print(A[k-1])
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?