# 2023q1 Homework4 (quiz4)
contributed by < `Korin777` >
> [測驗題目](https://hackmd.io/@sysprog/linux2023-quiz4)
## 測驗三
### `RB_LOG2_MAX_NODES`
`node_t` 的大小為 `sizeof(void*) * 4` 所以 node 最多有 $$\frac{1 << (sizeof(void *) << 3)} {(sizeof(void*) * 4)}$$ 取 `log2` 可以得到 `RB_LOG2_MAX_MEM_BYTES - log2(sizeof(void*)*2) - 1`,若 `sizeof(void*) == 8` 就會是 `RB_LOG2_MAX_MEM_BYTES - 4 - 1`
### `tree_insert`
這裡透過 `path` 來記錄插入節點的所有祖先節點,所以節點不須記錄自己的親代節點也能在插入後向上修復紅黑數,遇到黑色節點就可以停下,因為只有紅色節點可能會違反規則
```c=
if (pathp->less) { \
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; \
} \
} 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); \
rbtn_red_set(x_type, x_field, cnode); \
cnode = tnode; \
} \
}
```
第 7 行修復紅色相鄰的情況,將 `cnode` 左旋並將 `leftleft` 變為黑色讓路徑上的黑色節點數相同
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.8]
left,leftleft[fillcolor=red]
null,null1[fontsize=0,color=transparent]
cnode -> left
cnode -> null[color=transparent]
left -> leftleft
left -> null1[color=transparent]
fix_left[fillcolor=red]
fix_left -> fix_leftleft
fix_left -> fix_cnode
}
```
第 15 行讓紅黑數是左傾的,將 `cnode` 右旋並將 `cnode` 設為紅色 `right` 的顏色則要跟 `cnode` 一樣確保路徑上的黑色節點數相同
這裡做完可能會有紅色相鄰的情況,會在下一步透過上面或下面的方法修復
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.8]
right[fillcolor=red]
cnode[fillcolor=white]
null,null1[fontsize=0,color=transparent]
cnode -> null[color=transparent]
cnode -> right
fix_cnode[fillcolor=red]
fix_right[fillcolor=white]
fix_right -> fix_cnode
fix_right -> null1[color=transparent]
}
```
第 20 行將顏色翻轉,這個操作既不會改變路徑上黑色節點的數量又可以修復紅色相鄰的情況
```graphviz
digraph BST {
graph[ordering="out"]
node [shape=circle, fixedsize=true, style=filled,width=.8]
left,right[fillcolor=red]
cnode -> left
cnode -> right
fix_cnode[fillcolor=red]
fix_cnode -> fix_left
fix_cnode -> fix_right
}
```
### `sum_subtree`
```c=
static int sum_subtree(node_t *node)
{
int result = 0;
while (node) {
node_t *pre;
if (!rbtn_left_get(node_t, link, node))
goto do_print;
for (pre = rbtn_left_get(node_t, link, node);
rbtn_right_get(node_t, link, pre) &&
rbtn_right_get(node_t, link, pre) != node;
pre = rbtn_right_get(node_t, link, pre)) {
}
if (!rbtn_right_get(node_t, link, pre)) {
rbtn_right_get(node_t, link, pre) = node;
node = rbtn_left_get(node_t, link, node); // JJJJ
} else {
rbtn_right_get(node_t, link, pre) = NULL; // KKKK
do_print:
result += node->value;
printf("value: %d\n", node->value);
node = rbtn_right_get(node_t, link, node); // LLLL
}
}
return result;
}
/* 5 15行 5 輸出 1 5 輸出 5 5
* / => / => / => /
* 1 1 22行回到5 1 10行發現回到5 1
* \ \ 將1的rightmost node 設回 null
* 5 5 輸出 5
* /
```
可以發現沒有用到額外的空間卻能將值由小到大取出,8 ~ 12 行利用了 left node 的 right most child 空間紀錄前一個較大的 node , 之後輸出這個較大 node 的值後再把它改回 NULL , 所以 22 行 `rbtn_right_get` 可以回到前一個較大的 node , 第 10 行就是判斷是否回到這個較大的 node
相較於用額外空間來記錄較大的 node , 為了找出 right most child 會多出許多走訪的過程