# 2023q1 Homework3 (quiz3) contributed by < `koty6069` > [2023q1 第 3 週測驗題](https://hackmd.io/@sysprog/linux2023-quiz3) ## 測驗 `1` ### 程式碼解析 [(incomplete treesort.c)](https://gist.github.com/jserv/920766d1fd893d0efd66ac7fdaf23401) 此程式主要是使用 C 實作紅黑樹來實現 C++ 中的 `std::map`。 ```c typedef struct __node { uintptr_t color; struct __node *left, *right; struct __node *next; long value; } node_t __attribute__((aligned(sizeof(long)))); ``` 定義紅黑樹節點時使用與 [linux 核心](https://github.com/torvalds/linux/blob/master/include/linux/rbtree_types.h) 相同的技巧,宣告 `unsigned long` 儲存親代節點,並透過 `__attribute__((aligned(sizeof(long))))` 將此結構對齊 `sizeof(long)` ,使得最低 2 個位元空出,就可以將顏色儲存在最低位元中。 與 linux 核心的宣告不同處在於,因為要直接使用來進行排序,因此將 `value` 放在結構體內用來存放數值,並且新增 `next` 指向原本 `list` 中的下一個節點。 > 在 `/usr/include/stdint.h` 可以得知 `uintptr_t` 為 `unsigned long` 型態。 ```c #define rb_parent(r) ((node_t *) (AAAA)) #define rb_color(r) ((color_t) (r)->color & 1) ``` 根據名字可以看出 `rb_parent` 用來取得親代節點,`rb_color` 用來取得當前節點的顏色,因為親代節點和顏色同時存放在 `uintptr_t color` 中,要取得顏色就是將最低位元取出,要取得親代節點則是要去除掉最低位元,因此 `AAAA` 為 `(r)->color & ~1`。 ```c typedef enum { CMAP_RED = 0, CMAP_BLACK } color_t; ``` 在此實作中將紅色設定為 0、黑色為 1,所以設定顏色時就是將最低位元設定為 0 或 1。 ```c #define rb_set_red(r) \ do { \ BBBB; \ } while (0) #define rb_set_black(r) \ do { \ CCCC; \ } while (0) ``` 要設成紅色就是與 `1111...1110` 做 `and`,`BBBB` 為 `(r)->color &= ~1`,要設成黑色就是與 `0000...0001` 做 `or`, `CCCC` 為 `(r)->color |= 1`。 <!-- ```c=118 /* Set the color to black by default */ rb_set_red(node); ``` 新加入的節點應該為紅色,對照 119 行也是如此,所以 118 行應該為 red 而非 black。 --> ```c static node_t *cmap_rotate_left(cmap_t obj, node_t *node) static node_t *cmap_rotate_right(cmap_t obj, node_t *node) ``` `cmap_rotate_left` 和 `cmap_rotate_right` 為單一次的左旋轉和右旋轉,可參考 [Linux 核心的紅黑樹](https://hackmd.io/@sysprog/linux-rbtree#%E7%B0%A1%E8%BF%B0%E7%B4%85%E9%BB%91%E6%A8%B9)。`cmap_l_l`、`cmap_l_r`、`cmap_r_r` 和 `cmap_r_l` 則是插入或刪除紅黑樹節點時遇到不同情況進行的調整,會透過 `cmap_fix_colors` 在調整顏色時呼叫這些函式進行使用。 ```c static bool cmap_insert(cmap_t obj, node_t *node, void *value) ``` ```c /* Traverse the tree until we hit the end or find a side that is NULL */ for (node_t *cur = obj->head;;) { int res = obj->comparator(&node->value, &cur->value); if (!res) /* If the key matches something else, don't insert */ assert(0 && "not support repetitive value"); if (res < 0) { if (!cur->left) { cur->left = node; rb_set_parent(node, cur); cmap_fix_colors(obj, node); break; } DDDD; } else { if (!cur->right) { cur->right = node; rb_set_parent(node, cur); cmap_fix_colors(obj, node); break; } EEEE; } } ``` `cmap_insert` 為插入新節點,因此會透過 `for` 迴圈找到應該對應的位置,與一般的二元搜尋樹相同,若要插入的值較小就往左走,反之往右走,若當前節點的左或右子樹為 `NULL`,就代表找到插入的位置,也就是會進入 `if (!cur->left)` 和 `if (!cur->right)` 條件式中,將節點放入該位置並檢查插入節點後的紅黑樹是否符合規則,若不為 `NULL` 代表需要繼續向左或右走,因此 `DDDD` 為 `cur = cur->left`,`EEEE` 為 `cur = cur->right`。 ```c void tree_sort(node_t **list) { node_t **record = list; cmap_t map = cmap_new(sizeof(long), sizeof(NULL), cmap_cmp_int); while (*list) { cmap_insert(map, *list, NULL); list = FFFF; } node_t *node = cmap_first(map), *first = node; for (; node; node = cmap_next(node)) { *list = node; list = GGGG; } HHHH; *record = first; free(map); } ``` `tree_sort` 則是使用 `map` 來進行排序,會傳入一個要被排序的 `list`,在 `while` 迴圈中一個一個將值插入 `map`,因此 `FFFF` 為 `&(*list)->next`,接著在 `for` 迴圈中使用 `cmap_next` 照順序取出並放回 `list` 即完成排序,`GGGG` 同樣為 `&(*list)->next`,當兩個迴圈都執行完時,`*list` 會落在 `list` 的最尾端,需要將其指向 `NULL` 表示結束,所以 `HHHH` 為 `*list = NULL`。