# 2023q1 Homework4 (quiz4)
###### tags: `linux2023`
contributed by < `Paintako` >
## 測驗 `1`
```c
typedef struct { \
x_type *node; \
int cmp; \
} x_prefix##path_entry_t;
```
閱讀前置處理器篇, 得知 `x_prefix##` 是透過 `cpp` 來展開產生程式碼的, 註釋也有提示 :
```c
* x_prefix:
* Prefix for generated functions (e.g., ex_).
```
故產生出來的程式碼會像是 `x_prefix##path_entry_t -> ex_path_entry_t
`
### rb_gen macro
#### insert
這段程式是 `left-leaning 2-3 red-black trees` 的實作
參考 [LLRB](https://sedgewick.io/wp-content/uploads/2022/03/2008-09LLRB.pdf)
插入時, 先找到插入位置, 再從從樹葉一路往上到 root 調整
因為是左傾樹, 所以如果遇到插入時, 所以插入時遇到只有右子時需要往左調整
![](https://i.imgur.com/Y4WSDBi.png)
如果遇到連續兩個紅點 (or link), 則需要做 balance
![](https://i.imgur.com/u8cNqQF.png)
而遇到 4 樹的情況, 需要做分裂
![](https://i.imgur.com/eKY9KbF.png)
:::warning
4 樹: node with 3 keys and 4 children
3、4 樹 in RB trees:
* 用 **紅邊** 表示 3、4 樹的邊
* 3樹: ![](https://i.imgur.com/wEP0ebR.png)
* 4樹: ![](https://i.imgur.com/gLr6AEl.png)
:::
```c
x_prefix##path_entry_t path[RB_MAX_DEPTH]; \
x_prefix##path_entry_t *pathp; \
rbt_node_new(x_type, x_field, rbtree, node); \
/* Wind. */ \
path->node = rbtree->root;
```
一開始不曉得為何 `path` 明明就是宣告成 array 的形式, 但卻可以直接賦值, 但後來想到, path 雖然是一個 array, 但 array 等同於指向 array[0] 的指針, 所以當我們使用 path->node 時, 相當於 path[0].node, 這樣的寫法可以省去紀錄 i, 並且只通過操作指標的方式取得 array 中的成員.
```c
pathp[1].node = rbtn_left_get(x_type, x_field, pathp->node);
```
而這裡為何是 `pathp[1]`? 因為我們只是操作 pointer, 而 pointer 的下一個成員即是樹中的下一個節點.
這邊插入一個新節點時, 從 root 開始比較, 並且把比較的結果 ( cmp ) 以及比較的節點紀錄在 `path` 中
找到要插入的位置後, 看 `path` 中前一個點 (父點) 的資訊, 前一個點中有 `cmp` , 可以得知插入點是屬於左子 ( cmp < 0 ), 或者右子 (cmp > 0)
給定一節點 x, 考慮以下情形:
1. rbtn_rotate_left
2. rbtn_rotate_right
第一種 rotate_left
* 旋轉前:
* ![](https://i.imgur.com/D2v33jK.png)
* 旋轉後:
![](https://i.imgur.com/n1E3TnJ.png)
第二種 rotate_right
* 旋轉前:
* ![](https://i.imgur.com/e9vPDXV.png)
* 旋轉後:
![](https://i.imgur.com/n1E3TnJ.png)
```c
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); // AAAA \
rbtn_rotate_right(x_type, x_field, cnode, tnode); \
cnode = tnode; \
}
```
此段程式碼是檢查出現連續兩紅點的情形, 如果連續出現兩個紅點, 則旋轉調整, 而調整完後是 4 樹, 所以這邊先對 `leftleft` 調整成黑點, 則在調整完後, 就已經是 4 樹分裂的情形.
```c
else { \
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, tred); // BBBB \
rbtn_red_set(x_type, x_field, cnode); // CCCC \
cnode = tnode; \
} \
} \
pathp->node = cnode; \
}
```
上面的是考慮 `插入的點 > 比較節點` 的情形, 如圖示
![](https://i.imgur.com/CECfNtQ.png)
這種情況下, 比較節點有兩種情形:
1. 沒有左子, 或者左子不是紅的 :
這種情況, 會先紀錄 cnode 的顏色, 並且左旋轉, 旋轉上來的 tnode 設為 cnode 的顏色, 並且因為旋轉後的 cnode 改成紅色, 並且往上繼續檢查
2. 有左子, 並且左子是紅的, 也就是 4 樹, 如下圖:
![](https://i.imgur.com/eKY9KbF.png)
這種情形只需調整顏色即可
:::danger
Todo:
圖表改成 `Graphviz` & `TreeComparison`
:::
#### remove
```c=
else { \
pathp[1].node = rbtn_right_get(x_type, x_field, pathp->node); \
if (cmp == 0) { \
/* Find node's successor, in preparation for swap. */ \
pathp->cmp = 1; // DDDD \
nodep = pathp; \
for (pathp++; pathp->node; pathp++) { \
pathp->cmp = -1; \
pathp[1].node = \
rbtn_left_get(x_type, x_field, pathp->node); \
} \
break; \
} \
}
```
要刪除某個節點時, 一樣先從 root 一路往下尋找要刪除的點, 這邊我們假設已經找到要刪除的點, 用一個指針 `nodep` 記錄刪除地址, 再來往右子樹開始找, 找到 `右子樹中最小的點`, 作為替代點, 這也就是為何 `pathp->cmp` (第5行) 要設為 1 的原因.
接著, 判斷刪除點是否有 successor, 有的話把此點拉上來替代
這個點有三種情況:
* case1: 是 right-least (右子樹中最小的)
* case2: 沒有右子但有左子
* case3: 這個樹只有刪除點一點
首先是第一種情況: 後繼者在右子樹當中
```c
if (pathp->node != node) { \
/* Swap node with its successor. */ \
bool tred = rbtn_red_get(x_type, x_field, pathp->node); \
rbtn_color_set(x_type, x_field, pathp->node, \
rbtn_red_get(x_type, x_field, node)); \
rbtn_left_set(x_type, x_field, pathp->node, \
rbtn_left_get(x_type, x_field, node)); \
/* If node's successor is its right child, the following code */ \
/* will do the wrong thing for the right child pointer. */ \
/* However, it doesn't matter, because the pointer will be */ \
/* properly set when the successor is pruned. */ \
rbtn_right_set(x_type, x_field, pathp->node, \
rbtn_right_get(x_type, x_field, node)); \
rbtn_color_set(x_type, x_field, node, tred); \
/* The pruned leaf node's child pointers are never accessed */ \
/* again, so don't bother setting them to nil. */ \
nodep->node = pathp->node; \
pathp->node = node; \
if (nodep == path) { \
rbtree->root = nodep->node; \
} else { \
if (nodep[-1].cmp < 0) { \
rbtn_left_set(x_type, x_field, nodep[-1].node, \
nodep->node); \
} else { \
rbtn_right_set(x_type, x_field, nodep[-1].node, \
nodep->node); \
} \
}
```
1. 先找到後繼者的位置 `pathp`, 並把連結顏色 assign 給後繼者
2. 再來把要刪除節點 node 的左右子點 assign 給後繼者, 就可以把 node 給移除了
3. 移除後, 判斷後繼者的位置, 如果他是 root, 代表此點沒有雙親, 反之, 找出他的父母並 assign 給此點
再來考慮第二種情況, 也就是沒有右子, 但是有左子的情況, 有左子同樣判斷以下條件:
1. 如果找到後繼者的位址 == pathp, 也就是找到的位置位於樹根, 那麼將 root 的位址改成刪除點的左子
2. 不是以上情況, 只要把左子拉到刪除的位置並且 assign 他的左右父點即可
最後是第三種情況, 刪除點的位置位於 root, 這時只需要判斷 `nodep == path` 即可確定是不是刪除點 = root