二元搜尋樹 (Binary Search Tree)
二元搜尋樹 (Binary Search Tree)的重點整理。
定義
-
是一棵二元樹,可以為空。
-
若不為空,必須滿足:
-
左子樹所有節點的值均小於根節點的值。
-
右子樹所有節點的值均大於根節點的值。
-
左、右子樹本身也都是二元搜尋樹。
-
特性
-
Inorder Traversal (中序走訪):順序為 Left → Data → Right (L D R)。
-
排序應用:
-
將輸入資料逐一插入,建構成 BST。
-
對 BST 進行 Inorder Traversal,可獲得升冪排序結果。
-
-
搜尋與重複值處理:
- BST 不允許重複的 key 值,因為它是設計來進行「搜尋」的。
- 插入一筆資料
x時,總是會先進行搜尋:- 如果已經找到
x,就不需要再插入。 - 如果找不到,才會插入
x。
- 如果已經找到
- ➤ 這確保了 BST 的 key 值都是唯一的。
-
不同的插入策略:
-
方法一(常見):
- 若
x <= 節點值→ 插入左子樹 - 若
x > 節點值→ 插入右子樹
- 若
-
方法二(記錄方式不同):
- 每個節點用
key表示左子樹的值,用counter表示右子樹中該 key 的出現次數或統計資料(可用於統計或自訂搜尋邏輯)
- 每個節點用
-
證明 Inorder Traversal 有序性
假設對任意節點 \(v\):
-
左子樹所有值小於 \(v\),右子樹所有值大於 \(v\)。
-
對左子樹 Inorder Traversal 得到有序序列 \(L\),且所有 \(L\) 中元素都小於 \(v\)。
-
訪問 \(v\)。
-
對右子樹 Inorder Traversal 得到有序序列 \(R\),且所有 \(R\) 中元素都大於 \(v\)。
合併後序列為: \[L ∥ [v] ∥ R\]
因此整體輸出序列有序。
時間複雜度
令節點數為 n,BST 高度為 hh。
-
單次搜尋 / 插入 / 刪除:
-
最壞:\(O(h)=O(n)\)(BST 退化時)
-
平均:\(O(\log n)\)(AVL、RB Tree 等)
-
-
建構 (插入 n 個元素):
-
普通 BST 最壞:\(O(n^2)\)(若每次插入均退化為鏈尾)
-
平衡 BST:\(O(n\log n)\)
-
-
Inorder Traversal:\(O(n)\)
在 BST 中搜尋 X (Search X)
-
時間複雜度:\(O(h)\),其中 \(h\) 為 BST 高度。
-
最壞情況:\(O(n)\)。
-
最佳情況:\(O(1)\)(命中根節點)。
-
平均 / 平衡:\(O(\log n)\)。
插入與刪除操作時間複雜度同搜尋。
int search(struct node *root, int x)
{
if (root == NULL)
return 0; // 未找到
else if (root->data == x)
return 1; // 找到
else if (x > root->data)
return search(root->right_child, x);
else
return search(root->left_child, x);
}
搜尋時間複雜度推導
這段推導其實是在算一棵理想(滿/完全)二元樹上,搜尋所有節點一次所需要的比較次數總和 \(S\),然後再除以節點數 \(n\) 得到「平均比較次數」。具體思路是:
- 分層計算
一棵高為 \(k\) 的滿二元樹,層號從 1 到 \(k\):- 第 \(j\) 層有 \(2^{j-1}\) 個節點,
- 搜到第 \(j\) 層的比較次數就是 \(j\)。
因此把所有層加起來:
- 乘以 2 再相減(消項技巧)
為了化簡這個帶 \(2^{j-1}\) 的級數,我們對整式乘以 2:
接著用 \(2S - S\),中間大部分項會互相抵消,只剩下尾巴和開頭累加:
因此:
- 除以節點總數
滿二元樹的總節點數為 \(n = 2^k - 1\),所以平均比較次數:
找最小值 / 最大值 (Find-min / Find-max)
-
時間複雜度:
-
最佳:\(O(logn)\)
-
最壞:\(O(n)\)
-
struct node* find_minimum(struct node *root)
{
if (root == NULL)
return NULL;
else if (root->left_child == NULL)
return root;
return find_minimum(root->left_child);
}
struct node* find_maximum(struct node *root)
{
if (root == NULL)
return NULL;
else if (root->right_child == NULL)
return root;
return find_maximum(root->right_child);
}
插入 X (Insert X)
-
若 X 已存在,則不插入。
-
策略:
-
若 \(x \le root\)->data → 插入右子樹。
-
若 \(x \gt root\)->data → 插入左子樹。
-
struct node* new_node(int x) {
struct node *p;
p = malloc(sizeof(struct node));
p->data = x;
p->left_child = NULL;
p->right_child = NULL;
return p;
}
struct node* insert(struct node *root, int x) {
if (root == NULL)
return new_node(x);
else if (x > root->data) // 往右子樹插入
root->right_child = insert(root->right_child, x);
else // 往左子樹插入
root->left_child = insert(root->left_child, x);
return root;
}
刪除節點 (Delete X in a BST)
-
Degree 0(葉節點 leaf)
- 條件:無左子也無右子
- 刪除方式:直接刪除該節點
-
Degree 1
- 條件:僅有一個子節點(左或右其中之一為 NULL)
- 刪除方式:用唯一的子節點取代自己,保留原本在父節點的位置(如果原本在右邊,就把子節點接到右邊)
-
Degree 2
- 條件:同時擁有左子與右子節點
- 刪除方式:
- 從右子樹中找最小值(或左子樹找最大值)
- 用該值取代當前節點
- 再去刪除那個被取代的值(此時會是 degree 0 或 degree 1)
50 / \ 30 70 / \ / \ 20 40 60 80 刪除節點 50: 60 / \ 30 70 / \ \ 20 40 80
struct node* delete(struct node *root, int x) {
if (root == NULL)
return NULL;
if (x > root->data) {
root->right_child = delete(root->right_child, x);
}
else if (x < root->data) {
root->left_child = delete(root->left_child, x);
}
else {
// 找到欲刪除的節點
if (root->left_child == NULL && root->right_child == NULL) {
free(root);
return NULL;
}
// x degree-1
else if (root->left_child == NULL || root->right_child == NULL) {
struct node *temp;
if (root->left_child == NULL)
temp = root->right_child;
else
temp = root->left_child;
free(root);
return temp;
}
// x degree-2
else {
struct node *temp = find_minimum(root->right_child);
root->data = temp->data;
root->right_child = delete(root->right_child, temp->data);
}
}
return root;
}