樹、集合與雜湊表
樹、集合與雜湊表是 B4 中的非線性抽象資料型別:二元搜尋樹(Binary Search Tree, BST)讓資料保持排序並能快速搜尋,集合(set)處理不重複元素與集合運算,雜湊表(hash table)則用雜湊函式達到平均 O(1) 的查詢速度。本頁屬於 HL 專屬的 B4 單元。
HL 專屬內容
本頁屬於 B4.1,只有 HL 需要學。
樹的基本名詞
| 名詞 | 意義 |
|---|---|
| 根(root) | 最上層的節點,沒有父節點 |
| 父節點/子節點(parent/child) | 直接相連的上下層節點 |
| 葉節點(leaf) | 沒有子節點的節點 |
| 子樹(subtree) | 某節點與其所有後代 |
| 高度(height) | 從根到最深葉節點的邊數 |
| 二元樹(binary tree) | 每個節點最多兩個子節點 |
二元搜尋樹如何保持排序?
BST 的規則:對每個節點,左子樹所有值 < 節點值 < 右子樹所有值。依序插入 50, 30, 70, 20, 40, 60, 80 得到:
flowchart TD
A((50)) --> B((30))
A --> C((70))
B --> D((20))
B --> E((40))
C --> F((60))
C --> G((80))
- 搜尋:從根開始,比目標小往左、比目標大往右。平衡時每一步排除一半,O(log n)。
- 最差情況:依序插入已排序資料(10, 20, 30, …),樹退化成一條鏈,搜尋變成 O(n)。實務上用自我平衡樹(AVL 樹、紅黑樹)避免。
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
class BST:
def __init__(self):
self.root = None
def insert(self, value):
self.root = self._insert(self.root, value)
def _insert(self, node, value):
if node is None:
return TreeNode(value)
if value < node.value:
node.left = self._insert(node.left, value)
elif value > node.value:
node.right = self._insert(node.right, value)
return node # 重複值不插入
def contains(self, value):
cur = self.root
while cur is not None:
if value == cur.value:
return True
cur = cur.left if value < cur.value else cur.right
return False
樹有哪三種走訪方式?
走訪(traversal)是依特定順序拜訪每個節點,三種深度優先走訪的差別在「何時處理根節點」:
| 走訪 | 順序 | 上圖結果 | 用途 |
|---|---|---|---|
| 前序(pre-order) | 根 → 左 → 右 | 50 30 20 40 70 60 80 | 複製樹、輸出樹結構 |
| 中序(in-order) | 左 → 根 → 右 | 20 30 40 50 60 70 80 | BST 依大小排序輸出 |
| 後序(post-order) | 左 → 右 → 根 | 20 40 30 60 80 70 50 | 刪除整棵樹、計算運算式樹 |
def in_order(node):
if node is None:
return
in_order(node.left)
print(node.value, end=" ")
in_order(node.right)
如何刪除 BST 節點?
- 葉節點:直接移除。
- 只有一個子節點:讓子節點取代它的位置。
- 有兩個子節點:用 中序後繼(in-order successor,右子樹中最小的值) 取代它,再刪除那個後繼節點。
集合有哪些運算?
集合(set)是 不含重複元素、沒有順序 的 ADT,主要操作:
| 運算 | 意義 | Python |
|---|---|---|
| 成員判斷 | x 是否在集合中 | x in A |
| 聯集(union) | 在 A 或 B | A \| B |
| 交集(intersection) | 同時在 A 和 B | A & B |
| 差集(difference) | 在 A 但不在 B | A - B |
| 子集(subset) | A 的元素都在 B | A <= B |
cs = {"Amy", "Ben", "Chris"}
math = {"Ben", "Dora"}
print(cs & math) # {'Ben'}:同時修兩科
print(cs - math) # {'Amy', 'Chris'}:只修 CS
集合通常用雜湊表實作,成員判斷平均 O(1);用 BST 實作則能依序列出元素。
雜湊表如何快速查詢?
雜湊表用 雜湊函式(hash function) 把鍵(key)轉成陣列索引,直接到該位置存取資料:
index = hash(key) mod 表格大小
例如表格大小 10,用學號除以 10 的餘數當索引:學號 1234 → 索引 4。
碰撞怎麼處理?
兩個不同的鍵算出同一個索引稱為 碰撞(collision),例如 1234 與 5674 都對應索引 4。
- 鏈結法(chaining):每個位置放一個串列,碰撞的資料串在同一個串列中。
- 開放定址法(open addressing)/線性探測(linear probing):位置被佔就往下一格找空位,搜尋時也依同樣規則往下找。
class HashTable:
def __init__(self, size=10):
self.size = size
self.buckets = [[] for _ in range(size)] # 鏈結法
def _hash(self, key):
return hash(key) % self.size
def put(self, key, value):
bucket = self.buckets[self._hash(key)]
for pair in bucket:
if pair[0] == key:
pair[1] = value # 已存在就更新
return
bucket.append([key, value])
def get(self, key):
for k, v in self.buckets[self._hash(key)]:
if k == key:
return v
return None
好的雜湊函式要 計算快、分布均勻。負載因子(load factor) = 資料筆數 ÷ 表格大小;負載因子太高時碰撞增加,通常會擴大表格並重新雜湊(rehash)。
各結構的效率比較
| 操作 | 未排序陣列 | 已排序陣列 | 平衡 BST | 雜湊表(平均) |
|---|---|---|---|---|
| 搜尋 | O(n) | O(log n) | O(log n) | O(1) |
| 插入 | O(1)(尾端) | O(n) | O(log n) | O(1) |
| 依序輸出 | 需先排序 | O(n) | O(n) | 需先排序 |
推薦影音
樹與二元搜尋樹(HackerRank)
簡述:說明二元搜尋樹的插入、搜尋與三種走訪方式,並以 Java 實作,對應本頁 BST 與走訪兩節。
雜湊表(HackerRank)
簡述:說明雜湊函式如何把鍵對應到索引、碰撞發生的原因,以及用鏈結法處理碰撞,對應本頁雜湊表一節。
雜湊演算法與安全(Computerphile)
簡述:從另一個角度介紹雜湊函式在資訊安全中的應用(密碼儲存、檔案完整性),可連結 A2.4 網路安全。
練習題
練習 1:建立 BST
依序插入 45, 20, 60, 10, 30, 55, 70, 25,畫出 BST,並寫出中序與前序走訪結果。
參考答案
樹:45 的左子為 20、右子為 60;20 的左子 10、右子 30;30 的左子 25;60 的左子 55、右子 70。
中序:10 20 25 30 45 55 60 70;前序:45 20 10 30 25 60 55 70。
練習 2:BST 高度
寫一個遞迴函式 height(node) 計算 BST 的高度(空樹高度為 −1,只有根為 0)。
練習 3:刪除節點
在練習 1 的樹中刪除 20,應用哪個值取代它?刪除後 20 的位置變成什麼?
參考答案
20 有兩個子節點,用中序後繼(右子樹 30 中最小的值)25 取代。刪除後該位置為 25,左子為 10、右子為 30,30 不再有左子節點。
練習 4:雜湊與碰撞
表格大小 7,雜湊函式為 key mod 7,使用線性探測。依序插入 15, 22, 8, 29,寫出每個鍵存放的索引。
參考答案
15 mod 7 = 1 → 索引 1。22 mod 7 = 1,碰撞 → 索引 2。8 mod 7 = 1,碰撞 → 2 被佔 → 索引 3。29 mod 7 = 1,碰撞 → 1、2、3 都被佔 → 索引 4。
練習 5:集合運算
A = {1, 2, 3, 4}、B = {3, 4, 5},寫出 A ∪ B、A ∩ B、A − B、B − A。
參考答案
A ∪ B = {1, 2, 3, 4, 5};A ∩ B = {3, 4};A − B = {1, 2};B − A = {5}。