# [2020q3 Homework1 (quiz1)](https://hackmd.io/@sysprog/rJ7WDWNVv) contributed by < `zhu849` > ## 題目解析 考慮一個單向 linked list,其結構定義為: ```c=4 typedef struct __node { int value; struct __node *next; } node_t; ``` ```graphviz digraph structs { rankdir = LR node[shape=record] struct0 [label= "{__node|{<value>value|<next>next}}"] struct1 [label= "{__node|{<value>value|<next>next}}"] struct0:next -> struct1 } ``` :::info 已知條件: 1. 不存在 circular (環狀結構) 2. link 是單向結構 ::: ### Sub Function 說明 1. `add_entry`: 新增節點,當 linked list 沒有內容時,必須由開發者更新指向開頭的指標。因此實際得到 reference,而非 copy 2. `remove_entry`: 移除指定節點,指向開頭的指標可能因此變更,所以需要用到 a pointer to a pointer (指標的指標) 3. `swap_pair`: 交換一對相鄰的節點,取自 LeetCode: Swap Nodes in Pairs,給定 1->2->3->4,則該回傳 2->1->4->3 4. `reverse`: 將給定的 linked list 其內節點予以反向,即 1->2->3->4,則該回傳 4->3->2->1 ### 題目程式碼 :::spoiler ```c= #include <stdio.h> #include <stdlib.h> typedef struct __node { int value; struct __node *next; } node_t; void add_entry(node_t **head, int new_value) { node_t **indirect = head; node_t *new_node = malloc(sizeof(node_t)); new_node->value = new_value; new_node->next = NULL; AA1; while (*indirect) indirect = &(*indirect)->next; AA2; } node_t *find_entry(node_t *head, int value) { node_t *current = head; for (; current && current->value != value; current = current->next) /* interate */; return current; } void remove_entry(node_t **head, node_t *entry) { node_t **indirect = head; while ((*indirect) != entry) indirect = &(*indirect)->next; *indirect = entry->next; free(entry); } node_t *swap_pair(node_t *head) { for (node_t **node = &head; *node && (*node)->next; BB1) { node_t *tmp = *node; BB2; tmp->next = (*node)->next; (*node)->next = tmp; } return head; } node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; CCC; head = next; } return cursor; } void print_list(node_t *head) { for (node_t *current = head; current; current = current->next) printf("%d ", current->value); printf("\n"); } int main(int argc, char const *argv[]) { node_t *head = NULL; print_list(head); add_entry(&head, 72); add_entry(&head, 101); add_entry(&head, 108); add_entry(&head, 109); add_entry(&head, 110); add_entry(&head, 111); print_list(head); node_t *entry = find_entry(head, 101); remove_entry(&head, entry); entry = find_entry(head, 111); remove_entry(&head, entry); print_list(head); /* swap pair. * See https://leetcode.com/problems/swap-nodes-in-pairs/ */ head = swap_pair(head); print_list(head); head = reverse(head); print_list(head); return 0; } ``` * 執行時記得要在開頭先加上 `#include <assert.h>` ,才能讓系統知道 assert function 在哪裡 ::: ### 各個 Function 分析 * 在了解程式功能前,我們需要先了解 pointer to pointer 的結構是像這樣,pointer 內存放的是記憶體的位址 ```graphviz digraph structs { node [shape=record]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="address"]; struct2 [label="address"]; struct3 [label="value"]; struct4 [shape=plaintext, label="pointer"]; struct5 [shape=plaintext, label="pointer"]; struct6 [shape=plaintext, label="variable"]; rankdir = LR struct1 -> struct2; struct2 -> struct3; struct3 -> NULL struct4 -> struct5 [style=invisible,dir=none] struct5 -> struct6 [style=invisible,dir=none] } ``` ### ==add_entry ( )== ```c=9 void add_entry(node_t **head, int new_value) { node_t **indirect = head; node_t *new_node = malloc(sizeof(node_t)); new_node->value = new_value; new_node->next = NULL; AA1; while (*indirect) indirect = &(*indirect)->next; AA2; } ``` * line 11:宣告一個 node_t 型態的指標的指標 indirect 指向傳入的 head 的位址 * line 13:向記憶體要求一塊 node_t 型態的空間,且利用 new_node 指標紀錄位址 * line 18:當 indirect 所指向的位址不是NULL,就持續做 line 19 內容 * line 19:取 indirect 所指到的 pointer varible 中 next 的值,再對這個值取它的位址 * 這邊需要注意的是根據 [operator 的優先順序規則](https://en.cppreference.com/w/c/language/operator_precedence):**->** 的優先順序較 **&** 還高,所以這行 code 也可視為 ```c=19 indirect = &((*indirect)->next); ``` ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL struct4:next -> NULL head -> struct1:next indirect -> struct1:next } ``` ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL struct4:next -> NULL head -> struct1:next indirect -> struct2:next } ``` ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL struct4:next -> NULL head -> struct1:next indirect -> struct3:next } ``` ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL struct4:next -> NULL head -> struct1:next indirect -> NULL } ``` 因為NULL的記憶體位址為0,所以跳出 while 迴圈 #### ==AA1:== * `(a)` `assert(new_node)` * `(b)` `*indirect = new_node` 如果選`b`:代表 indirect 指向的記憶體位址會先指向 newnode,乍看之下確實新增了一個 entry,但是這麼一做就會發現 node 2, 3 也隨著指標改變變成了孤兒,造成 [memory leak](https://en.wikipedia.org/wiki/Memory_leak) 的現象(沒有其他方法可以再找到 node 2,3 在記憶體的哪個位置)。 ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct4; struct2:next -> struct3:thenode; struct3:next -> NULL struct4:next -> NULL head -> struct1:next indirect -> struct1:next } ``` 如果選`a`:[C lib 提供的 assert](https://www.tutorialspoint.com/c_standard_library/c_macro_assert.htm)會去幫我們檢查newnode是否正確成立,若 `assert(expression)` 中 expression 為 false 代表 newnode 為 NULL,則當錯誤發生時系統會主動告知我們。 ==所以AA1選擇 (a)== #### ==AA2:== * `(a)` `assert(new_node)` * `(b)` `*indirect = new_node` 因為做完 line 18, 19 兩行後 node 之間的狀態會如下 ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL struct4:next -> NULL head -> struct1:next indirect -> NULL } ``` 如果選`a`:同 ==AA1== 解釋,因為 line 18, 19 並不會影響 newnode ,但是這樣 newnode 並沒有成功的被任何指標所記錄,在 `add_entry()` 結束後會造成 newnode 的點會產生 [ memory leak ](https://en.wikipedia.org/wiki/Memory_leak)現象。 如果選`b`:indirect 會將 node 3 的 next 指向 newnode ,順利加入新的 entry ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; indirect[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> newnode|{109|<next>next}}"]; rankdir = LR struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> struct4 struct4:next -> NULL head -> struct1:next indirect -> struct4 } ``` ==所以AA2選擇 (b)== ### ==swap_pair ( )== ```c=42 node_t *swap_pair(node_t *head) { for (node_t **node = &head; *node && (*node)->next; BB1) { node_t *tmp = *node; BB2; tmp->next = (*node)->next; (*node)->next = tmp; } return head; } ``` * 從 line 44 可以得知終止條件有兩種 1. ```*node == NULL``` 或是 2. ```(*node)->next == NULL``` * 其中第1種狀況屬於邊界狀況,僅有傳入head == NULL 時會判斷成立 * line 45:用一個 tmp pointer 指向 node 所指向的指標的位址 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct1:next -> struct2:thenode; struct2:next -> NULL "node"->head tmp->struct1 head -> struct1 } ``` #### ==BB2:== ```(a)``` ```node = (*node)->next``` ```(b)``` ```node = &(*node)->next``` ```(c)``` ```*node = (*node)->next``` ```(d)``` ```*node = &(*node)->next``` ```(e)``` ```*node = &((*node)->next)``` 如果選 ```a``` :無法達成交換,且程式根本無法 work,因為 node 是一個 pointer to pointer的結構,但指向的 node 2 型態並不是一個指向 __node 型態的 pointer,所以 line 47 無法執行。 line 46: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct1:next -> struct2:thenode; struct2:next -> NULL struct2:thenode -> "__node ???" "node"->struct2 tmp->struct1 head -> struct1 } ``` 如果選 ```b``` :會產生兩種狀況 1. node 1 和 node 3 產生 loop,這個程式會進入無限迴圈狀態 2. node 2 會變成孤兒,一樣會有 memory leak 的問題 line 46: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; pointer[shape=plaintext]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct1:next -> struct2:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL; "node"-> pointer -> struct2 tmp->struct1 head -> struct1 } ``` line 47: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; pointer[shape=plaintext]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct1:next -> struct3:thenode; struct2:next -> struct3:thenode; struct3:next -> NULL; "node"-> pointer -> struct3 tmp->struct1 head -> struct1 } ``` line 48: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; pointer[shape=plaintext]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct1:next -> struct3:thenode; struct2:next -> struct3:thenode; struct3:next -> struct1:thenode; "node"-> pointer -> struct3 tmp->struct1 head -> struct1 } ``` 如果選 ```c``` :可以正確的 swap 兩個元素的位置 line 46: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct1:next -> struct2:thenode; struct2:next -> NULL "node"->head tmp->struct1 head -> struct2 } ``` line 47: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct1:next -> NULL; struct2:next -> NULL "node"->head tmp->struct1 head -> struct2 } ``` line 48: ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct1:next -> NULL; struct2:next -> struct1 "node"->head tmp->struct1 head -> struct2 } ``` 如果選 ```d``` :因為讓 head 指向一個「指向 __node 的 pointer」,但是 head 的型態為 指向 __node 的 pointer,所以程式會產生錯誤無法 work。 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; tmp[shape=plaintext,fontcolor=blue]; "address of node 2"[shape=plaintext]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct1:next -> struct2:thenode; struct2:next -> NULL "node"->head tmp->struct1 head -> "address of node 2" -> struct2 } ``` 如果選 ```e``` :我們在上述有提到因為 operator 優先順序的關係,所以這個選項可以視為和 ```d``` 選項同義。 ==所以BB2選擇 \(c\)== #### ==BB1== ```(a)``` ```node = (*node)->next->next``` ```(b)``` ```*node = (*node)->next->next``` ```(c)``` ```*node = ((*node)->next)->next``` ```(d)``` ```*node = &((*node)->next)->next``` ```(e)``` ```node = &(*node)->next->next``` ```(f)``` ```*node = &(*node)->next->next``` * 每次交換完的結果會如下,如果想要繼續交換的話我們必須將 head 移動到下一個要交換的節點上 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct1:next -> struct3; struct2:next -> struct1; struct3:next -> NULL "node"->head head -> struct2 } ``` 如果選```a```:程式無法 work,因為 node 是一個 pointer to pointer的結構,但指向的 node 3 型態並不是一個指向 __node 型態的 pointer,所以進行第一次 swap 結束後就會無法執行。 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct1:next -> struct3; struct2:next -> struct1; struct3:next -> NULL struct3 -> "___node???" "node"->struct3 head -> struct2 } ``` 如果選```b```:因為我們如果直接挪動了 \*node 指標,就會造成 node 1, 2 變成孤兒。所以 swap 結束後回傳的 head 會之後的元素。 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct3; struct2:next -> struct1; struct3:next -> struct4; struct4:next -> NULL "node"->head head -> struct2 } ``` ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct4; struct2:next -> struct1; struct3:next -> NULL; struct4:next -> struct3 "node"->head head -> struct4 } ``` 如果選```c```:狀況和 ```b```同義,存在一樣的問題。 如果選```d```:因為讓 head 指向一個「指向 __node 的 pointer」,但是 head 的型態為指向 __node 的 pointer,所以程式會產生錯誤無法 work。 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; "address of node 3"[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct3; struct2:next -> struct1; struct3:next -> struct4; struct4:next -> NULL "node"->head head -> "address of node 3" -> struct3 } ``` 如果選```e```:head 仍然能正確的紀錄起始位置,透過 node 的變動去兩兩做調整可以完成 swap ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; "address of node 3"[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct3; struct2:next -> struct1; struct3:next -> struct4; struct4:next -> NULL "node"->"address of node 3" -> struct3 head ->struct2; } ``` ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; "node"[shape=plaintext,fontcolor=red]; NULL[shape=plaintext,fontcolor=black]; "address of node 4"[shape=plaintext,fontcolor=black]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct4; struct2:next -> struct1; struct3:next -> NULL; struct4:next -> struct3 "node"->"address of node 4" -> struct4 head ->struct2; } ``` 如果選```f```:同選項 ```d``` ,會有相同的問題 ==所以BB2選擇 \(e\)== ### ==reverse( )== ```c=53 node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; CCC; head = next; } return cursor; } ``` * line 57:宣告一個 next 指標指向 head->next ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct2; struct2:next -> struct3; struct3:next -> struct4; struct4:next -> NULL; cursor -> NULL head -> struct1 next -> struct2 } ``` #### ==CCC:== ```(a)``` ```cursor = head; head->next = cursor``` ```(b)``` ```head->next = cursor; cursor = head``` ```(c)``` ```cursor = head``` ```(d)``` ```head->next = cursor``` ```(e)``` ```head->next->next = cursor; cursor->next = head``` ```(f)``` ```cursor->next = head; head->next->next = cursor``` 如果選 ```a```:這樣會變成 node 都會產生一個 self-loop ,並沒有達到 reverse 的功能,且每個點在 ```reverse()```結束後都會有 memory leak 的問題。 ```graphviz digraph structs { node [shape=record]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct1 struct2:next -> struct2; struct3:next -> struct3; struct4:next -> NULL; head -> struct4 next -> struct4 cursor -> struct3 head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; } ``` 如果選 ```b```: 能夠正確的 reverse list,注意是回傳 cursor 不是 head ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> NULL; struct2:next -> struct3; struct3:next -> struct4; struct4:next -> NULL; cursor -> struct1 head -> struct2 next -> struct2 } ``` ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> NULL; struct2:next -> struct1; struct3:next -> struct2; struct4:next -> struct3; cursor -> struct4 head -> NULL next -> NULL } ``` 如果選 ```c```: 完全沒有動到原本的 list 結構,僅是單純的移動 pointer ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct2; struct2:next -> struct3; struct3:next -> struct4; struct4:next -> NULL; cursor -> struct4 head -> NULL next -> NULL } ``` 如果選 ```d```: 因為 cursor 都沒有改變,僅是移動 head 和 next 的話最後所有 node 都會指向 NULL ```graphviz digraph structs { node [shape=record]; head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> NULL; struct2:next -> NULL; struct3:next -> NULL; struct4:next -> NULL; cursor -> NULL head -> NULL next -> NULL } ``` 如果選 ```e```:cursor 指向 NULL , NULL中並沒有 next 的成員可以使用,所以程式無法運行 ```graphviz digraph structs { node [shape=record]; rankdir = LR head[shape=plaintext,fontcolor=darkgreen]; next[shape=plaintext,fontcolor=blue]; NULL[shape=plaintext,fontcolor=black]; cursor[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|{72|<next>next}}"]; struct2 [label="{<thenode> node 2|{101|<next>next}}"]; struct3 [label="{<thenode> node 3|{108|<next>next}}"]; struct4 [label="{<thenode> node 4|{109|<next>next}}"]; struct1:next -> struct2; struct2:next -> struct3; struct3:next -> NULL; struct4:next -> NULL; cursor -> NULL -> "next ???" head -> struct1 next -> struct2 } ``` 如果選 ```f```: 和 ```d```選項有相同的問題,NULL 中並沒有 next 可供使用,所以程式也無法運行 ==所以CCC選擇 \(b\)== ## 延伸問題 2 - 改寫 swap_pair( ) 與 reverse( ) :::info 函式 swap_pair 和 reverse 對於指標的操作方式顯然異於 add_entry 及 remove_entry,需要額外做 head = ... 的更新,請用指標的指標來改寫,並避免回傳指標; ::: ### ==new_swap_pair( )== 原始版本: ```c=42 node_t *swap_pair(node_t *head) { for (node_t **node = &head; *node && (*node)->next; node = &(*node)->next->next) { node_t *tmp = *node; *node = (*node)->next; tmp->next = (*node)->next; (*node)->next = tmp; } return head; } // call swap_pair() function head = swap_pair(head); ``` * 我們可以發現 head 傳進來後只被使用過一次,就是被 node 用來參考指向 head 的 address,考慮 head 和 node 可能可以達到相同的功能,將 node 的使用替代成 head 方向做改寫 修改版本: ```c= void swap_pair(node_t **head) { for (; *head && (*head)->next; head = &(*head)->next->next) { node_t *tmp = *head; *head = (*head)->next; tmp->next = (*head)->next; (*head)->next = tmp; } } // call new_swap_pair() function new_swap_pair(&head); ``` * 如此一來呼叫函式可以少利用一個 head 接 function 所傳回來的指標 * 在 `new_swap_pair()` function 中也省去了惱人的 node pointer,讓整體的可讀性更好 ### ==new_reverse( )== 原始版本: ```c=53 node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; head->next = cursor; cursor = head; head = next; } return cursor; } // call reverse() function head = reverse(head); ``` * 修改和 swap_pair() 的出發點相似,但是 function 內使用到的 cursor, next 皆沒辦法再省略 * line 62 的地方將回傳改成用 \*head 指向 cursor 達到相同的效果 ```c=53 void reverse_v2(node_t **head) { node_t *cursor = NULL; while (*head) { node_t *next = (*head)->next; (*head)->next = cursor; cursor = *head; *head = next; } *head = cursor; } // call new_reverse() function new_reverse(&head); ``` :::warning 自問自答:這樣的修改方法除了可讀性和封裝性較強,那對效能有好處嗎? // TODO ::: ## 延伸問題 3 - 利用遞迴改寫 reverse( ) :::info 以遞迴改寫上述的 reverse,注意,你可能因此需要建立新的函式,如 rev_recursive,隨後在 reverse 函式中呼叫 rev_recursive; ::: * 思考:要換成遞迴,代表某段程式具有重複性,我們可以觀察到 line 56 ~ line 61 (while迴圈內容)具有這種特性 版本一:將 while 拆解放入 rev_recursive( ) 做 ```c= node_t* rev_recursive(node_t *cursor, node_t *head){ if (!(head)) return cursor; node_t *next = head->next; head->next = cursor; cursor = head; head = next; return rev_recursive(cursor, head); } void new_reverse(node_t **head) { *head = rev_recursive(NULL, *head); } // call new_reverse()function new_reverse(&head); ``` * 用反射的寫法就是將 while 直接丟到 recursive function 裡面做,但是會發現這麼做沒得到任何好處,甚至程式碼變得更醜了 版本二:直接傳入 \*\*head 的方法讓 function 內部有更多資訊,且富有更好的靈活度 ```c= void rev_recursive(node_t *cursor, node_t **head){ if (!(*head)) { *head = cursor; return; } node_t *next = (*head)->next; (*head)->next = cursor; cursor = *head; *head = next; rev_recursive(cursor, head); } void new_reverse(node_t **head) { rev_recursive(NULL, head); } // call new_reverse()function new_reverse(&head); ``` * 本來想要想辦法節省 line 8, 9,經過一番嘗試後發現仍然沒辦法去除這兩行,但程式感覺還能再提升 版本三:利用多呼叫一個變數,減少 assign 和指標操作次數 ```c= void rev_recursive(node_t *cursor, node_t *now, node_t **head){ if (!now) { *head = cursor; return; } node_t *next = now->next; now->next = cursor; rev_recursive(now, next, head); } void new_reverse(node_t **head) { rev_recursive(NULL, *head, head); } // call new_reverse()function new_reverse(&head); ``` * 如上面所說,程式更簡短了,犧牲多輸入一個變數的空間換取兩次 assign 的效能 :::warning 自問自答:版本二和版本三的效能取捨是可以討論的 // TODO ::: ## 延伸問題 4 - 利用 singly-linked list 實作 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) :::info 針對 singly-linked list 的節點,實作 Fisher–Yates shuffle,你應該儘量降低記憶體的使用量; ::: ### Fisher–Yates shuffle 特性分析 * 一種在**有限**序列上產生**隨機排序**的演算法 * 每一種排列的機率相同,不會偏執某種排列順序 * 計算複雜度和輸入大小成等比例關係 * 若有 N 個 item 則需要 N-1 輪做處理 * 是一種 [In-place algorithm](https://en.wikipedia.org/wiki/In-place_algorithm) * 衍伸:Sattolo's algorithm:一種在長度n情況下產生亂序尋懷排列的演算法 * 傳統的 Fisher–Yates shuffle 演算法時間複雜度為 $\ O(n^2)$ ,改良過用於當代計算機中的演算法為 $\ O(n)$ * 傳統演算法會需要 $\ O(n^2)$ 的原因是每次選擇挑選出一個數,原序列皆需重新調整 ### Modern method * 假設 N = 10 * Initial state ```graphviz digraph structs { node [shape=record]; tail[shape=plaintext,fontcolor=black]; "random number"[shape=plaintext,fontcolor=black]; struct1 [label="{index|content}|{0|5}|{1|3}|{2|4}|{3|2}|{4|9}|{5|7}|{6|8}|{7|1}|{8|6}|{<0>9|0}"]; struct2 [label=""]; "random number" -> struct2; tail ->struct1:0; } ``` * round start - select a number from 0 to N - 1 ```graphviz digraph structs { node [shape=record]; tail[shape=plaintext,fontcolor=black]; pointer[shape=plaintext,fontcolor=black]; "random number"[shape=plaintext,fontcolor=blue]; struct1 [label="{index|content}|{0|5}|{1|3}|{2|4}|{<3>3|2}|{4|9}|{5|7}|{6|8}|{7|1}|{8|6}|{<0>9|0}"]; struct2 [label="3"]; "random number" -> struct2; tail -> struct1:0; pointer ->struct1:3 } ``` * swap pointer <-> tail ```graphviz digraph structs { node [shape=record]; tail[shape=plaintext,fontcolor=blue]; pointer[shape=plaintext,fontcolor=darkgreen]; "random number"[shape=plaintext,fontcolor=black]; struct1 [label="{index|content}|{0|5}|{1|3}|{2|4}|{<3>3|0}|{4|9}|{5|7}|{6|8}|{7|1}|{<8>8|6}|{<9>9|2}"]; struct2 [label="3"]; "random number" -> struct2; tail -> struct1:9; pointer ->struct1:3 } ``` * round over - tail move ```graphviz digraph structs { node [shape=record]; tail[shape=plaintext,fontcolor=blue]; "random number"[shape=plaintext,fontcolor=black]; struct1 [label="{index|content}|{0|5}|{1|3}|{2|4}|{<3>3|0}|{4|9}|{5|7}|{6|8}|{7|1}|{<8>8|6}|{<9>9|2}"]; struct2 [label="3"]; "random number" -> struct2; tail -> struct1:8; } ``` * after 9 rounds - select number sequence = [ 3, 5, 7, 0, 4, 4, 0, 1, 1 ] * final state ```graphviz digraph structs { node [shape=record]; tail[shape=plaintext,fontcolor=blue]; "random number"[shape=plaintext,fontcolor=black]; struct1 [label="{index|content}|{0|0}|{<1>1|4}|{2|3}|{<3>3|8}|{4|6}|{5|9}|{6|5}|{7|1}|{<8>8|7}|{<9>9|2}"]; struct2 [label="1"]; "random number" -> struct2; tail -> struct1:1; } ``` ### 程式實現 ```c= void FYshuffle(node_t **head) { srand((unsigned)time(NULL)); int randnum; int lens = 0; // number of items node_t *p = *head; node_t *nhead = NULL; node_t *prev; // count number of items while (p) { lens++; p = p->next; } // do shuffle while (lens - 1) { randnum = rand() % lens; // select number from 0 to (lens - 1) p = *head; prev = NULL; while (randnum) { prev = p; p = p->next; randnum--; } if (prev) prev->next = p->next; else *head = (*head)->next; p->next = nhead; nhead = p; lens--; } (*head)->next = nhead; } ``` * 雖然我們想像中它是做 swap 的動作,但是因為 linked-list 具有良好的修改特性,所以我們實際上是將選擇到的 node->next 指向新的指標 nhead ,再將 nhead 更新指向選擇的 node * line 11 - 14:單純計算 list 長度並記錄在 lens 中 * line 17 :判斷條件為 (lens - 1) ,因為我們僅需要做 N - 1 輪便可以知道結果,即使是做 N 輪結果仍相同 * line 19 - 20:初始化 p 和 prev 指標的位置 * line 21 - 25:將指標移動到 list 第 k 個位置(k = random nubmer) * line 26 - 29:prev 初始指向 NULL,所以需要判斷 random number = 0 的情況做處理 * 若 prev = NULL 代表 p 指向 \*head 所指向的位置, 則將 \*head 移動到下一格 ```graphviz digraph structs { node [shape=record]; rankdir = LR prev[shape=plaintext,fontcolor=darkgreen]; p[shape=plaintext,fontcolor=brown]; NULL[shape=plaintext,fontcolor=black]; "*head"[shape=plaintext,fontcolor=black]; head[shape=plaintext,fontcolor=blue]; nhead[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|72|<next>}"]; struct2 [label="{<thenode> node 2|101|<next>}"]; struct3 [label="{<thenode> node 3|108|<next>}"]; struct1:next ->struct2; struct2:next ->struct3; struct3:next -> NULL "*head" -> struct1 head -> "*head" p -> struct1 prev -> NULL nhead -> NULL } ``` ```graphviz digraph structs { node [shape=record]; rankdir = LR prev[shape=plaintext,fontcolor=darkgreen]; p[shape=plaintext,fontcolor=brown]; NULL[shape=plaintext,fontcolor=black]; "*head"[shape=plaintext,fontcolor=black]; head[shape=plaintext,fontcolor=blue]; nhead[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|72|<next>}"]; struct2 [label="{<thenode> node 2|101|<next>}"]; struct3 [label="{<thenode> node 3|108|<next>}"]; struct1:next ->struct2; struct2:next ->struct3; struct3:next -> NULL "*head" -> struct2 head -> "*head" p -> struct1 prev -> NULL nhead -> NULL } ``` * line 30:將 p 指標的 next 指向 nhead 的位置 ```graphviz digraph structs { node [shape=record]; rankdir = LR prev[shape=plaintext,fontcolor=darkgreen]; p[shape=plaintext,fontcolor=brown]; NULL[shape=plaintext,fontcolor=black]; "*head"[shape=plaintext,fontcolor=black]; head[shape=plaintext,fontcolor=blue]; nhead[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|72|<next>}"]; struct2 [label="{<thenode> node 2|101|<next>}"]; struct3 [label="{<thenode> node 3|108|<next>}"]; struct1:next -> NULL; struct2:next ->struct3; struct3:next -> NULL "*head" -> struct2 head -> "*head" p -> struct1 prev -> NULL nhead -> NULL } ``` * line 31:再將 nhead 的位置更新成 p 的位置,這樣可以保持 nhead 永遠指向最前端 ```graphviz digraph structs { node [shape=record]; rankdir = LR prev[shape=plaintext,fontcolor=darkgreen]; p[shape=plaintext,fontcolor=brown]; NULL[shape=plaintext,fontcolor=black]; "*head"[shape=plaintext,fontcolor=black]; head[shape=plaintext,fontcolor=blue]; nhead[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|72|<next>}"]; struct2 [label="{<thenode> node 2|101|<next>}"]; struct3 [label="{<thenode> node 3|108|<next>}"]; struct1:next -> NULL; struct2:next ->struct3; struct3:next -> NULL "*head" -> struct2 head -> "*head" p -> struct1 prev -> NULL nhead -> struct1 } ``` * line 34:做完 N - 1 輪後,\*head 所指向的 list 應該僅剩一個元素,將這個元素的 next 指向亂序排列的 list 的頭(nhead)就可以使 head 拿到完整的亂序排列了 ```graphviz digraph structs { node [shape=record]; rankdir = LR NULL[shape=plaintext,fontcolor=black]; "*head"[shape=plaintext,fontcolor=black]; head[shape=plaintext,fontcolor=blue]; nhead[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|72|<next>}"]; struct2 [label="{<thenode> node 2|101|<next>}"]; struct3 [label="{<thenode> node 3|108|<next>}"]; struct1:next ->NULL; struct2:next ->struct1; struct3:next -> NULL "*head" -> struct3 head -> "*head" nhead -> struct2 } ``` ```graphviz digraph structs { node [shape=record]; rankdir = LR NULL[shape=plaintext,fontcolor=black]; "*head"[shape=plaintext,fontcolor=black]; head[shape=plaintext,fontcolor=blue]; nhead[shape=plaintext,fontcolor=purple]; struct1 [label="{<thenode> node 1|72|<next>}"]; struct2 [label="{<thenode> node 2|101|<next>}"]; struct3 [label="{<thenode> node 3|108|<next>}"]; struct1:next ->NULL; struct2:next ->struct1; struct3:next -> NULL struct3:next -> struct2 [color="red"] "*head" -> struct3 head -> "*head" nhead -> struct2 } ```