# 2021q1 Homework1 (quiz1) contributed by < `robertlin0401` > ###### tags: `linux2021` > [2021 年第 1 週隨堂測驗題目](https://hackmd.io/@sysprog/linux2021-quiz1) --- ## 程式運作原理 ### node 結構 ```c typedef struct __node { int value; struct __node *next; } node_t; ``` ### list operation 函式 ```c= static inline void list_add_node_t(node_t **list, node_t *node_t) { node_t->next = *list; *list = node_t; } ``` * 說明 * 作用:將 `node_t` 加入 linked list 的前端 * `node_t`:表指向欲加入目標 linked list 的新 node 的指標 * `list`:表指向目標 linked list 的第一個 node 的指標的指標 * 分析 * 給定一 linked list 結構如下, `head` 表指向第一個 node 的指標 ```graphviz digraph { graph [rankdir=LR]; list_ptr [shape=box; label="list"]; head_ptr [shape=box; label="head"]; node_invis [shape=box; style=invis]; node_A [shape=record; label="{node_A|<out>}"]; node_B [shape=record; label="{node_B|}"]; node_end [shape=plaintext; label="..."]; list_ptr -> head_ptr; head_ptr -> node_A [constraint=false]; node_invis -> node_A [style=invis]; node_A -> node_B -> node_end; { rank="same"; list_ptr; head_ptr; }; } ``` * 第 2 行程式碼將 `*list`,即 `head`,賦值給 `node_t->next`,因此 `node_t->next` 將指向 `head` 所指的 `node_A`,結果如下 ```graphviz digraph { graph [rankdir=LR]; list_ptr [shape=box; label="list"]; head_ptr [shape=box; label="head"]; node_A [shape=record; label="{node_A|}"]; node_B [shape=record; label="{node_B|}"]; node_end [shape=plaintext; label="..."]; node_t [shape=record; label="{node_t|}"]; list_ptr -> head_ptr; head_ptr -> node_A [constraint=false]; node_t -> node_A -> node_B -> node_end; { rank="same"; list_ptr; head_ptr; }; } ``` * 第 3 行程式碼將 `*list`,即 `head`,改成指向 `node_t`,結果如下 ```graphviz digraph { graph [rankdir=LR]; list_ptr [shape=box; label="list"]; head_ptr [shape=box; label="head"]; node_invis [shape=box; style=invis]; node_A [shape=record; label="{node_A|}"]; node_B [shape=record; label="{node_B|}"]; node_end [shape=plaintext; label="..."]; node_t [shape=record; label="{node_t|}"]; list_ptr -> head_ptr; head_ptr -> node_t [constraint=false]; node_invis -> node_t [style=invis]; node_t -> node_A -> node_B -> node_end; { rank="same"; list_ptr; head_ptr; }; } ``` ```cpp= static inline void list_concat(node_t **left, node_t *right) { while (*left) left = &((*left)->next); // LLL *left = right; } ``` * 說明 * 作用:將 `right` 所指向的 linked list,串接(concatenate)在 `*left` 所指向的目標 linked list 尾端 * `left`:表指向目標 linked list 的第一個 node 的指標的指標 * `right`:表欲串接在目標 linked list 尾端的 linked list 的第一個 node 的指標 * 分析 * 給定兩 linked list 結構如下, `head` 表指向目標 linked list 的第一個 node 的指標 ```graphviz digraph { graph [rankdir=LR]; right_ptr [shape=box; label="right"]; node_invis2 [shape=box; style=invis]; node_C [shape=record; label="{node_C|}"]; node_D [shape=record; label="{node_D|}"]; node_end2 [shape=box; label="NULL"]; node_invis2 -> node_C [style=invis]; right_ptr -> node_C [constraint=false]; node_C -> node_D -> node_end2; left_ptr [shape=box; label="left"]; head_ptr [shape=box; label="head"]; node_invis1 [shape=box; style=invis]; node_A [shape=record; label="{node_A|}"]; node_B [shape=record; label="{node_B|}"]; node_end1 [shape=box; label="NULL"]; left_ptr -> head_ptr; head_ptr -> node_A [constraint=false]; node_invis1 -> node_A [style=invis]; node_A -> node_B -> node_end1; { rank="same"; left_ptr; head_ptr; }; } ``` * 第 2-3 行程式碼的目的為尋找 `*left` 串列的尾端 node 的 next 指標的指標,步驟如下 * 第一步:`*left` 即 `head`,指向 `node_A`,並非 NULL,因此將 `left` 向前移動,改成指向 `node_A->next` 的指標 ```graphviz digraph { graph [rankdir=LR]; left_ptr [shape=box; label="left"]; head_ptr [shape=box; label="head"]; node_invis [shape=box; style=invis]; node_A [shape=record; label="{node_A|<next>}"]; node_B [shape=record; label="{node_B|}"]; node_end [shape=box; label="NULL"]; head_ptr -> left_ptr [style=invis]; left_ptr -> node_A:next; head_ptr -> node_A [constraint=false]; node_invis -> node_A [style=invis]; node_A -> node_B -> node_end; } ``` * 第二步:`*left` 仍非 NULL,因此繼續將 `left` 向前移動,改成指向 `node_B->next` 的指標 ```graphviz digraph { graph [rankdir=LR]; left_ptr [shape=box; label="left"]; head_ptr [shape=box; label="head"]; node_invis [shape=box; style=invis]; node_A [shape=record; label="{node_A|}"]; node_B [shape=record; label="{node_B|<next>}"]; node_end [shape=box; label="NULL"]; head_ptr -> left_ptr [style=invis]; left_ptr -> node_B:next [constraint=false]; head_ptr -> node_A [constraint=false]; node_invis -> node_A [style=invis]; node_A -> node_B -> node_end; } ``` * 第三步:此時 `*left` 為 NULL ,迴圈終止 * 根據上述步驟可知,==LLL 應填入 `&((*left)->next)`==,其中,`(*left)->next` 為下一個 node 的指標 * 第 4 行程式碼將當前 `*left`,即 `node_B->next`,改成欲串接的 linked list 的第一個 node 的指標 `right`,由此兩串列得以成功串接,結果如下 ```graphviz digraph { graph [rankdir=LR]; left_ptr [shape=box; label="left"]; right_ptr [shape=box; label="right"]; head_ptr [shape=box; label="head"]; node_invis [shape=box; style=invis]; node_A [shape=record; label="{node_A|}"]; node_B [shape=record; label="{node_B|<next>}"]; node_C [shape=record; label="{node_C|}"]; node_D [shape=record; label="{node_D|}"]; node_end [shape=box; label="NULL"]; head_ptr -> left_ptr -> right_ptr[style=invis]; left_ptr -> node_B:next [constraint=false]; right_ptr -> node_C [constraint=false]; head_ptr -> node_A [constraint=false]; node_invis -> node_A [style=invis]; node_A -> node_B -> node_C -> node_D -> node_end; } ``` ### quicksort 實作 ```cpp= 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 ? &right /* AAA */ : &left /* BBB */ , n); } quicksort(&left); quicksort(&right); node_t *result = NULL; list_concat(&result, left); list_concat(&result, pivot); list_concat(&result, right); // CCC *list = result; } ``` * 說明 * <span id="quicksort_concept">quicksort 基本概念</span> 1. 取數列其中一個元素作為 pivot,將 pivot 與其餘元素逐一比較,較 pivot 小者視為一個新數列,較 pivot 大者視為一個新數列,此過程稱為 Partition 2. <span id="quicksort_concept_2">對兩個新數列分別做 quicksort,直到無法再分割出新的數列為止</span> <br> :::info **補充說明:Array vs. Linked List** * Array 表示 1. 在每次 Partition 的過程中,會同時進行元素的 SWAP 2. 以由小到大排序為例,過程中會從前端搜尋比 pivot 大的元素,與從尾端搜尋的比 pivot 小的元素做 SWAP,直到比 pivot 小(大)的元素全部集中在前端(尾端) 3. 最後再將 pivot SWAP 到最正確的位置即完成一次 Partition 4. 由此過程可知,當無法再分割出新的數列時,排序即完成 * <span id="quicksort_linked_list">Linked List 表示</span> 1. 在每次 Partition 的過程中不做 SWAP,而是將 list 分割成三個 sublists:left、right 以及只有一個元素的 pivot 2. 針對 left 及 right 遞迴進行 quicksort 3. 當無法再分割出新的 list 時,排序尚未完成,需要再將所有 sublists 依 left -> pivot -> right 的順序進行合併 ::: * `list`:表目標串列的第一個 node 的指標的指標 * 分析 * 第 3-4 行程式碼為 quicksort 的終止條件,當 `!*list` 成立時,表示當前串列的元素個數為 0 個,無法再進行分割,此條件符合上述 [quicksort 基本概念第 2 點](#quicksort_concept_2) * 第 6 行程式碼將指向串列第一個 node 的指標賦值給 `pivot` ,因此 pivot 取的是串列的第一個元素 * 第 12-19 行程式碼根據 pivot 值將 linked list 進行分割,因為預期依據遞增順序排序,故分割成由較 pivot 小的 node 所組成的子串列 `left`,與由較 pivot 大的 node 所組成的子串列 `right` * `list_add_node_t` 函式的第一個引數 `n->value > value ? AAA : BBB` 為指定當前 node `n` 應加入的 linked list 的指標的指標,其中,`value` 為 pivot 之值 * `AAA` 為 `n->value > value` 成立時 node `n` 應加入的 linked list 的指標的指標,因此 ==`AAA` 應填入 `&right`== * `BBB` 為 `n->value > value` 不成立時 node `n` 應加入的 linked list 的指標的指標,因此 ==`BBB` 應填入 `&left`== * 第 21-22 行程式碼以遞迴的方式針對 `left`、`right` 兩子串列做 quicksort * 第 24-27 行程式碼目的是在分割結束後,將排序後的 nodes 依序重新串接,並將最終結果賦值給 `*list`,即完整串列的第一個 node 的指標 * ==`CCC` 應填入 `list_concat(&result, pivot); list_concat(&result, right)`==,說明可參考上述 quicksort 的補充說明中 [Linked List 表示](#quicksort_linked_list)部分 --- ## Random 行為的修正 ### 問題描述 * `random()` 方法是一種 Pseudo-Random Number Generator (PRNG),它會根據 `srandom()` 所設定的 seed 來生成隨機數,當未設定 seed 時,預設值為 1 ::: warning **節錄自 [random(3) man page description](https://linux.die.net/man/3/random)**: > The srandom() function sets its argument as the seed for a new sequence of pseudo-random integers to be returned by random(). These sequences are repeatable by calling srandom() with the same seed value. If no seed value is provided, the random() function is automatically seeded with a value of 1. ::: * 由於此測試程式未設定 seed,表示每次執行程式均以 1 為 seed 生成隨機數,故每次執行之結果均相同,以下為測試結果: ```shell $ ./random_test NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359 IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016 $ ./random_test NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359 IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016 ``` ### 想法 * 為了生成隨機亂數,我們需要在每次執行程式時給予不同的 seed 值 * Linux kernel 會收集來自驅動程式或其它來源的環境噪音並存入熵池(entropy pool),由於環境噪音等資訊是無法預期的,因此可以使用它作為 seed ::: warning **節錄自 [random(4) man page description](https://man7.org/linux/man-pages/man4/random.4.html):** > * The character special files /dev/random and /dev/urandom (present since Linux 1.3.30) provide an interface to the kernel's random number generator. > * The random number generator gathers environmental noise from device drivers and other sources into an entropy pool. > * Linux 3.17 and later provides the simpler and safer [getrandom(2)](https://man7.org/linux/man-pages/man2/getrandom.2.html) interface which requires no special files ::: * Linux 另外提供 `getrandom()` 的系統呼叫可從熵池中隨機讀取指定數量的位元組 :::info **補充說明:為何不全部使用熵池的資料作為亂數資料?** 因為熵池大小有限,若全部自熵池取用,在需要取得較大量的亂數的情形,可能會發生可取得的資料量不足的問題,因此只取用其中 4 bytes 作為 PRNG 的 seed ::: ### 修改 * 在測試程式中加入此段程式碼 ```c= int *seed = (int *)malloc(sizeof(int)); int result = getrandom(seed, sizeof(int), GRND_RANDOM | GRND_NONBLOCK); if (result == -1) *seed = result; srandom(*seed); free(seed) ``` * 說明 * 第 2 行程式碼透過 `getrandom()` 的系統呼叫從熵池中隨機讀取 4 個位元組,即 seed 所需的整數型態的大小 * 第 3 行程式碼檢查 `getrandom()` 的結果是否成功,若失敗則直接將 `result`(-1)做為 seed 使用 > 參考資料:[getrandom(2) man page](https://man7.org/linux/man-pages/man2/getrandom.2.html) * 以下為測試結果: ```shell $ ./optimized_random_test NOT IN ORDER : 188 170 115 147 816 212 472 309 249 942 1007 433 134 436 577 831 785 589 830 1009 IN ORDER : 115 134 147 170 188 212 249 309 433 436 472 577 589 785 816 830 831 942 1007 1009 $ ./optimized_random_test NOT IN ORDER : 678 155 495 710 632 554 699 416 46 524 143 264 930 10 249 375 177 30 582 576 IN ORDER : 10 30 46 143 155 177 249 264 375 416 495 524 554 576 582 632 678 699 710 930 $ ./optimized_random_test NOT IN ORDER : 404 750 572 632 568 570 942 61 306 565 993 235 997 853 268 328 286 712 416 201 IN ORDER : 61 201 235 268 286 306 328 404 416 565 568 570 572 632 712 750 853 942 993 997 ``` --- ## 非遞迴方法實作 ### 想法 * 在遞迴的方法中,每一回合會取一個 node 作為 pivot,並將 pivot 以外的 nodes 以較 pivot 大或小為基準分割為兩個 sublists,接著對個別的 sublist 做遞迴排序(詳見 [quicksort 基本概念](#quicksort_concept)) * 在非遞迴方法中,每一回合分割完成後,改從兩個 sublists 中挑選較短者先進行排序,另一者則將其記錄起來(以陣列記錄其起始與中止位置編號),並以迭代的方式一一排序,直到該陣列紀錄的所有 sublists 皆完成迭代 ### 實作 ```c= void non_recursive_quicksort(node_t **list, int elements) { #define MAX_LEVELS 300 int *arr = list2arr(list, elements); int pivot, begin[MAX_LEVELS], end[MAX_LEVELS], iter = 0, L, R; begin[0] = 0; end[0] = elements; while (iter >= 0) { L = begin[iter]; R = end[iter] - 1; if (L < R) { pivot = arr[L]; while (L < R) { while (arr[R] >= pivot && L < R) R--; if (L < R) arr[L++] = arr[R]; while (arr[L] <= pivot && L < R) L++; if (L < R) arr[R--] = arr[L]; } arr[L] = pivot; begin[iter+1] = L + 1; end[iter+1] = end[iter]; end[iter++] = L; if (end[iter]-begin[iter] > end[iter-1]-begin[iter-1]) { SWAP(&begin[iter], &begin[iter-1]); SWAP(&end[iter], &end[iter-1]); } } else { iter--; } } for (iter = 0; iter < elements; ++iter) { (*list)->value = arr[iter]; list = &((*list)->next); } free(arr); } ``` * 說明 * `list`:表目標串列的第一個 node 的指標的指標 * `elements`:表目標串列的 node 總數/長度 * 分析 * 第 3 行程式碼所設定的 `MAX_LEVELS`,其意義相似於 recursive 版本中的遞迴呼叫深度,作為限制其記憶體使用量 * note: 在遞迴版本中,每增加一層深度,便需要多一次 function call 占用 stack space; 在非遞迴版本中,則僅需多出記錄串列首尾位置編號的兩個變數的記憶體空間使用 * 第 5 行程式碼目的為將 linked list 中的資料提取出來,儲存成 array 以方便後續排序中搜尋與搬動資料,可避免大量的指標操作,其中,`list2arr` 的實作如下: ```c static int *list2arr(node_t **list, int elements) { int *arr = (int *)malloc(elements * sizeof(int)); for (int iter = 0; iter < elements; ++iter) { arr[iter] = (*list)->value; list = &((*list)->next); } return arr; } ``` * 第 10-30 行的迴圈目的是針對每個 subarray 進行排序,直到所紀錄的所有 subarrays 皆完成迭代 * 第 11-12 行程式碼會取出所記錄的最後一個 subarray 的資訊,並判斷是否為合法的 subarray(長度 > 0),若合法則進行排序 * 第 14 行程式碼會取當前 subarray 的第一個元素作為 pivot * 第 15-18 行的迴圈目的是根據 pivot 將 subarray 中的元素進行搬動,較 pivot 小者搬至左側,反之搬至右側 * 第 19-22 行程式碼會將 pivot 搬至正確位置,並將左、右兩 subarrays 的資訊記錄起來 * 第 23-26 行程式碼會比較左、右兩 subarrays,並將較短者 swap 到紀錄的最後一筆,在下次迭代中便會優先排序,如此一來可避免迭代深度超出 `MAX_LEVELS`,其中,`SWAP` 的實作如下: ```c static inline void SWAP(int *a, int *b) { *a ^= *b; *b ^= *a; *a ^= *b; } ``` * 第 32-35 行程式碼將排序好的 array 的值依序存回 linked list 中 <!-- TODO:分析兩者差異 --> > 參考資料:[Optimized QuickSort — C Implementation (Non-Recursive)](https://alienryderflex.com/quicksort/) --- ## linux-list quicksort 探討 ### 資料結構 * list_head:用於紀錄雙向鏈結,同時也用作 list 的指標 ```c struct list_head { struct list_head *prev; struct list_head *next; }; ``` > 參考 [/include/list.h#list_head](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L57) * listitem:表 node 本身 ```c struct listitem { uint16_t i; struct list_head list; }; ``` > 參考 [/private/common.h#listitem](https://github.com/sysprog21/linux-list/blob/master/private/common.h#L11) ### 程式碼與說明 ```c= static void list_qsort(struct list_head *head) { struct list_head list_less, list_greater; struct listitem *pivot; struct listitem *item = NULL, *is = NULL; if (list_empty(head) || list_is_singular(head)) return; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); pivot = list_first_entry(head, struct listitem, list); list_del(&pivot->list); list_for_each_entry_safe (item, is, head, list) { if (cmpint(&item->i, &pivot->i) < 0) list_move_tail(&item->list, &list_less); else list_move(&item->list, &list_greater); } list_qsort(&list_less); list_qsort(&list_greater); list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); } ``` * 第 10-11 行程式碼將 `list_less` 與 `list_greater` 兩 lists 進行初始化,分別作為鏈結比 pivot 小與比 pivot 大的 nodes 的 sublist > 參考 [INIT_LIST_HEAD](#INIT_LIST_HEAD) * 第 13-14 行程式碼取串列的第一個 node 作為 pivot,並將其從原串列刪除 > 參考 [list_first_entry](#list_first_entry)、[list_del](#list_del) * 第 16-21 行程式碼將串列剩餘的 nodes 依序與 pivot 做比較,較 pivot 小者會從原串列刪除並加入 `list_less` 的尾端,較 pivot 大者則是會從原串列刪除並加入 `list_greater` 的前端,因此比較皆完成之後,原串列會變為空串列 > 參考 [list_for_each_entry_safe](#list_for_each_entry_safe)、[cmpint](#cmpint)、[list_move_tail](#list_move_tail)、[list_move](#list_move) * 第 23-24 行程式碼針對 `list_less` 與 `list_greater` 兩 sublists 進行遞迴排序 * 第 26-28 行程式碼將 `pivot` 加入當前為空的原串列的前端,並將 `list_less` 加入原串列的前端、 `list_greater` 加入原串列的尾端 > 參考 [list_add](#list_add)、[list_splice](#list_splice)、[list_splice_tail](#list_splice_tail) ### 相關 operation * <span id="INIT_LIST_HEAD">INIT_LIST_HEAD</span>:初始化 list 的 head 指標,將 next 與 prev 均指向自己,表串列無 node ```c static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } ``` > 參考 [/include/list.h#INIT_LIST_HEAD](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L68) * <span id="list_first_entry">list_first_entry</span>:get first entry of the list > 參考 [/include/list.h#list_first_entry](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L348) * <span id="list_del">list_del</span>:Remove a list node from the list > 參考 [/include/list.h#list_del](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L119) * <span id="list_for_each_entry_safe">list_for_each_entry_safe</span>:iterate over list entries and allow deletes,因為以 safe 記錄下一個 node,所以若刪除當前的 node,即 entry,仍可繼續迭代 ```csharp <!-- c language, but csharp has better text color --> #define list_for_each_entry_safe(entry, safe, head, member) \ for (entry = list_entry((head)->next, __typeof__(*entry), member), \ safe = list_entry(entry->member.next, __typeof__(*entry), member); \ &entry->member != (head); entry = safe, \ safe = list_entry(safe->member.next, __typeof__(*entry), member)) ``` > 參考 [/include/list.h#list_for_each_entry_safe](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L414) * <span id="cmpint">cmpint</span>:回傳前者減去後者的值(整數減法) ```c static inline int cmpint(const void *p1, const void *p2) { const uint16_t *i1 = (const uint16_t *) p1; const uint16_t *i2 = (const uint16_t *) p2; return *i1 - *i2; } ``` :::warning **:question:延伸問題:為何要用指標,而非直接將兩數相減?** 仔細觀察可以發現其參數型態為 `void *`,代表此函式可應用於非整數型態變數的整數減法,例如:記憶體位址(指標型態)的減法操作 <!-- TODO:尚未找到實例 --> ::: > 參考 [/private/common.h#cmpint](https://github.com/sysprog21/linux-list/blob/master/private/common.h#L47) * <span id="list_move_tail">list_move_tail</span>:將目標 node 從原串列移除,並加入新串列的尾端 > 參考 [/include/list.h#list_move_tail](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L324) * <span id="list_move">list_move</span>:將目標 node 從原串列移除,並加入新串列的前端 > 參考 [/include/list.h#list_move](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L310) * <span id="list_add">list_add</span>:將目標 node 加入目標串列 > 參考 [/include/list.h#list_add](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L89) * <span id="list_splice">list_splice</span>:將目標串列加入原串列的前端 > 參考 [/include/list.h#list_splice](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L184) * <span id="list_splice_tail">list_splice_tail</span>:將目標串列加入原串列的尾端 > 參考 [/include/list.h#list_splice_tail](https://github.com/sysprog21/linux-list/blob/master/include/list.h#L210) ### 分析 * 對比[本題的 quicksort](#quicksort-實作) 與 [linux-list 版本的 quicksort](#程式碼與說明),我們可以發現兩者的運作流程是相同的 * 不同點在於後者的資料結構較複雜,相對地,其 list operation 也較為複雜,以下為後者的資料結構圖示: ```graphviz digraph { rankdir=LR; head_ptr [shape=box; label="head"]; node_Head [shape=none; label=< <TABLE BORDER="0"> <TR> <TD COLSPAN="2" BORDER="0" PORT="list_head"> <TABLE BGCOLOR="gray"> <TR><TD COLSPAN="2" BORDER="0" ALIGN="center">list_head</TD></TR> <TR> <TD COLSPAN="1" ALIGN="left" PORT="prev">prev</TD> <TD COLSPAN="1" ALIGN="right" PORT="next">next</TD> </TR> </TABLE> </TD> </TR> </TABLE>> ]; node_A [shape=none; label=< <TABLE BORDER="0"> <TR><TD COLSPAN="2" BORDER="0">listitem_A</TD></TR> <TR><TD> <TABLE BGCOLOR="bisque"> <TR><TD COLSPAN="2" BORDER="1" BGCOLOR="white">i</TD></TR> <TR> <TD COLSPAN="2" BORDER="0" PORT="list_head"> <TABLE BGCOLOR="gray"> <TR><TD COLSPAN="2" BORDER="0" ALIGN="center">list_head</TD></TR> <TR> <TD COLSPAN="1" ALIGN="left" PORT="prev">prev</TD> <TD COLSPAN="1" ALIGN="right" PORT="next">next</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD></TR> </TABLE>> ]; node_B [shape=none; label=< <TABLE BORDER="0"> <TR><TD COLSPAN="2" BORDER="0">listitem_B</TD></TR> <TR><TD> <TABLE BGCOLOR="bisque"> <TR><TD COLSPAN="2" BORDER="1" BGCOLOR="white">i</TD></TR> <TR> <TD COLSPAN="2" BORDER="0" PORT="list_head"> <TABLE BGCOLOR="gray"> <TR><TD COLSPAN="2" BORDER="0" ALIGN="center">list_head</TD></TR> <TR> <TD COLSPAN="1" ALIGN="left" PORT="prev">prev</TD> <TD COLSPAN="1" ALIGN="right" PORT="next">next</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD></TR> </TABLE>> ]; node_C [shape=none; label=< <TABLE BORDER="0"> <TR><TD COLSPAN="2" BORDER="0">listitem_C</TD></TR> <TR><TD> <TABLE BGCOLOR="bisque"> <TR><TD COLSPAN="2" BORDER="1" BGCOLOR="white">i</TD></TR> <TR> <TD COLSPAN="2" BORDER="0" PORT="list_head"> <TABLE BGCOLOR="gray"> <TR><TD COLSPAN="2" BORDER="0" ALIGN="center">list_head</TD></TR> <TR> <TD COLSPAN="1" ALIGN="left" PORT="prev">prev</TD> <TD COLSPAN="1" ALIGN="right" PORT="next">next</TD> </TR> </TABLE> </TD> </TR> </TABLE> </TD></TR> </TABLE>> ]; head_ptr -> node_Head:list_head; node_Head:next -> node_A:list_head; node_Head:prev -> node_C:list_head [constraint=false]; node_A:next -> node_B:list_head; node_A:prev -> node_Head:list_head [constraint=false]; node_B:next -> node_C:list_head; node_B:prev -> node_A:list_head; node_C:next -> node_Head:list_head [constraint=false]; node_C:prev -> node_B:list_head; } ``` ### 非遞迴方法實作 ```c= static bool isValid(struct list_head *head, struct list_head *L, struct list_head *R) { if (L == R->next) return false; while (1) { L = L->next; if (L == R) return true; if (L == head) return false; } } static int list_offset(struct list_head *head, struct list_head *begin, struct list_head *end) { int count = 0; while (begin != end) { if (begin == head) return -1; count++; begin = begin->next; } return count; } static void SWAP(struct list_head **a, struct list_head **b) { struct list_head *temp = *a; *a = *b; *b = temp; } static void list_qsort(struct list_head *head) { #define MAX_LEVELS 300 #define isValid() isValid(head, L, R) #define valueOf(node) list_entry(node, struct listitem, list)->i struct list_head *pivot, *begin[MAX_LEVELS], *end[MAX_LEVELS], *L, *R; int iter = 0; if (list_empty(head) || list_is_singular(head)) return; begin[0] = head->next; end[0] = head; while (iter >= 0) { L = begin[iter]; R = end[iter]->prev; if (isValid()) { pivot = L; while (isValid()) { while (valueOf(R) >= valueOf(pivot) && isValid()) R = R->prev; if (isValid()) { struct list_head *target = R; R = R->prev; list_move(target, L); L = L->next; } while (valueOf(L) <= valueOf(pivot) && isValid()) L = L->next; if (isValid()) { struct list_head *target = L; L = L->prev; list_move(target, R); } } begin[iter] = pivot->next; if (valueOf(L) >= valueOf(pivot)) { if (L != pivot) { begin[iter+1] = L; list_move_tail(pivot, L); } else { begin[iter+1] = L->next; } } else { begin[iter+1] = L->next; list_move(pivot, L); } end[iter+1] = end[iter]; end[iter++] = pivot; if (list_offset(head, begin[iter], end[iter]) > list_offset(head, begin[iter-1], end[iter-1])) { SWAP(&begin[iter], &begin[iter-1]); SWAP(&end[iter], &end[iter-1]); } } else { iter--; } } } ``` * 說明 * 此處與[前例](#非遞迴方法實作)先轉換成 array 的作法不同,而是使用給定的 list 結構與其提供的 list operation 進行實作 * 程式運作流程與 array 版本相同,但由於是以 linked list 進行實作,需要顧及 list 結構的維護,導致程式碼之可讀性與邏輯較混亂,此處應該可以進行加強<!-- TODO --> --- ## 高效率的 linked list 排序演算法 ### 說明 * 在 doubly linked list 的資料結構下,tree sort 為文章中所提及的最快速的排序演算法 * tree sort 演算法步驟如下: * 將串列建構成一個[二元搜尋樹](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9) * 對該二元搜尋樹進行中序走訪(inorder traversal),所得到的結果便會是由小到大排序好的結果 (中序走訪:按照左子樹->樹根->右子樹的順序進行走訪) ### 程式碼 ```c static inline void set_leaf(struct list_head *leaf) { leaf->prev = NULL; leaf->next = NULL; } static inline void inorder_traversal(struct list_head *root, struct list_head *head) { if (root != NULL) { inorder_traversal(root->prev, head); struct list_head *next = root->next; list_add_tail(root, head); inorder_traversal(next, head); } } static void list_tsort(struct list_head *head) { #define valueOf(node) list_entry(node, struct listitem, list)->i if (list_empty(head) || list_is_singular(head)) return; struct list_head *root = head->next; list_del(root); set_leaf(root); while (!list_empty(head)) { struct list_head *target = head->next; list_del(target); set_leaf(target); struct list_head *position = root; while (1) { if (valueOf(target) <= valueOf(position)) { if (position->prev != NULL) position = position->prev; else { position->prev = target; break; } } else { if (position->next != NULL) position = position->next; else { position->next = target; break; } } } } inorder_traversal(root, head); } ``` > 參考資料:[A Comparative Study of Linked List Sorting Algorithms](https://pages.mtu.edu/~shene/PUBLICATIONS/1996/3Conline.pdf) <!-- history: * 20210316 04:50 - first version -->