# 2020q3 Homework1 (quiz1) contributed by <`Sisker1111`> ###### tags: `進階電腦系統理論與實作` [TOC] ## add_entry ```c=1 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; } ``` ### AA1 1. 第 3 行使用 pointer to pointer 的技巧存取 address of head. 2. 第 10~11 行是為了走到link-list的最後一個節點. 此處選擇(b)`*indirect = new_node`明顯不合理,new_node是準備要插入的新點,如果將`*indirect = new_node`的話,`(*indirect)->next`永遠都會是`NULL`,無法走訪 link-list. ### AA2 走訪到最後一個節點時,因 `*indirect == NULL`所以跳出 while,此時我們需要將`*indirect`指向要加入的新點,故選擇`*indirect = new_node`. ## swap_pair ```c=1 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; } ``` ### BB1 1. `swap_pair` 的功能為兩兩掉換,故for迴圈內條件是希望連跳兩個node. 2. 考試時這邊我原本選 `(b)` ` *node = (*node)->next->next`,但仔細想想此處只是要改變node指向的address而不是要修改 Link-list 的連接,故等號左邊要是`node`. 3. 因為 node 使用 pointer to pointer的技巧,此處要取 address of pointer,故答案選擇 `(e)` `node = &(*node)->next->next` ### BB2 1. 接著我們要開始對 node 做 swap 的工作,以下圖演示整個iteration 的第一步: ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red] rankdir=LR { rankdir = LR A[label=A] B[label=B] NULL1[label="...",shape=plaintext] } { rank="same"; n[shape=plaintext,label="node"] n->head->A } A->B->C->NULL1 } ``` * 第45行 `node_t *tmp = *node` ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red,label="head(tmp)"] rankdir=LR { rankdir = LR A[label=A] B[label=B] NULL1[label="...",shape=plaintext] } { rank="same"; n[shape=plaintext,label="node"] n->head->A } A->B->C->NULL1 } ``` * 接著,此處是把 `*node` 指到的地址換成下一個節點的地址。因此 `BB2` 會是 `*node = (*node)->next;`,注意因為這邊是交換節點的位址,因此 是把`head`的 address 變成 B 的 address,也就是讓 head 指向 B,此時 B 變成新的開頭。 ```graphviz digraph graphname{ node[shape=box] tmp[shape=plaintext] n[shape=plaintext,label="node"] m[shape=plaintext,label="head\n",fontcolor=red] rankdir=LR { rankdir = LR A[label=A] B[label=B] NULL1[label="...",shape=plaintext] } { rank="same"; tmp->A } { rank="same"; n->m->B } A->B->C->NULL1 } ``` * 然後把`tmp->next`指向`(*node)->next`,再把`*node->next`指回舊的 head,注意此時`*node`已經變成這個 link-list 新的 head,指標圖如下: ```graphviz digraph graphname{ node[shape=box] tmp[shape=plaintext] n[shape=plaintext,label="node"] m[shape=plaintext,label="head\n",fontcolor=red] rankdir=LR { rankdir = LR A[label=A] B[label=B] NULL[label="...",shape=plaintext] } { rank="same"; tmp->A } { rank="same"; n->m->B } B->A->C->NULL } ``` ## reverse ```c=1 node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; CCC; head = next; } return cursor; } ``` ### CCC 1. `cursor` 用來暫存每次 iterative 時舊的`head`. 2. `node_t *next = head->next`用來暫存新的 `head` . 3. 在更新`head`前,需先將 `head->next`指到舊的`head`,並且用`cursor`暫存舊的`head`,符合的選項指有`(e)` `head->next = cursor; cursor = head` ## 延伸問題 2 ### reverse 的 pointer to pointer 版本 ```c= void reverse(node_t **head) { node_t *cursor = NULL; while (*head) { node_t *next = (*head)->next; (*head)->next = cursor; cursor = *head; *head = next; } *head = cursor; } ``` 只要將傳入參數改成`&head`,function內的`head` 都換成 `*head` 即好,因為在function內直接設定`*head`了,因此不需要回傳值,最後一行`*head = cursor;`是為了避免最後`*head`被設為NULL. ### swap_pair 的 pointer to pointer 版本 ```c= void swap_pair(node_t **head) { node_t *tmp = NULL; for(; *head && (*head)->next; head = &(*head)->next->next ){ tmp = *head; *head = (*head)->next; tmp->next = (*head)->next; (*head)->next = tmp; } } ``` 因為直接使用 pointer to pointer 的技巧,不需要原來的`node_t **node = &head`,最後`head`會自然的指向了新的`head`,不需要用額外的程式碼來處理。 ## 延伸問題 3 - recursive reverse ```c= node_t *rev_reverse(node_t **head, node_t *node) { if (!node->next) { *head = node; return node; } rev_reverse(head, node->next); node->next->next = node; node->next = NULL ; return node; } void recursive_reverse(node_t **head) { if(*head && (*head)->next){ node_t *cursor = *head; rev_reverse(head, (*head)->next); cursor->next->next = cursor; cursor->next = NULL; } } ``` ### 程式解說 思考流程是這樣的: 1. 先判斷此link-list是否為NULL或是只有一個Element,如果不是以上兩種情況則進入遞迴. 2. 一直遞迴到link-list最後一個node,`*head = node;`會把 head 指向最後一個node,接著把最後一個return. 3. `node->next->next = node;`將此時`node->next`的pointer反向指回來,並把`node->next`設為`NULL`,同理,整個recursive做完後,也要將原本`head`的`next`指回原本的`head`. * 這個部分寫得並不好,為了讓 recursive 到最後時更新 head 的指向的 address,我在 recursive 內不斷傳入 head 這個 pointer 的 address,程式看起來並不美. ## 延伸問題 4 - Fisher-Yates Shuffle ```c= void shuffle(node_t **head){ int link_length=0; srand(time(NULL)); node_t *cursor, *old_head, **indirect; cursor = old_head = *head; for (; cursor ; cursor = cursor->next) link_length++; /* interate */ *head = NULL; while(link_length){ // link-list is non-empty indirect = &old_head; for(int i = rand()%link_length; i > 0; i--){ indirect = &(*indirect)->next; } cursor = *indirect; *indirect = (*indirect)->next; cursor->next = *head; *head = cursor; link_length--; } } ``` ### 程式解說 + 6: 取得 link-list 的長度 + 10~14:用`indirect`來走訪原本的link-list,找到隨機出來的節點 + 15: cursor 在前面用來走訪link-list計算長度,在這用來暫存節點 + 16~18: 將這個節點從舊 link-list remove 並新增到新 link-list 的head + 19: 舊 link-list 長度-1.