# 2021q1 Homework1 (quiz1) contributed by < `ChenBoSyun` > ###### tags: `linux2021` ## Outline [toc] ## 程式碼分析 ### linked list struct :::info 既然已經 typedef node_t 為何不用 `node_t *next`,有其他考量或是單純 coding style ::: ```c typedef struct __node { int value; struct __node *next; } node_t; ``` ### list_add_node_t 這段程式碼是將 node_t 插入 list 的頭,並且把 list 重新指向 node_t ```c static inline void list_add_node_t(node_t **list, node_t *node_t) { node_t->next = *list; *list = node_t; } ``` 下圖為進入 list_add_node_t 時的狀態,C 語言 pass by value 會配置一段記憶體空間,將傳入參數的值複製到所配置的記憶體 ```graphviz digraph { rankdir=LR; node [shape=record]; head [label="{ <data> head | <ref> }"]; "*node_t" [label="{ <data> node | <ref> }"]; "list" -> "*list" -> head; "list(in_func)" -> "*list"; "node_t" -> "*node_t"; "node_t(in_func)" -> "*node_t"; } ``` 下圖為 `node_t->next = *list;` 後的狀態,若傳入參數是 ```graphviz digraph { rankdir=LR; node [shape=record]; head [label="{ <data> head | <ref> }"]; "*node_t" [label="{ <data> node | <ref> }"]; "list" -> "*list" -> head; "list(in_func)" -> "*list"; "node_t" -> "*node_t"; "node_t(in_func)" -> "*node_t"; "*node_t" -> head; } ``` 下圖為 ` *list = node_t;` 後的狀態,這邊的動作是移動 list 的指標到 node,因此傳入的型態是 list 指標的指標,這樣才會確實的將 node address 指派給 list 的指標 ```graphviz digraph { rankdir=LR; node [shape=record]; head [label="{ <data> head | <ref> }"]; "*node_t" [label="{ <data> node | <ref> }"]; "list" -> "*list" -> "*node_t"; "list(in_func)" -> "*list"; "node_t" -> "*node_t"; "node_t(in_func)" -> "*node_t"; "*node_t" -> head; } ``` :::warning [延伸思考] 若傳入的參數是 list 的指標 ```c static inline void list_add_node_t(node_t *list, node_t *node_t) { node_t->next = list; *list = *node_t; } ``` 下圖是執行 list_add_node_t 後的結果 ```graphviz digraph { rankdir=LR; node [shape=record]; head [label="{ <data> node | <ref> }"]; "*node_t" [label="{ <data> node | <ref> }"]; "list" -> head; "list(in_func)" -> head; "node_t" -> "*node_t"; "node_t(in_func)" -> "*node_t"; "*node_t" -> head; head -> head; } ``` ::: ### list_concat list concat 從部分程式碼與涵式名稱可以推測,其功能是將鏈結串列 right 接在 鏈結串列 left 後面 實際上, left 是用來紀錄 `node_t.next` 的記憶體位址,`node_t.next` 的型態是 `node_t *` ,因此 left 的型態才會是 `node_t **` 此外基於 pass by value 的機制, 引數 left 本身的值沒有改變,還是指向 list 的開頭 ```c= static inline void list_concat(node_t **left, node_t *right) { while (*left) left = &((*left)->next); *left = right; } ``` ```c #1 static inline void list_concat(node_t **left, node_t *right) ``` 下圖是進入 list_concat 後的示意圖 ```graphviz digraph { rankdir=LR; node [shape = box]; "left" [label= "left"]; "left(inFunc)" [label= "left(inFunc)"]; "*left" [label= "*left"]; "right" [label= "right"]; NULL [label= "NULL"]; node [shape = record]; node1 [label="{ <data> node1 | <ref> }"]; node2 [label="{ <data> node2 | <ref> }"]; node3 [label="{ <data> node3 | <ref> }"]; node_n [label="{ <data> node_n | <ref> }"]; { rank="same"; "left" -> "*left"; "left(inFunc)" -> "*left"; } "*left" -> node1 -> node2 -> node3 -> NULL; "right" -> node_n; } ``` ```c #3 left = &((*left)->next); ``` 每次迴圈,都會指派下一個 `node_t.next` 的記憶體位址給 left ```graphviz digraph { rankdir=LR; node [shape = box]; "left" [label= "left"]; "left(inFunc)" [label= "left(inFunc)"]; "*left" [label= "*left"]; NULL [label= "NULL"]; node [shape = record]; node1 [label="{ <data> node1 | <ref> }"]; node2 [label="{ <data> node2 | <ref> }"]; node3 [label="{ <data> node3 | <ref> }"]; { rank="same"; "left" -> "*left"; } "*left" -> node1 -> node2 -> node3 -> NULL; "left(inFunc)" -> node1:ref; } ``` ```c #2 while (*left) ``` 迴圈結束條件是 `*left == NULL`,迴圈結束時 left 的值是最後一個 `node_t.next` 的記憶體位址,如下圖 ```graphviz digraph { rankdir=LR; node [shape = box]; "left" [label= "left"]; "left(inFunc)" [label= "left(inFunc)"]; "*left" [label= "*left"]; NULL [label= "NULL"]; node [shape = record]; node1 [label="{ <data> node1 | <ref> }"]; node2 [label="{ <data> node2 | <ref> }"]; node3 [label="{ <data> node3 | <ref> }"]; { rank="same"; "left" -> "*left"; } "*left" -> node1 -> node2 -> node3 -> NULL; "left(inFunc)" -> node3:ref; } ``` ```c #4 *left = right; ``` ```graphviz digraph { rankdir=LR; node [shape = box]; "left" [label= "left"]; "left(inFunc)" [label= "left(inFunc)"]; "*left" [label= "*left"]; node [shape = record]; node1 [label="{ <data> node1 | <ref> }"]; node2 [label="{ <data> node2 | <ref> }"]; node3 [label="{ <data> node3 | <ref> }"]; node_n [label="{ <data> node_n | <ref> }"]; { rank="same"; "left" -> "*left"; } "*left" -> node1 -> node2 -> node3 -> node_n; "left(inFunc)" -> node3:ref; } ``` ### quicksort 參考 [quicksort wiki](https://zh.wikipedia.org/wiki/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F) quicksort 使用 Divide and conquer 的策略 實作分為三個步驟 1. 從數列中選定一個基準 (pivot) 2. 將數列分成兩個子數列,分別是比 pivot 大的數列,和比 pivot 小的數列 3. 遞迴執行 1,2 步驟 ```c= void quicksort(node_t **list) { if (!*list) return; node_t *pivot = *list; int value = pivot->value; node_t *p = pivot->next; pivot->next = NULL; node_t *left = NULL, *right = NULL; while (p) { node_t *n = p; p = p->next; list_add_node_t(n->value > value ? AAA : BBB, n); } quicksort(&left); quicksort(&right); node_t *result = NULL; list_concat(&result, left); CCC; *list = result; } ``` 遞迴程式需要設定結束的條件,否則會陷入無窮遞迴 quicksort 在實作上會重複將數列切成左右兩個子數列,直到子數列小到不能再分割,也就是 `NULL` ```c #1 if (!*list) #2 return; ``` 這段程式碼是將 list 的開頭當作 pivot,並且 `pivot->next = NULL` 代表 pivot 是只有一個元素的 linked list,這是為了確保 `list_concat` 的正確性 ```c node_t *pivot = *list; int value = pivot->value; node_t *p = pivot->next; pivot->next = NULL; ``` linked list 的分割比起 array 還要單純,只要走訪一遍linked list,將 value 比 pivot 小的加到left ,大的加到 right ```c node_t *left = NULL, *right = NULL; while (p) { node_t *n = p; p = p->next; list_add_node_t(n->value > value ? &right : &left, n); } ``` 遞迴過程,分別對 left 跟 right 排序 ``` quicksort(&left); quicksort(&right); ``` 在對 left, right 都排序過後,只要將 left, pivot, right 接在一起就好 ```c node_t *result = NULL; list_concat(&result, left); list_concat(&result, pivot); list_concat(&result, right); *list = result; ```