[toc]
# 樹與二元樹
## Tree
### Tree 定義
由 $\ge 1$ 個 nodes 所形成之集合,不可以為空,並且滿足
- 至少有一個特殊 node 稱為 root
- 其餘 node 分為 $T_1、T_2、T_3 \ ...\ T_m$ 之互斥集合,即為 root 之子樹
### Tree 表示方式
- **用 linked list**
| data | link 1 | link 2 | link 3 | ... | link k |
|--|--|--|--|--|--|
:::danger
極度浪費 linking space
共 $n*k$ 個 link ,有用的只有 $n-1$ 個
:::
- **Leftmost Child Next Right Sibling**
- 左子右兄弟 (Tree 化為 Binary Tree)
- | data | child | sibling |
|--|--|--|
- **括號法**
以 父(子子子子子...) 的方法,可以 nested
### Tree 其他考題
- node level 值
- root level 為 1
- forest 是由 $\ge 0$ 棵互斥的 trees 所形成的集合 (forest 可以為空)
## Binary Tree
### Binary Tree 定義
可以為空,若不為空,則滿足
- 有 root 及 左右子樹
- 左右子樹也是 Binary Tree
### Binary Tree 種類
- Skewed Binary Tree
- Left Skewed Binary Tree
- Right Skewed Binary Tree
- Full Binary Tree
- 具有最多 node 數
- Complete Binary Tree
- 高度 k,總 node 數為 n,滿足 $2^{k-1}-1\lt n \le 2^{k}-1$
- node 編號要由上而下,由左而右依序增長
- 一個 Complete Binary Tree 具有 n 個 nodes,若某 node 編號為 i
- 其左子點編號為 $2i$
- 其右子點編號為 $2i+1$
- 其父點編號為 $[\frac{i}{2}]$ (若 $[\frac{i}{2}] \lt 1$ 則無父點)
- Strict Binary Tree
- Binary Tree 中,任何 non leaf 皆有 2 個子點,即 $n_1 = 0$
### Binary Tree 實作
- 利用 array
- 作法 : 若 Binary Tree 高度為 k,則準備一個一維陣列 $A = array[1 ... 2^{k}-1]$,將各個 node 仿照 Full Binary Tree node 的編號,將 node data 填入 A 對應的表格中
- 優點 :
- 易於存取左右子點及父點
- 對於 Full Binary Tree 或 Complete Binary Tree 而言,無儲存空間浪費
- 缺點 :
- node 之插入、刪除不便
- 對於 Skewed Binary Tree 的儲存極為浪費空間(浪費 $2^k-1-k$ 格)
- 利用 link list 表示
- 作法 :
| Lchild | Data | Rchild |
|---|---|---|
- 優點 :
- node 之插入、刪除較為方便
- 對於 Skewed Binary Tree 儲存較 array 省空間
- 缺點 :
- 不易存取父點
- link 之空間浪費約 50% ($2n-(n-1)$)
### Binary Tree 追蹤
- Preorder
```cpp=
Preorder(BinaryTree T) {
if (T != NULL) {
print(T -> data);
Preorder(T -> leftChild);
Preorder(T -> rightChild);
}
}
```
- Inorder
```cpp=
Inorder (BinaryTree T) {
if (T != NULL) {
Inorder(T -> leftChild);
print(T -> data);
Inorder(T -> rightChild);
}
}
```
### Binary Tree 複製
```cpp=
CopyTree(node origin) {
if (origin == NULL)
return NULL;
else {
t = new_node();
t -> data = origin -> data;
t -> leftChild = CopyTree(origin -> leftChild);
t -> rightChild = CopyTree(origin -> rightChild);
}
}
```
### Binary Tree 比較
```cpp=
bool answer = false;
Equal(BinaryTree S, BinaryTree T) {
if (S == NULL && T == NULL)
return true;
else if (S != NULL && T != NULL) {
if (S -> data == T -> data) {
if (Equal(S -> leftChild, T -> leftChild))
answer = Equal(S -> rightChild, T -> rightChild);
}
}
return answer;
}
```
### Binary Tree 數 node
```cpp=
CountNodes(BinaryTree T) {
if (T == NULL)
return 0;
else {
int n1 = CountNodes(T -> leftChild);
int n2 = CountNodes(T -> rightChild);
return n1+n2+1;
}
}
```
### Binary Tree 樹高
```cpp=
TreeHeight(BinaryTree T) {
if (T == NULL)
return 0;
else {
int n1 = TreeHeight(T -> leftChild);
int n2 = TreeHeight(T -> rightChild);
return max(n1, n2)+1;
}
}
```
### Binary Tree 葉子數
```cpp=
TreeLeaves(BinaryTree T) {
if (T == NULL)
return 0;
else {
int n1 = TreeLeaves(T -> leftChild);
int n2 = TreeLeaves(T -> rightChild);
if (n1+n2 > 0)
return n1+n2;
else
return 1;
}
}
```
### Binary Tree 交換
```cpp=
SwapTree(BinaryTree T) {
if (T != NULL) {
SwapTree(T -> leftChild);
SwapTree(T -> rightChild);
temp = T -> leftChild;
T -> leftChild = T -> rightChild;
T -> rightChild = temp;
}
}
```
### Binary Tree 算算式
```cpp=
struct Node{
leftChild;
rightChild;
data;
answer;
}
ExprBT(BinaryTree T) {
if (T != NULL) {
ExprBT(T -> leftChild);
ExprBT(T -> rightChild);
switch(data) {
case "+" :
T -> answer = (T -> leftchild) -> answer + (T -> rightChild) -> answer;
break;
case "-" :
T -> answer = (T -> leftchild) -> answer - (T -> rightChild) -> answer;
break;
case "*" :
T -> answer = (T -> leftchild) -> answer * (T -> rightChild) -> answer;
break;
case "/" :
T -> answer = (T -> leftchild) -> answer / (T -> rightChild) -> answer;
break;
case "^" :
T -> answer = (T -> leftchild) -> answer ^ (T -> rightChild) -> answer;
break;
case "operand" :
T -> answer = int(operand);
}
}
}
```
### Binary Tree 排序
- Heap Sort
- Search Tree
- Binary Search Tree
- m-way Search Tree
- AVL Tree
- B Tree
- B+ Tree
### Binary Tree 和其他資料結構轉換
- Binary Tree 化為 Tree
步驟為 :
1. 逆時針轉 45 度,即右子點均向上拉成為父點之次右兄弟
2. 補足父、子之 links
3. 刪除 sibling 間的 link
- Tree 化為 Binary Tree
步驟為 :
1. 建立 sibling 之間的 links
2. 除了 Leftmost Child 的 link,刪掉其他父與子的 links
3. 順時針轉 45 度,即保留的 Leftmost Child 當左子點, Next Right Sibling 當右子點
- Forest 化為 Binary Tree
步驟為 :
1. 將 Forest 中每棵 Tree 化為 Binary Tree
2. 將各個 Binary Tree 之 root 們視為 siling,建立平行 links
3. 將這些 roots 順時針旋轉 45 度,其餘不變
- Binary Tree 化為 Forest
步驟為 :
1. 將 Binary Tree root 之右子點 link 上所有 node 均向上拉形成 root 之 sibing
2. 刪除 roots 間的 links,得到多棵 Binary Tree
3. 每個 Binary Tree 再化為 Tree
### Binary Tree 其他考題
- Binary Tree 與 Tree 之比較
| Tree | Binary Tree |
| -------- | -------- |
| 不可以為空 | 可以為空 |
| node degree $\ge 0$ 即可 | $2 \ge$ node degree $\ge 0$ |
| 子樹之間無次序或左右之分 | 子樹之間有左右之分 |
- Binary Tree 三個基本定理
- 二元樹中第 i level 最多 node 數為 $2^{i-1}$
- 高度為 k 之 tree 的 node 數最多為 $2^k-1$
- 給予一個非空的 Binary Tree,若 leaf 個數有 $n_0$ 個,degree-2 的 node 數為 $n_2$ 個,則滿足 $n_0 = n_2 + 1$
- n 個 nodes 可以形成的不同 Binary Tree 個數為 $\frac{1}{n+1}\binom{2n}{n}$
## Binary Search Tree (BST)
### Binary Search Tree 定義
是一個 Binary Tree,若不為空,則滿足
- 左子樹所有 node 均小於 root
- 右子樹所有 node 均大於 root
- 左右子樹亦是 Binary Search Tree
### Binary Search Tree 排序
步驟為
1. 將 Input Data 建成 Binary Search Tree
2. 對 Binary Search Tree 進行 Inorder 追蹤,得出由小到大的排序
### Binary Search Tree 建立
受到 Input Data 的順序影響,若 Input Data 的順序為
- 小到大 $\rightarrow$ Right Skewed Binary Search Tree
- 大到小 $\rightarrow$ Left Skewed Binary Search Tree
### Binary Search Tree 中刪除某元素 x
步驟為
1. 先 search for x
2. 分成下列 cases
- case 1 : x 是 leaf,則刪 x
- case 2 : x 是 degree-1 的 node,則將 x 的父點原先指向 x 的 pointer 指向 x 的子點,再刪 x
- case 3 : x 是 degree-2 的 node,則以 x 左子樹的最大值或 x 右子樹的最小值取代 x,再回到 case 1 或 case 2
> 平均時間複雜度為 $T(n) = O(logn)$
> 但若為 Skewed Binary Search Tree,則時間複雜度為 $T(n) = O(n)$
### Binary Search Tree 中搜尋某元素 x
```cpp=
Search(BinaryTree T, int x) {
if (T != NULL) {
switch(CompareValue(T -> data, x)) {
case "=" :
return true;
case "<" :
return Search(T -> rightChild, x);
case ">" :
return Search(T -> leftChild, x);
}
}else
return false;
}
```
> 若為 AVL Tree、Red Black Tree、Full Binary Tree 或 Complete Binary Tree 等高度最小化的樹時,則時間複雜度為 $T(n) = O(logn)$
> 但若為 Skewed Binary Search Tree,則時間複雜度為 $T(n) = O(n)$
### Binary Search Tree 找出第 i 小的元素
| Lsize | Lchild | Data | Rchild |
|--|--|--|--|
Lsize 主要功能為記 node 左子樹的 node 個數
```cpp=
struct node {
leftChild;
lsize;
data;
rightChild;
}
SearchiSmall(BinaryTree T, int i) {
if (T != NULL) {
int k = (T -> lsize) + 1;
if (i == k)
return T -> data;
else if (i < k)
return SearchiSmall(T, i);
else
return SearchiSmall(T, i-k);
}
}
```
## Thread Binary Tree
### Thread Binary Tree 定義
Binary Tree 具有 n 個 nodes,以 link list 表示,會有 n+1 條 null links,為了充分利用這些 null links,所以會將這些 null links 視為 Thread Pointer,改成指向其他 node
一般而言,是以 Inorder 順序為規範,Thread Pointer 如下 :
- 若 x $\rightarrow$ Lchild 為 null,則此 pointer 當作左引線,改指向 Inorder 順序中 x 的前一個 node
- 若 x $\rightarrow$ Rchild 為 null,則此 pointer 當作右引線,改指向 Inorder 順序中 x 的後一個 node
### Thread Binary Tree 實作
| Left Thread | Lchild | Data | Rchild | Right Thread |
| -------- | -------- | -------- |--|--|
Left Thread :
- true : 表 Lchild 為 Left Thread
- false : 表 Lchild 為 Lchild
Right Thread :
- true : 表 Rchild 為 Right Thread
- false : 表 Rchild 為 Rchild
會額外加一個 Head node 不存在 Data,Head node 規定如下 :
- case 1 : 空樹
| True | Lchild | Data | Rchild | True |
|--|--|--|--|--|
- case 2 : 非空樹
| False | Lchild | Data | Rchild | False |
|--|--|--|--|--|
### Thread Binary Tree 中找 Inorder 順序裡 x 的下一個元素
步驟為 :
1. 若 x 的 Right Thread 為 true,則 x 的 Rchild 即是
2. 若 x 的 Right Thread 為 flase,則沿著 x 的右子點往左下尋找,直到某個 node 的 Left Thread 是 true 為止,則為該 node
```cpp=
Insuc(node x) {
temp = x -> Rchild;
if (! x -> RightThread) {
while(! temp -> LeftThread)
temp = temp -> Lchild;
}
return temp -> data;
}
```
### Thread Binary Tree 中找 Inorder 順序裡 x 的上一個元素
步驟為 :
1. 若 x 的 Left Thread 為 true,則 x 的 Lchild 即是
2. 若 x 的 Left Thread 為 flase,則沿著 x 的左子點往右下尋找,直到某個 node 的 Right Thread 是 true 為止,則為該 node
```cpp=
Inpre(node x) {
temp = x -> Lchild;
if (! x -> LeftThread) {
while(! temp -> RightThread)
temp = temp -> Rchild;
}
return temp -> data;
}
```
### Thread Binary Tree 簡化 Inorder 追蹤
從 Head node 開始,不斷地問 x 的下一個元素,直到又回到 Head 為止
```cpp=
Inorder(node n) {
temp = n;
repeat :
temp = Insuc(temp);
if (temp != n)
print(temp -> data);
until (temp == n);
}
```
### Thread Binary Tree 中在 s 的右子點插入元素 x
分為兩個 case
- case 1 : 若 s 原本無右子點
步驟為 :
1. 將元素 x 的 Right Thread 指向 s 的 Right Thread (Right Thread 為 true)
2. 將元素 x 的 Left Child 指向 s (Left Thread 為 false)
3. 將 s 的 Right Child 指向 x
- case 2 : 若 s 原本有右子點
步驟為 :
1. 將元素 x 的 Right Child 指向 s 的Right Child (Right Thread 為 false)
2. 將元素 x 的 Left Thread 指向 s (Left Thread 為 true)
3. 將 s 的 Right Child 指向 x
4. 將 s 的右子樹中最左邊 node 的 Left Thread 指向元素 x
```cpp=
Insert(s, t : nodes of thread binary tree) {
t -> RightThread = s -> RightThread;
t -> Rchild = s -> Rchild;
s -> RightThread = false;
s -> Rchild = t;
t -> LeftThread = true;
t -> Lchild = s;
if (t -> RightThread) {
temp = Insuc(t);
temp -> Lchild = t;
}
}
```
## Heap
### Heap 定義
可分為 Max-Heap 及 Min-Heap
- Max-Heap :
- 是一個 Complete Binary Tree
- 所有父點均 $\ge$ 其子點
- root 具有最大值
- Min-Heap :
- 是一個 Complete Binary Tree
- 所有父點均 $\le$ 其子點
- root 具有最小值
### Heap 建立
方法有兩種
- Top-Down 法 (慢)
- 作法 : 依序執行插入某元素 x 的動作,逐步建立 Heap,而在每一中間過程之後,均維持是 Heap 的性質
- > $T(n) = O(nlogn)$
- Bottom-Up 法 (快)
- 作法 :
- 將 Input Data 以 Complete Binary Tree 方式呈現
- 從 the last parent 開始,往回各個父點依序調整每棵子樹成為 Heap,直到調到 root 為止
- code
```cpp=
CreateHeap(Tree T, int n) {
for i = n/2 to 1
Adjust(T, i, n);
}
Adjust(Tree T, int i, int n) {
k = T[i];
j = 2*i;
while(j <= n) {
if (j < n) {
if (T[j+1] > T[j])
j ++;
}
if (T[j] <= k)
break;
else {
T[j/2] = T[j];
j *= 2;
}
}
T[j/2] = k;
}
```
- > $T(n) = O(n)$
### Heap 中插入某元素 x (Max-Heap 為例)
步驟為 :
1. x 先暫置於 the last node 的下一個位置
2. x 往上挑戰父點,直到無父點或挑戰失敗為止
> $T(n) = O(logn)$
### Heap 中刪除某元素 x (Max-Heap 為例)
步驟為 :
1. 移走 root 的 Data
2. 將 the last node x 刪掉,並將 x 暫置於 root 位置
3. x 自 root 向下調整,直到符合 Heap 為止
> $T(n) = O(logn)$
### Heap 中刪除最大元素 (Max-Heap 為例)
```cpp=
DeleteHeap(Tree T, int n) {
item = T[1];
T[1] = T[n];
n --;
Adjust(T, 1, n);
return item;
}
Adjust(Tree T, int i, int n) {
k = T[i];
j = 2*i;
while(j <= n) {
if (j < n) {
if (T[j+1] > T[j])
j ++;
}
if (T[j] <= k)
break;
else {
T[j/2] = T[j];
j *= 2;
}
}
T[j/2] = k;
}
```
> $T(n) = O(lgn)$
### Heap 應用
- Heap Sort
- Priority Queue 最適合的 Data Structure
- | Operation | Time |
| -------- | -------- |
| Insert x | $O(logn)$ |
| Delete max (或 min) | $O(logn)$ |
| Search max (或 min) | $O(1)$ |
| Create Heap | $O(n)$ |
| Merge two Heap into a Heap ($O(2n)$) | $O(n)$ |
| Increase (Decrease) key of node | $O(logn)$ |
| Search for x | $O(n)$ |
| Delete x | $O(logn)$ |
### Heap 其他考題
- Heap 適合用 array 儲存
## Disjoint Set
### Disjoint Set 定義
一組互斥之集合組成
### Disjoint Set 表示方式
每個 set 利用 Tree 結構表示,即從 set 中任取一做為 Tree 的 root,其餘為他的子點
- 用 link list
- | Data | Parent |
|--|--|
- 用 array
- | Index | Data | Parent |
| -------- | -------- | -------- |
| 1 | 1 | 0 |
| 2 | 2 | 1 |
| 3 | 3 | 1 |
| 4 | 4 | 5 |
| 5 | 5 | 0 |
| 6 | 6 | 5 |
| 7 | 7 | 0 |
| 8 | 8 | 7 |
| 9 | 9 | 5 |
| 10 | 10 | 7 |
### Disjoint Set 應用
- Kruskal's Algorithm 中判斷邊 (u, v) 加入 Spanning Tree 中是否形成 cycle
- 根據等價配對資訊,求出等價集合
- 求 Directed Graph 中的 Connected Component 之方法之一
### Disjoint Set 尋找運作之組合討論
兩個基本操作 :
union(i, j) $\rightarrow$ 聯集 $set_i$ 與 $set_j$
find(x) $\rightarrow$ 找出 x 元素所在的 set 的 root,傳回那個 root
- [組合一] 任意的 union(i, j) 及 simpleFind(x)
```cpp=
//任意的 union(i, j)
union(i, j) {
j -> parent = i;
}
```
```cpp=
// simpleFind(x)
simpleFind(x) {
temp = x;
while(temp -> parent != temp) {
temp = temp -> parent;
}
return temp;
}
```
> $Union : T(n) = O(1)$
> $Find : T(n) = O(n)$
- [組合二] union_by_height(i, j) 及 simpleFind(x)
樹高較高者作為新樹的 root,另一個 set 作為其子樹
若高度相同,則做任意 union
```cpp=
union_by_height(i, j) {
if (Height(i) >= Height(j)) {
if (Height(i) == Height(j))
i ++:
j -> parent = i;
}else
i -> parent = j;
}
```
```cpp=
// simpleFind(x)
simpleFind(x) {
temp = x;
while(temp -> parent != temp) {
temp = temp -> parent;
}
return temp;
}
```
> $Union\_By\_Height : T(n) = O(1)$
> $Find : T(n) = O(lgn)$
- [組合三] union_by_height(i, j) 及 Find_with_path_compression(x)
除了找出 x 所在 set 之 root 外,另外會將 x 的 parent links 上所有 nodes (包含 x,但不包含 root) 的 parent 指標均改成指向 root (即做了路徑壓縮)
```cpp=
union_by_height(i, j) {
if (Height(i) >= Height(j)) {
if (Height(i) == Height(j))
i ++:
j -> parent = i;
}else
i -> parent = j;
}
```
```cpp=
Find_with_path_compression(x) {
if (x != x -> parent)
x -> parent = Find_with_path_compression(x -> parent);
return x -> parent;
}
```
> $Union\_By\_Height : T(n) = O(1)$
> $Find\_with\_path\_compression : T(n) = O(\alpha(m,n))趨近於O(1)$