リリース・改善中
Algorithm
木の用語と走査、二分探索木の探索・挿入・削除、二分ヒープと優先度付きキューを、仕組みと計算量、実装コードで学びます。
木はルートから始まり、親と子へ枝分かれしていく階層型のデータ構造です。二分木に「小さいキーは左、大きいキーは右」という規則を加えると二分探索木(BST)になり、「親は子以下」という規則と完全二分木の形を加えると二分ヒープになります。このトピックでは木の用語、4種類の走査、二分探索木、平衡木、ヒープと優先度付きキューをまとめて扱います。
木とヒープは多くの実システムの土台です。データベースのインデックスはB木、C++のstd::mapやJavaのTreeMapは赤黒木で、OSのスケジューラやタイマー、ダイクストラ法の最短経路も木やヒープに支えられています。高さが性能を左右すること、ヒープが1つの配列に収まることを理解すれば、どの構造を選ぶべきか判断でき、技術面接やコーディングテストでもよく問われる内容です。
まず小さな木を紙に描き、行きがけ順・通りがけ順・帰りがけ順・レベル順の走査を手でたどってみましょう。次に二分探索木の挿入・探索・削除とヒープのsift up・sift downを自分で実装し、heapifyがO(n)になる理由を確かめます。最後にPythonのheapq、C++のpriority_queue、JavaのPriorityQueueといった標準ツールで、上位K件やソート済みリストのマージといった問題を解いてみてください。
ルート・葉・深さ・高さといった用語と、行きがけ順・通りがけ順・帰りがけ順・レベル順の走査を押さえれば、ほとんどの木の問題を読んで解けます。
小さいキーは左、大きいキーは右という規則により、探索・挿入・削除は木の高さに比例した時間で済み、通りがけ順の走査でソート順が得られます。
ソート済みの入力では二分探索木が連結リストのように伸びてしまいます。AVL木や赤黒木は回転によって高さをO(log n)に保ちます。
完全二分木を配列に格納することで、最小値の参照はO(1)、挿入と取り出しはO(log n)、配列全体のheapifyはO(n)で行えます。
Nodeクラスで二分探索木を作り、bst_insertはキーを比較しながら空いている位置を探して新しいノードを付け、bst_searchはループで下りながらキーを探します。通りがけ順の走査でキーが昇順に並ぶことを確認できます。MinHeapはリストに要素を持ち、pushではsift up、popではsift downでヒープの性質を保つため、値が小さい順に取り出されます。
trees_and_heaps.py
# Binary search tree (insert/search) and a binary min-heap
class Node:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def bst_insert(root, key):
if root is None:
return Node(key)
if key < root.key:
root.left = bst_insert(root.left, key)
elif key > root.key:
root.right = bst_insert(root.right, key)
return root # duplicates are ignored
def bst_search(root, key):
while root is not None and root.key != key:
root = root.left if key < root.key else root.right
return root is not None
def inorder(root, out):
if root is not None:
inorder(root.left, out)
out.append(root.key)
inorder(root.right, out)
return out
class MinHeap:
def __init__(self):
self.a = []
def push(self, x):
a = self.a
a.append(x)
i = len(a) - 1
while i > 0 and a[(i - 1) // 2] > a[i]: # sift up
p = (i - 1) // 2
a[i], a[p] = a[p], a[i]
i = p
def pop(self):
a = self.a
top, last = a[0], a.pop()
if a:
a[0] = last
i, n = 0, len(a)
while True: # sift down
small = i
for c in (2 * i + 1, 2 * i + 2):
if c < n and a[c] < a[small]:
small = c
if small == i:
break
a[i], a[small] = a[small], a[i]
i = small
return top
def __len__(self):
return len(self.a)
root = None
for k in [50, 30, 70, 20, 40, 60, 80]:
root = bst_insert(root, k)
print(inorder(root, [])) # [20, 30, 40, 50, 60, 70, 80]
print(bst_search(root, 60), bst_search(root, 65)) # True False
heap = MinHeap()
for x in [5, 3, 8, 1, 9, 2]:
heap.push(x)
print([heap.pop() for _ in range(len(heap))]) # [1, 2, 3, 5, 8, 9]
インストールから 木構造とヒープ の中心となる考え方まで、6 章で順を追って学びます。
木構造とヒープ について質問し、経験を共有し、意見を交わす場所です。
まだディスカッションはありません。最初の話題を始めましょう。
python trees_and_heaps.py
コメント 0件
ログイン · ログインするとコメントできます。
最初のコメントを書いてみましょう。