二分探索木

高速な検索・挿入・削除が可能な木構造。

二分探索木
各ノードが最大2つの子を持つ木構造。左の子は親より小さく、右の子は親より大きい。
ノード
木構造の各要素。キー、左の子、右の子を持つ。
木構造の最上位ノード。
子を持たないノード。
中間順
左の子、親、右の子の順に走査する。
前順
親、左の子、右の子の順に走査する。
後順
左の子、右の子、親の順に走査する。
平衡
木の左右の高さがほぼ等しい状態。
AVL木
自己平衡二分探索木。挿入・削除時に自動的に平衡を保つ。

二分探索木

家系図

二分探索木は「家系図」のようなもの。各ノードは最大2つの子を持ち、左の子は親より小さく、右の子は親より大きい。ある人を探すとき、親の年齢と比較して、左か右かを決定します。これを繰り返すことで、効率的に人を探せます。

なぜ二分探索木が必要か?

二分探索木は、高速な検索・挿入・削除が可能な木構造です。平均的な計算量はO(log n)で、配列のO(n)よりも効率的です。特に、データの検索・挿入・削除が頻繁な場合に適しています。

いつ使うか?

  • 高速な検索が必要な場合
  • データの挿入・削除が頻繁な場合
  • データをソート済みの状態で維持したい場合
  • 範囲検索が必要な場合

実践テクニック

再帰を使う

二分探索木の操作は、再帰を使って簡潔に実装できます。挿入、検索、削除、走査など、多くの操作が再帰的に実装できます。

走査方法を理解する

二分探索木の走査方法には、中間順、前順、後順があります。中間順は、左の子、親、右の子の順に走査し、ソートされた結果を得られます。

計算量を理解する

二分探索木の操作の計算量を理解することは、パフォーマンス最適化に重要です。例:挿入は平均O(log n)、検索は平均O(log n)、走査はO(n)です。これらを理解して、適切なデータ構造を選択します。

平衡を保つ

二分探索木が平衡していない場合、最悪の計算量はO(n)になります。平衡二分探索木(AVL木、赤黒木など)を使うことで、常にO(log n)の計算量を保てます。

Binary Search Tree Implementation
# 二分探索木の基本操作
class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
class BinarySearchTree:
def __init__(self):
self.root = None
def insert(self, key):
self.root = self._insert(self.root, key)
def _insert(self, node, key):
if node is None:
return Node(key)
if key < node.key:
node.left = self._insert(node.left, key)
else:
node.right = self._insert(node.right, key)
return node
def search(self, key):
return self._search(self.root, key)
def _search(self, node, key):
if node is None or node.key == key:
return node
if key < node.key:
return self._search(node.left, key)
return self._search(node.right, key)
def delete(self, key):
self.root = self._delete(self.root, key)
def _delete(self, node, key):
if node is None:
return node
if key < node.key:
node.left = self._delete(node.left, key)
elif key > node.key:
node.right = self._delete(node.right, key)
else:
# ノードが1つ以下の子を持つ場合
if node.left is None:
return node.right
elif node.right is None:
return node.left
# ノードが2つの子を持つ場合
node.key = self._min_value_node(node.right).key
node.right = self._delete(node.right, node.key)
return node
def _min_value_node(self, node):
current = node
while current.left is not None:
current = current.left
return current
def inorder(self):
result = []
self._inorder(self.root, result)
return result
def _inorder(self, node, result):
if node:
self._inorder(node.left, result)
result.append(node.key)
self._inorder(node.right, result)
# 使用例
bst = BinarySearchTree()
bst.insert(50)
bst.insert(30)
bst.insert(70)
bst.insert(20)
bst.insert(40)
bst.insert(60)
bst.insert(80)
# 検索
result = bst.search(40)
print(result.key if result else "Not found") # 40
# 走査(中間順)
print(bst.inorder()) # [20, 30, 40, 50, 60, 70, 80]
# 削除
bst.delete(30)
print(bst.inorder()) # [20, 40, 50, 60, 70, 80]

基本操作

二分探索木の基本操作を確認する。

