# 2020q3 Homework1 (quiz1) contributed by <`howish`> ## Quiz 1 [原問題網址](https://hackmd.io/@sysprog/sysprog2020-quiz1) [Github](https://github.com/howish/NCKU_Embedded2020Fall/blob/master/quiz/week1/list.c) ## Part 1 > 解釋上述程式運作原理,搭配 Graphviz,比照 Linked List 練習題 在 HackMD 筆記上視覺化展現; ### `add_entry` ```c= void add_entry(node_t **head, int new_value) { node_t **indirect = head; node_t *new_node = malloc(sizeof(node_t)); assert(new_node); // AA1; (應該在的位置) new_node->value = new_value; new_node->next = NULL; //AA1; (題目上的位置) while (*indirect) indirect = &(*indirect)->next; *indirect = new_node; //AA2 } ``` AA1: 在 [Linux manual](https://linux.die.net/man/3/assert) 中,`assert` 的用法是吃一個 expression ,根據它是否非零來判斷通過與否,在這邊等同於檢查 new_node 是不是NULL值。 所以這邊題目寫錯行了,這個assert要寫在第六行才符合邏輯。 AA2: 把新產生的點加在整個串列的尾端。 ### `swap_pair` ```c= node_t *swap_pair(node_t *head) { for (node_t **node = &head; *node && (*node)->next; node = &(*node)->next->next //BB1 ) { node_t *tmp = *node; *node = (*node)->next; //BB2 tmp->next = (*node)->next; (*node)->next = tmp; } return head; } ``` 這個函式的寫法使用到指標的指標,以避免額外處理 head 指向的節點。 一個串列的表示法如下 ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0, s1; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; head -> s0; s0:next -> s1; s1:next -> nuls; } ``` #### for 迴圈初始化 ```c node_t **node = &head ``` 迴圈的初始化用一個指標的指標存取 head 這個變數的指標。 ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; pp -> head; head -> s0; s0:next -> s1; s1:next -> nuls; } ``` #### for 條件式 ```c *node && (*node)->next ``` 兩個要交換的節點都要存在(不是 `NULL` ) 才會繼續之後的處理,以下兩個情況都會結束迴圈: ```graphviz digraph g1 { rankdir = LR pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nulp[shape=none, label= "NULL"]; pp -> head; head -> nulp; } ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nulp[shape=none, label= "NULL"]; pp -> head; head -> s0; s0:next -> nulp; } ``` #### for 迴圈內處理 以下逐行圖解串列結構的改變: ```c node_t *tmp = *node; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; pp[shape=none, label= "node", fontcolor="red"]; p1[shape=none, label= "tmp", fontcolor="blue"]; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; head -> s0; pp -> head; p1 -> s0; s0:next -> s1; s1:next -> nuls; } ``` ```c *node = (*node)->next; //BB2 ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; p1[shape=none, label= "tmp", fontcolor="blue"]; pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; p1 -> s0; pp -> head; head -> s1; s0:next -> s1; s1:next -> nuls; } ``` ```c tmp->next = (*node)->next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; p1[shape=none, label= "tmp", fontcolor="blue"]; pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; p1 -> s0; pp -> head; head -> s1; s0:next -> nuls; s1:next -> nuls; } ``` ```c (*node)->next = tmp; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; p1[shape=none, label= "tmp", fontcolor="blue"]; pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; p1 -> s0; pp -> head; head -> s1; s0:next -> nuls; s1:next -> s0; } ``` #### for 迴圈的更新部分 ```c node = &(*node)->next->next //BB1 ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; pp[shape=none, label= "node", fontcolor="red"]; head[shape=none, label= "head"]; nuls[shape=none, label= "..."]; pp -> s0:next; head -> s1; s0:next -> nuls; s1:next -> s0; } ``` #### 結論 可以看到使用指標的指標後,head指向的節點會自動被更新,不用特別指定。整體的程式碼會更為乾淨,成為[較有品味的程式碼](https://www.ted.com/talks/linus_torvalds_the_mind_behind_linux/transcript?language=en)。 ### `reverse` ```c= node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; head->next = cursor, cursor = head; // CCC head = next; } return cursor; } ``` 這邊 CCC 的部分是 reverse linked list 的反轉,以下表示逐行執行下串列的結構變化: ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p2[shape=none, label= "head", fontcolor="red"]; nuls[shape=none, label= "NULL"]; p2 -> s0; s0:next -> s1; s1:next -> s2; s2:next -> nuls; } ``` ```c node_t *cursor = NULL; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> nulp; p2 -> s0; s0:next -> s1; s1:next -> s2; s2:next -> nuls; } ``` ```c node_t *next = head->next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> nulp; p2 -> s0; p3 -> s1; s0:next -> s1; s1:next -> s2; s2:next -> nuls; } ``` ```c head->next = cursor, ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> nulp; p2 -> s0; p3 -> s1; s0:next -> nulp; s1:next -> s2; s2:next -> nuls; } ``` ```c cursor = head; // CCC ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s0; p2 -> s0; p3 -> s1; s0:next -> nulp; s1:next -> s2; s2:next -> nuls; } ``` ```c head = next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s0; p2 -> s1; p3 -> s1; s0:next -> nulp; s1:next -> s2; s2:next -> nuls; } ``` ```c node_t *next = head->next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s0; p2 -> s1; p3 -> s2; s0:next -> nulp; s1:next -> s2; s2:next -> nuls; } ``` ```c head->next = cursor, ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s0; p2 -> s1; p3 -> s2; s0:next -> nulp; s1:next -> s0; s2:next -> nuls; } ``` ```c cursor = head; // CCC ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s1; p2 -> s1; p3 -> s2; s0:next -> nulp; s1:next -> s0; s2:next -> nuls; } ``` ```c head = next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s1; p2 -> s2; p3 -> s2; s0:next -> nulp; s1:next -> s0; s2:next -> nuls; } ``` ```c node_t *next = head->next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s1; p2 -> s2; p3 -> nuls; s0:next -> nulp; s1:next -> s0; s2:next -> nuls; } ``` ```c head->next = cursor, ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s1; p2 -> s2; p3 -> nuls; s0:next -> nulp; s1:next -> s0; s2:next -> s1; } ``` ```c cursor = head; // CCC ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s2; p2 -> s2; p3 -> nuls; s0:next -> nulp; s1:next -> s0; s2:next -> s1; } ``` ```c head = next; ``` ```graphviz digraph g1 { rankdir = LR node[shape=record, label= "{__node|value|<next>next}"]; s0[color=indigo, fontcolor=indigo]; s1[color=brown, fontcolor=brown]; s2; p1[shape=none, label= "cursor", fontcolor="red"]; p2[shape=none, label= "head", fontcolor="red"]; p3[shape=none, label= "next", fontcolor="blue"]; nuls[shape=none, label= "NULL"]; nulp[shape=none, label= "NULL"]; p1 -> s2; p2 -> nuls; p3 -> nuls; s0:next -> nulp; s1:next -> s0; s2:next -> s1; } ``` 此時 head 為空指標,所以會跳出迴圈 ```c while (head) { ... } ``` 然後輸出的 cursor 指標正好就是反轉後的串列新的 head 指標 ```c return cursor; ``` ## Part 2 > 函式 swap_pair 和 reverse 對於指標的操作方式顯然異於 add_entry 及 remove_entry,需要額外做 head = ... 的更新,請用指標的指標來改寫,並避免回傳指標 ### 改寫 `swap_pair` ```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; } } ``` ### 改寫 `reverse` ```c= void new_reverse(node_t **head) { node_t *cursor = NULL; while (*head) { node_t *next = (*head)->next; (*head)->next = cursor, cursor = (*head); *head = next; } *head = cursor; } ``` 這兩個改寫方法減少了變數的使用,同時簡化了函式使用的方法。可以說是一舉雙得。 ## Part 3 >以遞迴改寫上述的 reverse,注意,你可能因此需要建立新的函式,如 rev_recursive,隨後在 reverse 函式中呼叫 rev_recursive; ```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 rec_reverse(node_t **head) { rev_recursive(NULL, head); } ``` ### Part 4 > 針對 singly-linked list 的節點,實作 Fisher–Yates shuffle,你應該儘量降低記憶體的使用量; #### Fisher–Yates shuffle簡介: 剩下 n 個的時候,先抽出 0~n-1的 其中一個數字 m ,然後從左邊養右邊數到第 m 個標的,將它劃掉,並且放到新產生的序列中。 #### 實作 Linked-list 的結構很適合此演算法的實作,所以就照著原來演算法的概念去實作就好。需要注意的是這邊與前幾個練習一樣可以使用*指標的指標*的技巧,可以使整個流程簡化很多。 ```c=110 void fisher_yates_shuffle(node_t **head) { // Compute length int num = 0; for (node_t *itr = *head; itr; num++, itr=itr->next) /*iterate*/; srand(time(NULL)); node_t *oldhead = *head; for (; num; num--) { // Pick the sampled node node_t **picker = &oldhead; for (int pos = rand() % num; pos; pos--) picker = &(*picker)->next; // Append picked node to result node *head = *picker; head = &(*head)->next; // Remove picked node from old list *picker = (*picker)->next; } } ```