# 2023q1 Homework4 (quiz4)
contributed by < `LiChiiiii` >
>測驗題目:[quiz4](https://hackmd.io/@sysprog/linux2023-quiz4)
## 測驗一
在測驗一提供的程式碼中定義
```c
typedef rb_tree(node_t) tree_t;
rb_gen(static, tree_, tree_t, node_t, link, node_cmp);
```
對照 [rb.h](https://gist.github.com/jserv/6610eb56bf2486979c8bf6ee8061f71c) 之巨集的使用,展開 `rb_gen(static, ex_, ex_t, ex_node_t, ex_link, ex_cmp)` 巨集可以得到插入、刪除等功能之函式。
#### 推敲其設計思維
##### `tree_insert()`
將巨集 `x_attr void x_prefix##insert(x_rbt_type *rbtree, x_type *node)` 展開為 `static void tree_insert (tree_t *rbtree, node_t *node)` ,此函式將新節點插入紅黑樹中,主要有三個步驟:
1. 根據節點的比較結果遞迴查找插入位置
2. 依次從下往上檢查是否需要旋轉
3. 將根節點設為黑色。
舉個例子來說,現在有一個紅黑樹。
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.5]
2[fillcolor=red]
A,B,C,D,E,F[fontsize=0,color=transparent]
7 -> {2 11}
2 -> {1 5}
1 -> {A B}
5 -> {C D}
11 -> {E F}
}
```
我們想插入新的節點 `3`,會先用 BST 的概念尋找插入的位置。
1. `3` 與 `7` 比較,紀錄 cmp = -1 ,並往左進行下一層尋找。
2. `3` 與 `2` 比較,紀錄 cmp = 1 ,並往右進行下一層尋找。
3. `3` 與 `5` 比較,紀錄 cmp = -1 ,並往左進行下一層尋找,得到 NULL 跳出迴圈,把插入位置指向此節點,也就是 `pathp->node = node` 。
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.5]
2,3[fillcolor=red]
A,B,C,D,E,F[fontsize=0,color=transparent]
7 -> {2 11}
2 -> {1 5}
1 -> {A B}
5 -> {3 D}
11 -> {E F}
}
```
由於在尋找插入的位置的過程中,有依序紀錄經過每個節點及其 cmp 至名為 path 的陣列,因此當找到插入位置的同時,也得到了以下陣列 path 。
```graphviz
digraph path{
// node [shape=plaintext, fontcolor=black, fontsize=18];
// "Pointers:" -> "Values:" [color=white];
node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true];
pointers [label="<f0> | <f1> | <f2>pathp |<f3> pathp[1]", color=white];
values [label="<f0> 7, -1 | <f1> 2, 1 | <f2> 5, -1 | <f3> 3", fillcolor=pink, style=filled];
// { rank=same; "Pointers:"; pointers }
// { rank=same; "Values:"; values }
edge [color=black];
pointers:f2 -> values:f2;
pointers:f3 -> values:f3;
}
```
接下來將 pathp 反向回去,判斷每個 pathp 的 cmp 值來檢查是否需要旋轉。
如果 cmp < 0 ,且出現左邊狀況則進行旋轉成右邊的樣子。
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.7]
left, leftleft [fillcolor=red]
A,B[fontsize=0,color=transparent]
cnode -> left
cnode -> A [color=transparent]
left -> leftleft
// left -> B [color=transparent]
left_[fillcolor=red]
A,B[fontsize=0,color=transparent]
left_ -> leftleft_
left_ -> cnode_
}
```
```c
x_type *left = pathp[1].node;
rbtn_left_set(x_type, x_field, cnode, left);
if (!rbtn_red_get(x_type, x_field, left))
return;
x_type *leftleft = rbtn_left_get(x_type, x_field, left);
if (leftleft && rbtn_red_get(x_type, x_field, leftleft)) {
/* Fix up 4-node. */
x_type *tnode;
rbtn_black_set(x_type, x_field, leftleft);
rbtn_rotate_right(x_type, x_field, cnode, tnode);
cnode = tnode;
}
```
如果 cmp >= 0 ,
1. 出現左邊狀況則進行顏色轉換設定成右邊的樣子。
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.7]
left, right [fillcolor=red]
cnode -> left
cnode -> right
cnode_[fillcolor=red]
cnode_ -> left_
cnode_ -> right_
}
```
2. 出現左邊狀況則進行旋轉成右邊的樣子。這邊是為了滿足 LLRBT 的左傾條件。
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.7]
tnode [fillcolor=red]
null, null1[fontsize=0,color=transparent]
cnode -> null[color=transparent]
cnode -> tnode
cnode_[fillcolor=red]
tnode_ -> cnode_
tnode_ -> null1[color=transparent]
}
```
```c
x_type *right = pathp[1].node;
rbtn_right_set(x_type, x_field, cnode, right);
if (!rbtn_red_get(x_type, x_field, right))
return;
x_type *left = rbtn_left_get(x_type, x_field, cnode);
if (left && rbtn_red_get(x_type, x_field, left)) {
/* Split 4-node. */
rbtn_black_set(x_type, x_field, left);
rbtn_black_set(x_type, x_field, right);
rbtn_red_set(x_type, x_field, cnode);
} else {
/* Lean left. */
x_type *tnode;
bool tred = rbtn_red_get(x_type, x_field, cnode);
rbtn_rotate_left(x_type, x_field, cnode, tnode);
rbtn_color_set(x_type, x_field, tnode, BBBB);
rbtn_red_set(x_type, x_field, cnode);
cnode = tnode;
}
```
最後將根節點設為黑色。
##### `tree_remove()`
將巨集 `x_attr void x_prefix##remove(x_rbt_type *rbtree, x_type *node)` 展開為 `static void tree_remove(tree_t *rbtree, node_t *node)` ,此函式用於刪除紅黑樹中的節點,跟 `tree_insert()` 一樣的方式去尋找要刪除的節點,只是額外加上 `if(cmp==0)` ,若條件式成立,則表示找到要刪除的節點,接著就是找出節點的 successor ,來為交換做準備。
舉個例子來說,現在有一個紅黑樹,我想刪除節點 `2`。
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.5]
2[fillcolor=red]
A,B,C,D,E,F[fontsize=0,color=transparent]
7 -> {2 11}
2 -> {1 5}
1 -> {A B}
5 -> {C D}
11 -> {E F}
}
```
可以得到這個 path 陣列。
```graphviz
digraph path{
// node [shape=plaintext, fontcolor=black, fontsize=18];
// "Pointers:" -> "Values:" [color=white];
node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true];
pointers [label="<f0> | <f1>pathp ", color=white];
values [label="<f0> 7, -1 | <f1> 2, -1 ", fillcolor=pink, style=filled];
// { rank=same; "Pointers:"; pointers }
// { rank=same; "Values:"; values }
edge [color=black];
pointers:f1 -> values:f1;
}
```
在 `cmp == 0` 時,代表找到想刪除的節點,也就是找到節點 `2`,會設定 pathp->cmp = 1 ,並尋找此節點的 successor 。
```c
if (cmp == 0) {
/* Find node's successor, in preparation for swap. */
pathp->cmp = 1;
nodep = pathp;
for (pathp++; pathp->node; pathp++) {
pathp->cmp = -1;
pathp[1].node = rbtn_left_get(x_type, x_field, pathp->node);
}
break;
}
```
因此 path 陣列會變成
```graphviz
digraph path{
// node [shape=plaintext, fontcolor=black, fontsize=18];
// "Pointers:" -> "Values:" [color=white];
node [shape=record, fontcolor=black, fontsize=14, width=4.75, fixedsize=true];
pointers [label="<f0>pathp[-1] | <f1>pathp | <f2>pathp[1] ", color=white];
values [label="<f0> 7, -1 | <f1> 2, 1 | <f2> 1, -1", fillcolor=pink, style=filled];
// { rank=same; "Pointers:"; pointers }
// { rank=same; "Values:"; values }
edge [color=black];
pointers:f0 -> values:f0;
pointers:f1 -> values:f1;
pointers:f2 -> values:f2;
}
```
## 測驗二
## 測驗三