Basic Operations
# 二分探索木の基本操作
class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
class BinarySearchTree:
def __init__(self):
self.root = None
def insert(self, key):
self.root = self._insert(self.root, key)
def _insert(self, node, key):
if node is None:
return Node(key)
if key < node.key:
node.left = self._insert(node.left, key)
else:
node.right = self._insert(node.right, key)
return node
def search(self, key):
return self._search(self.root, key)
def _search(self, node, key):
if node is None or node.key == key:
return node
if key < node.key:
return self._search(node.left, key)
return self._search(node.right, key)
def delete(self, key):
self.root = self._delete(self.root, key)
def _delete(self, node, key):
if node is None:
return node
if key < node.key:
node.left = self._delete(node.left, key)
elif key > node.key:
node.right = self._delete(node.right, key)
else:
# ノードが1つ以下の子を持つ場合
if node.left is None:
return node.right
elif node.right is None:
return node.left
# ノードが2つの子を持つ場合
node.key = self._min_value_node(node.right).key
node.right = self._delete(node.right, node.key)
return node
def _min_value_node(self, node):
current = node
while current.left is not None:
current = current.left
return current
def inorder(self):
result = []
self._inorder(self.root, result)
return result
def _inorder(self, node, result):
if node:
self._inorder(node.left, result)
result.append(node.key)
self._inorder(node.right, result)
# 使用例
bst = BinarySearchTree()
bst.insert(50)
bst.insert(30)
bst.insert(70)
bst.insert(20)
bst.insert(40)
bst.insert(60)
bst.insert(80)
# 検索
result = bst.search(40)
print(result.key if result else "Not found") # 40
# 走査(中間順)
print(bst.inorder()) # [20, 30, 40, 50, 60, 70, 80]
# 削除
bst.delete(30)
print(bst.inorder()) # [20, 40, 50, 60, 70, 80]

計算量

二分探索木の操作の計算量を確認する。

Complexity
# 二分探索木の計算量
class BinarySearchTree:
# ... (上記の実装を参照)
# 挿入: 平均O(log n)、最悪O(n)
bst.insert(50)
# 検索: 平均O(log n)、最悪O(n)
result = bst.search(40)
# 削除: 平均O(log n)、最悪O(n)
bst.delete(30)
# 走査: O(n)
print(bst.inorder())

使用例

二分探索木の実際の使用例を確認する。

Use Cases
# 二分探索木の使用例
# データの高速検索
class BinarySearchTree:
# ... (上記の実装を参照)
# 使用例
bst = BinarySearchTree()
bst.insert(50)
bst.insert(30)
bst.insert(70)
# データの検索
result = bst.search(30)
print(result.key if result else "Not found") # 30
# データの削除
bst.delete(30)
result = bst.search(30)
print(result.key if result else "Not found") # Not found

平衡二分探索木

平衡二分探索木(AVL木)の実装を確認する。

Balanced Binary Search Tree
# 平衡二分探索木
class AVLNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1
class AVLTree:
def __init__(self):
self.root = None
def insert(self, key):
self.root = self._insert(self.root, key)
def _insert(self, node, key):
# 標準的な挿入
if not node:
return AVLNode(key)
if key < node.key:
node.left = self._insert(node.left, key)
else:
node.right = self._insert(node.right, key)
# 高さを更新
node.height = 1 + max(self._get_height(node.left),
self._get_height(node.right))
# 平衡を確認
balance = self._get_balance(node)
# 不平衡な場合、回転を行う
if balance > 1 and key < node.left.key:
return self._right_rotate(node)
if balance < -1 and key > node.right.key:
return self._left_rotate(node)
if balance > 1 and key > node.left.key:
node.left = self._left_rotate(node.left)
return self._right_rotate(node)
if balance < -1 and key < node.right.key:
node.right = self._right_rotate(node.right)
return self._left_rotate(node)
return node
def _get_height(self, node):
if not node:
return 0
return node.height
def _get_balance(self, node):
if not node:
return 0
return self._get_height(node.left) - self._get_height(node.right)
def _left_rotate(self, z):
y = z.right
T2 = y.left
# 回転を行う
y.left = z
z.right = T2
# 高さを更新
z.height = 1 + max(self._get_height(z.left),
self._get_height(z.right))
y.height = 1 + max(self._get_height(y.left),
self._get_height(y.right))
return y
def _right_rotate(self, z):
y = z.left
T3 = y.right
# 回転を行う
y.right = z
z.left = T3
# 高さを更新
z.height = 1 + max(self._get_height(z.left),
self._get_height(z.right))
y.height = 1 + max(self._get_height(y.left),
self._get_height(y.right))
return y
def inorder(self):
result = []
self._inorder(self.root, result)
return result
def _inorder(self, node, result):
if node:
self._inorder(node.left, result)
result.append(node.key)
self._inorder(node.right, result)
# 使用例
avl = AVLTree()
avl.insert(10)
avl.insert(20)
avl.insert(30)
avl.insert(40)
avl.insert(50)
avl.insert(25)
# 走査(中間順)
print(avl.inorder()) # [10, 20, 25, 30, 40, 50]

合格ライン

二分探索木の基本操作ができる
二分探索木の計算量を理解している
二分探索木の走査方法を理解している
平衡二分探索木を理解している
二分探索木の使い分けができる

演習課題

課題1: 二分探索木の実装
二分探索木クラスを実装し、要素の追加・削除・検索を行うメソッドを作成してください。
課題2: 走査の実装
二分探索木の走査(中間順、前順、後順)を実装してください。
課題3: 平衡二分探索木の実装
平衡二分探索木(AVL木)を実装してください。

参考文献

この記事は以下の公的ガイドライン/標準に基づいています。