# [資料結構] CH9.Red-Black, Splay and Huffman Trees
## 紅黑樹
* 紅黑樹也是Binary Search Trees的一種,同時也是屬於**平衡樹**的一種。
* 紅黑樹的性質(很重要):
```
1.所有node除了Data, left/right node之外,還會有一個"color"的屬性,這個color只會是紅色或是黑色。
2.root node 必定為黑色。
3.所有的leaf node也必定為黑色。
4.紅色節點的左右子樹必定為黑色。
5.不管從哪一個節點開始當起點,往下到leaf node的每個路徑中,經過的黑色節點數量是一樣的。
6.所有的leaf node都不會儲存Data。
```
* 一個紅黑樹的例子:
```graphviz
digraph RBT{
1[label="16",style=filled,fillcolor="gray30",fontcolor="white"]
2[label="9",style=filled,fillcolor="crimson",fontcolor="white"]
3[label="27",style=filled,fillcolor="crimson",fontcolor="white"]
4[label="7",style=filled,fillcolor="gray30",fontcolor="white"]
5[label="11",style=filled,fillcolor="gray30",fontcolor="white"]
6[label="21",style=filled,fillcolor="gray30",fontcolor="white"]
7[label="45",style=filled,fillcolor="gray30",fontcolor="white"]
8[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
9[label="11",style=filled,fillcolor="crimson",fontcolor="white"]
10[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
11[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
12[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
13[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
14[label="36",style=filled,fillcolor="crimson",fontcolor="white"]
15[label="63",style=filled,fillcolor="crimson",fontcolor="white"]
18[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
19[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
20[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
21[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
22[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
23[label="NULL",style=filled,fillcolor="gray30",fontcolor="white",shape="box"]
1->2
1->3
2->4
2->5
3->6
3->7
4->8
4->9
5->10
5->11
6->12
6->13
7->14
7->15
14->18
14->19
15->20
15->21
9->22
9->23
}
```
### 搜尋
* 由於同樣擁有Binary Search Trees的性質(左邊小右邊大),該部分和之前的一樣,故跳過。
### 插入
* 為了確保維持所有紅黑樹的特性,我們在插入時需要做**轉色**以及**旋轉**,以下是完整的步驟:
```
1.搜尋該資料應被放入哪個位置。
2.搜尋階段,如果遇到一個node兩個子樹都是紅色的:
a) 執行"換色"。
b) 換色完畢後,重新檢查該路徑中是否有出現"連續兩次的"紅色節點。
I) 如果有,執行"旋轉"。
3.插入該資料,並將它設定為紅色。
4.因為你加入了一個紅色節點,再次檢查該路徑中是否有出現"連續兩次的"紅色節點。
a) 如果有,執行"旋轉"。
5.將root node直接換成黑色(本來就是黑色就不用管)。
※Color Change → Rotation → Insert → Rotation → Check Root
```
* 看起來似乎有點複雜,實際找一個範例來試試看。
* 將下圖中的紅黑樹加入`35`。(子樹的NULL我不畫出來了)
```graphviz
digraph RBT{
1[label="27",style=filled,fillcolor="gray30",fontcolor="white"]
2[label="15",style=filled,fillcolor="gray30",fontcolor="white"]
3[label="45",style=filled,fillcolor="crimson",fontcolor="white"]
4[label="33",style=filled,fillcolor="gray30",fontcolor="white"]
5[label="57",style=filled,fillcolor="gray30",fontcolor="white"]
6[label="30",style=filled,fillcolor="crimson",fontcolor="white"]
7[label="40",style=filled,fillcolor="crimson",fontcolor="white"]
1->2
1->3
3->4
3->5
4->6
4->7
}
```
1.檢查應該放在哪裡:27 → 45 → 33,此時發現33的兩個子樹都是紅色的,進行換色。
2.換色:直接將33和其子樹的顏色轉換即可。
```graphviz
digraph RBT{
1[label="27",style=filled,fillcolor="gray30",fontcolor="white"]
2[label="15",style=filled,fillcolor="gray30",fontcolor="white"]
3[label="45",style=filled,fillcolor="crimson",fontcolor="white"]
4[label="33",style=filled,fillcolor="crimson",fontcolor="white"]
5[label="57",style=filled,fillcolor="gray30",fontcolor="white"]
6[label="30",style=filled,fillcolor="gray30",fontcolor="white"]
7[label="40",style=filled,fillcolor="gray30",fontcolor="white"]
1->2
1->3
3->4
3->5
4->6
4->7
}
```
3.因為換色了,檢查路徑上是否有連續的紅色;發現45和33都是紅色,根據其方向進行RL旋轉(詳情請看[AVL Tree](https://hackmd.io/s/rJksqh83X)的部分),旋轉後的中間值為黑色,兩個子樹為紅色。
```graphviz
digraph RBT{
subgraph cluster1
{
label="旋轉前";
1[label="27",style=filled,fillcolor="gray30",fontcolor="white"]
2[label="15",style=filled,fillcolor="gray30",fontcolor="white"]
3[label="45",style=filled,fillcolor="crimson",fontcolor="white"]
4[label="33",style=filled,fillcolor="crimson",fontcolor="white"]
5[label="57",style=filled,fillcolor="gray30",fontcolor="white"]
6[label="30",style=filled,fillcolor="gray30",fontcolor="white"]
7[label="40",style=filled,fillcolor="gray30",fontcolor="white"]
}
1->2
1->3[label="R"]
3->4[label="L"]
3->5
4->6
4->7
subgraph cluster2
{
label="旋轉後";
a1[label="33",style=filled,fillcolor="gray30",fontcolor="white"]
a2[label="27",style=filled,fillcolor="crimson",fontcolor="white"]
a3[label="45",style=filled,fillcolor="crimson",fontcolor="white"]
a4[label="15",style=filled,fillcolor="gray30",fontcolor="white"]
a5[label="30",style=filled,fillcolor="gray30",fontcolor="white"]
a6[label="40",style=filled,fillcolor="gray30",fontcolor="white"]
a7[label="57",style=filled,fillcolor="gray30",fontcolor="white"]
}
a1->a2
a1->a3
a2->a4
a2->a5
a3->a6
a3->a7
}
```
4.完成後將資料`35`放進來,設成紅色。
```graphviz
digraph RBT{
1[label="33",style=filled,fillcolor="gray30",fontcolor="white"]
2[label="27",style=filled,fillcolor="crimson",fontcolor="white"]
3[label="45",style=filled,fillcolor="crimson",fontcolor="white"]
4[label="15",style=filled,fillcolor="gray30",fontcolor="white"]
5[label="30",style=filled,fillcolor="gray30",fontcolor="white"]
6[label="40",style=filled,fillcolor="gray30",fontcolor="white"]
7[label="57",style=filled,fillcolor="gray30",fontcolor="white"]
8[label="35",style=filled,fillcolor="crimson",fontcolor="white"]
1->2
1->3
2->4
2->5
3->6
3->7
6->8
}
```
5.再次檢查有沒有連續的紅色。
6.將root改為黑色。
7.完成。
* 練習:試著依序將`1`, `2`, `3`, `4`, `5`和`6`放入一個空的Red-Black Tree.
* 最後的樣子應為下圖:
```graphviz
digraph RBT{
1[label="2",style=filled,fillcolor="gray30",fontcolor="white"]
2[label="1",style=filled,fillcolor="gray30",fontcolor="white"]
3[label="4",style=filled,fillcolor="crimson",fontcolor="white"]
4[label="3",style=filled,fillcolor="gray30",fontcolor="white"]
5[label="5",style=filled,fillcolor="gray30",fontcolor="white"]
6[label="6",style=filled,fillcolor="crimson",fontcolor="white"]
1->2
1->3
3->4
3->5
5->6
}
```
### 刪除
* 欸我發現我們沒有教欸 ㄏㄏ
* 有興趣的自己去看[wiki](https://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91#%E5%88%A0%E9%99%A4),感覺好麻煩。
### 與AVL比較
* 這些性質有什麼用?
* 為了維持這些特性,在插入、刪除資料時,我們也會進行一些旋轉,而這讓樹的高度不會差太多。
* 那為什麼我們不使用AVL Trees就好了?
* AVL對於高度的限制太嚴格,導致插入或刪除時吃掉太多資源。
* 那哪一個比較好?
* 根據需求有好有壞,沒有絕對。
| 執行動作 | AVL | Red-Black |
| -------- | -------- | -------- |
| 搜尋 | 勝 | |
|插入||勝|
|刪除||勝|
---
## Splay Tree
* Splay Tree的概念是,最近搜尋/插入的資料會越接近root。
* 至於為什麼要這樣做,應該蠻好理解的:你最近使用過的資料,短期內很有可能再次被用到,越靠近root搜尋所需的時間就越短。
* 將一筆資料移動root的動作稱為Splay,而Splay中我們會使用的操作又分為`Zig Step`和`Zig-zig Step`。
### Zig Step
* 當你使用的節點N和root node只差一階(也就是說,N是root node的子樹),我們就使用下圖來交換:
* 如果是在右子樹就反過來。
```graphviz
digraph ZigStep{
subgraph cluster1{
label="旋轉前"
1[label="P"]
2[label="N"]
3[label="T3",shape="triangle"]
4[label="T1",shape="triangle"]
5[label="T2",shape="triangle"]
}
1->2
1->3
2->1[style="dashed",color="orange"]
2->4
2->5
subgraph cluster2{
label="旋轉後"
a2[label="N"]
a3[label="T1",shape="triangle"]
a1[label="P"]
a4[label="T2",shape="triangle"]
a5[label="T3",shape="triangle"]
a2->a3
a2->a1
a1->a4
a1->a5
}
}
```
### Zig-zig Step
* 當你爸不是root,而且你爸和你的路徑呈現RR或是LL時,使用下圖交換:
```graphviz
digraph ZigzigStep{
subgraph cluster1{
label="旋轉前"
1[label="G"]
2[label="P"]
3[label="T4",shape="triangle"]
5[label="N"]
4[label="T3",shape="triangle"]
6[label="T1",shape="triangle"]
7[label="T2",shape="triangle"]
}
1->2[label="L"]
1->3
2->4
2->5[label="L"]
5->6
5->7
subgraph cluster2{
label="旋轉後"
a5[label="N"]
a6[label="T1",shape="triangle"]
a2[label="P"]
a7[label="T2",shape="triangle"]
a1[label="G"]
a4[label="T3",shape="triangle"]
a3[label="T4",shape="triangle"]
a5->a6
a5->a2
a2->a1
a2->a7
a1->a4
a1->a3
}
}
```
* 而情況是RL、LR時則是:
```graphviz
digraph ZigZigStep{
subgraph cluster1{
label="旋轉前"
1[label="G"]
2[label="P"]
3[label="T4",shape="triangle"]
4[label="T1",shape="triangle"]
5[label="N"]
6[label="T2",shape="triangle"]
7[label="T3",shape="triangle"]
}
1->2[label="L"]
1->3
2->4
2->5[label="R"]
5->6
5->7
subgraph cluster2{
label="旋轉後"
a5[label="N"]
a2[label="P"]
a1[label="G"]
a6[label="T1",shape="triangle"]
a7[label="T2",shape="triangle"]
a4[label="T3",shape="triangle"]
a3[label="T4",shape="triangle"]
a5->a2
a5->a1
a2->a6
a2->a7
a1->a4
a1->a3
}
}
```
* 當你要將N上提至root時,只要不停的重複這兩個步驟即可。
* 請和上方的紅黑樹旋轉比較(或是AVL旋轉),此部分的目的是將Node N上提,因此不是將中間值上提,切勿搞混。
### 插入
* 用一般Binary Search Tree的方法插入`N`。
* Splay `N`,也就是將`N`上提至root。
### 搜尋
* 用一般Binary Search Tree的方法搜尋`N`。
* 如果成功找到`N`,Splay`N`。
* 如果找不到該數字,Splay你最後一個遍歷到的node。
* 概念上來說就是把最接近的資料上提。
### 刪除
* ㄏㄏ我們還是沒教。
---
## Huffman Trees
* 霍夫曼樹用於壓縮字串。
* 與紅黑樹相反,霍夫曼樹的資料只會放在葉子的地方,其他節點則是用於計算和連接。
### Weighted External Path Length
* 霍夫曼樹除了高度之外,有另一種計算路徑長度的方法,我們把這個值稱作**權重**:
`將所有(葉子的值)*(該葉子到root的距離)給加總起來,即為Weighted External Path Length。`
* 舉個例子,下面的樹的長度為`77`。
* $2*3+3*3+5*2+11*3+5*3+2*2=77$
```graphviz
digraph Huffman{
1[label=""]
2[label=""]
3[label=""]
4[label=""]
5[label="5",shape="box"]
6[label=""]
7[label="2",shape="box"]
8[label="2",shape="box"]
9[label="3",shape="box"]
10[label="11",shape="box"]
11[label="5",shape="box"]
1->2[label="1"]
1->3[label="1"]
2->4[label="2"]
2->5[label="2"]
3->6[label="2"]
3->7[label="2"]
4->8[label="3"]
4->9[label="3"]
6->10[label="3"]
6->11[label="3"]
}
```
### Create Huffman Tree
* 霍夫曼樹不是搜索樹,因此不需要小的在左子樹大的在右子樹。它的用途不是搜尋而是壓縮。
* 霍夫曼樹的特性是它可以根據資料,找出Weighted External Path Length**最小**的結構。
* 只要根據字元出現的頻率給予node的number,就可以將字串壓縮成最小編碼。
* 以下是建霍夫曼樹的步驟與例子:
* 假設現在要將字串編碼,該字串該字串只會出現A~J,且出現的頻率A<B<...<J。
```
1.所有的data都先建成一個獨立的node,並依照頻率排序。
```
```graphviz
digraph Huffman
{
node[shape=record]
store1 [label="<f0> A 7|<f1> B 9|<f2>C 11|<f3>D 14|<f4>E 18|<f5> F 21|<f6>G 27|<f7>H 29|<f8>I 35 |<f9> J40"];
store1:f0 -> store1:f9[label="頻率低 頻率高"]
}
```
```
2.新建一個node,將兩個權重最小的root node當作左子樹和右子樹,並將該node的權重設為左右子樹的總和。
```
```graphviz
digraph Huffman
{
node[shape=record]
A[label="A 7",shape=""]
B[label="B 9",shape=""]
store1 [label="<f0> 16|<f2>C 11|<f3>D 14|<f4>E 18|<f5> F 21|<f6>G 27|<f7>H 29|<f8>I 35 |<f9> J40"];
store1:f0 -> store1:f9[label="頻率低 頻率高"]
store1:f0 -> A
store1:f0 -> B
}
```
```
3.不停重複步驟2,直到只剩下一個root node。
```
```graphviz
digraph Huffman
{
node[shape=record]
A[label="A 7",shape=""]
B[label="B 9",shape=""]
C[label="C 11",shape=""]
D[label="D 14",shape=""]
store1 [label="<f0> 16|<f2> 25|<f4>E 18|<f5> F 21|<f6>G 27|<f7>H 29|<f8>I 35 |<f9> J40"];
store1:f0 -> store1:f9[label="頻率低 頻率高"]
store1:f0 -> A
store1:f0 -> B
store1:f2 -> C
store1:f2 -> D
}
```
* 跳過中間步驟,最後的樹為:
```graphviz
digraph Huffman
{
node[shape=record]
221
125
86
56
69
G[label="G 27",shape=""]
H[label="H 29",shape=""]
34
I[label="I 35",shape=""]
16
E[label="E 18",shape=""]
A[label="A 7",shape=""]
B[label="B 9",shape=""]
46
J[label="J 40",shape=""]
25
F[label="F 21",shape=""]
C[label="C 11",shape=""]
D[label="D 14",shape=""]
221->125
221->86
125->56
125->69
56->G
56->H
69->34
69->I
34->16
34->E
16->A
16->B
86->46
86->J
46->25
46->F
25->C
25->D
}
```
* 如此一來,我們便完成了一棵霍夫曼樹。
### Data Coding
* 根據出現頻率完成了樹之後,我們到底如何運用這棵樹編碼呢?
* 我們將剛剛的樹往左邊的路寫上`0`,右邊則是`1`。
```graphviz
digraph Huffman
{
node[shape=record]
221
125
86
56
69
G[label="G 27",shape=""]
H[label="H 29",shape=""]
34
I[label="I 35",shape=""]
16
E[label="E 18",shape=""]
A[label="A 7",shape=""]
B[label="B 9",shape=""]
46
J[label="J 40",shape=""]
25
F[label="F 21",shape=""]
C[label="C 11",shape=""]
D[label="D 14",shape=""]
221->125[label="0"]
221->86[label="1"]
125->56[label="0"]
125->69[label="1"]
56->G[label="0"]
56->H[label="1"]
69->34[label="0"]
69->I[label="1"]
34->16[label="0"]
34->E[label="1"]
16->A[label="0"]
16->B[label="1"]
86->46[label="0"]
86->J[label="1"]
46->25[label="0"]
46->F[label="1"]
25->C[label="0"]
25->D[label="1"]
}
```
* 根據從root到每個data的路徑,我們可以得到這個表:
| 字元/資料 | 對應編碼 |
| -------- | -------- |
| A | 01000 |
|B|01001|
|C|1000|
|D|1001|
|E|0101|
|F|101|
|G|000|
|H|001|
|I|011|
|J|11|
* 然後我們就可以利用這個表編碼,例如"AFG"=01000101000。
* 你可以發現,頻率越高的字,編碼越短,因此霍夫曼樹可以提升壓縮字串時的壓縮率。
###### tags: `DS` `note`