# 2020q3 Homework1 (quiz1) contributed by < `tsengsam` > [第一週測驗題](https://hackmd.io/@sysprog/sysprog2020-quiz1) --- ## 測驗 `1` 題目為一個沒有 circular 的 singly-linked list,其資料結構為 : ```cpp typedef struct __node { int value; struct __node *next; } node_t; ``` 以下將個別介紹函式: * add_entry() + 原本硬頸地想嘗試用單指標當傳入參數並且不回傳,最後竟然用了一天釐清一些基本觀念。 所以另外做了一個筆記避免再次犯錯 : [「為何一個指標實作 add_entry() 會出事」](https://hackmd.io/@shanvia/BJMFWP8EP) ```cpp= 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; } ``` **Q1. AA1 = ?** 若動態配置記憶體需確認是否有成功配置記憶體位址,故此題選 (a) 選項 `assert(new_node)`。 **Q2. AA2 = ?** 為了找到 linked list 最尾端的 `NULL`,前面透過 while 迴圈持續將 `indirect` assign 為指向下個entry位址的指標直到 `(*indirect)` 指向 `NULL`,離開迴圈後應該是將 `indirect` 指向的指標更新為 `new_node`,故此題選 (b) 選項 `*indirect = new_node`。 --- * find_entry() 這個函式若找不到該 entry 時會回傳 `NULL`,也就表示已經找到 list 的最後面了。 ```cpp= node_t *find_entry(node_t *head, int value) { node_t *current = head; for (; current && current->value != value; current = current->next) /*interate*/; return current } ``` --- * remove_entry() 使用者使用這個函式要小心,想要移除的 entry 需存在於 linked list 中,否則程式會崩潰。 ```cpp= void remove_entry(node_t **head, node_t *entry) { node_t **indirect = head; while ((*indirect) != entry) indirect = &(*indirect)->next; *indirect = (*indirect)->next; free(entry); } ``` --- * swap_pair() ```cpp= 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; } ``` **Q3. BB1 = ?** 因為 swap_pair 的 for 迴圈每次對兩個 entries 做交換,所以 BB1 的部分應是讓 node 一次前進兩個單位,而又因為 node 為指向指標的指標,故要先對 `node` 取值後才能取得下下個 entry,然後再取址並 assign 給 node。 故這題應為選項 (e) `node = &(*node)->next->next`。 **Q4. BB2 = ?** 假設交換 `A` `B` 兩節點,表示 `A->next` 要改成指向 `B->next`,而 `B->next` 會改指向 `A`。 故 BB2 即是 `*node = (*node)->next` 故可選 c #### 以下圖形化解釋 swap_pair 流程: (1) 第一次迴圈 line 4: ```graphviz digraph G { rankdir=LR; { A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->B->C->D->NULL; } { n [label="node",shape=plaintext] head [shape=box] n->head->A tmp [shape=plaintext] tmp->A } } ``` (2) line 5: ```graphviz digraph G { rankdir=LR; { node [shape=record] A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->B->C->D->NULL; } { node [shape=plaintext] n [label="node"] head [shape=box] tmp [label=tmp] tmp->A n->head head->B [color=red]; } } ``` (3) line 6: ```graphviz digraph G { rankdir=LR; { node [shape=record] A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->C [color=red] B->C->D->NULL; } { node [shape=plaintext] n [label="node"] head [shape=box] tmp [label=tmp] tmp->A n->head->B; } } ``` (4) line 7: ```graphviz digraph G { rankdir=LR; { node [shape=record] A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->C B->A [color=red] C->D->NULL; } { node [shape=plaintext] n [label="node"] head [shape=box] tmp [label=tmp] tmp->A n->head->B; } } ``` (5) 第二次迴圈 line 3: * node 改指向A next 指標的位址 ```graphviz digraph G { rankdir=LR; { node [shape=record] A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->C B->A C->D->NULL; } { node [shape=plaintext] n [label="node"] head [shape=box] tmp [label=tmp] tmp->A n->A:next [color=red] head->B; } } ``` (6) line 4: ```graphviz digraph G { rankdir=LR; { node [shape=record] A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->C B->A C->D->NULL; } { n [label="node"] node [shape=plaintext] head [shape=box] tmp [label=tmp] tmp->C [color=red] n->A:next head->B; } } ``` (7) line 5: ```graphviz digraph G { rankdir=LR; { node [shape=record] n [label="node"] n->A:next A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] A->D [color=red] B->A C->D->NULL; } { node [shape=plaintext] head [shape=box] tmp [label=tmp] tmp->C head->B; } } ``` (8) line 6: ```graphviz digraph G { rankdir=LR; { node [shape=record] n [label="node"] n->A:next A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] B->A A->D C->NULL [color=red] D->NULL; } { node [shape=plaintext] head [shape=box] tmp [label=tmp] tmp->C head->B; } } ``` (9) line 7: ```graphviz digraph G { rankdir=LR; { node [shape=record] n [label="node"] n->A:next A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] B->A A->D C->NULL D->C [color=red] } { node [shape=plaintext] head [shape=box] tmp [label=tmp] tmp->C head->B; } } ``` (10) line 3: * 此時便跳出迴圈,完成交換 ```graphviz digraph G { rankdir=LR; { node [shape=record] A [shape=record,label="{<data> A| <ref> next}"]; B [shape=record,label="{<data> B| <ref> next}"]; C [shape=record,label="{<data> C| <ref> next}"]; D [shape=record,label="{<data> D| <ref> next}"]; NULL [shape=plaintext] B->A A->D C->NULL D->C } { node [shape=plaintext] head [shape=box] head->B; } } ``` #### ※ 延伸問題 改寫 `swap_pair` 函式回傳型態為 `void` 因為這個函式是和交換有關,一時和 value 的交換有錯誤的聯想,以為本來就不用回傳,故只修改型態及 `return` : ```cpp= void swap_pair(node_t *h) { for (node_t **node = &h; *node && (*node)->next; node = &(*node)->next->next) { node_t *tmp = *node; *node = (*node)->next; tmp->next = (*node)->next; (*node)->next = tmp; } } ``` 這部份踩了一個雷,鑑於 [add_entry()](https://hackmd.io/@shanvia/BJMFWP8EP) 的錯誤讓我想到原因依舊是 `head` 指向的位址在第六行的 `*node` 會改變。 故還是得用指標的指標來進行改寫 : ```cpp= void swap_pair(node_t **pp) { for (node_t **node = pp; *node && (*node)->next; node = &(*node)->next->next) { node_t *tmp = *node; *node = (*node)->next; tmp->next = (*node)->next; (*node)->next = tmp; } } ``` --- * reverse() ```cpp= node_t *reverse(node_t *head) { node_t *cursor = NULL; while (head) { node_t *next = head->next; CCC; head = next; } return cursor; } ``` **Q5. CCC = ?** 這邊的作法是創造一逆轉 ~~(Tenet)~~ 的 linked list,因為此函式會更改 `head` 指向的位址且以指向 `node_t` 的指標作為參數,故有必要再將新的位址傳回給 `head` 。 `cursor` 指標代表的是目前已經反轉好的 linked list 的 head,透過每回合的 while 逐個反轉,故可將流程分為 4 步驟: ``` 1. 紀錄下回合要反轉的 node 2. 將這回合指標的 next 改指向 cursor 3. 把 cursor 指向這回合的指標,表示反轉好的節點又多一個 4. 最後取回下回合的 node 準備給下回合使用 對照程式碼可知 CCC 為步驟 2. `head->next = cursor` ,及步驟 3. `cursor = head` 。故本題為選項 (b) 。 #### ※ 延伸問題 改寫 `reverse` 函式回傳型態為 `void` 透過指向指標的指標可以來指向新的 head 位址,而不用再將新的位址回傳。其餘作法皆相同。 ```cpp= void reverse(node_t **pp) { node_t *cursor = NULL; while (*pp) { node_t *next = (*pp)->next; (*pp)->next = cursor; cursor = *pp; *pp = next; } *pp = cursor; } ``` #### ※ 延伸問題 將 `reverse` 用遞迴改寫 遞迴函式需要注意**回傳條件**以及**回傳什麼**,與 iterative 不同於反轉的起始點,遞迴從尾巴開始,迭代則是由頭;且要注意當遞迴進 `NULL` 前的節點時便要回傳。 ```cpp= node_t *rev_recursive(node_t *h) { if (!h || h->next == NULL) return h; node_t *cursor = rev_recursive(h->next); node_t *tmp = cursor; while (tmp->next) tmp = tmp->next; tmp->next = h; h->next = NULL; return cursor; } node_t *reverse(node_t *head) { return rev_recursive(head); } ``` --- * print_list() ```cpp= void print_list(node *head) { for(node_t *current = head; current; current = current->next) printf("%d ", current->value); printf("\n"); } ``` --- ## 針對 singly-linked list 實作 Fisher–Yates shuffle ### 簡介 * 是將一個有限長度的序列進行隨機置換的演算法,核心作法是從 random table 挑選一個數字,而這個數字介於 **1 到尚未被選中的元素數量**並且作為序列的 index number,對該 index 的元素作下面提到的兩種不同的 permutation * Original method ``` /* * 假設序列名字為 S, 長度為 N * 建立長度和 S 相同的空序列 S' */ 1. 從 random table 中挑選一個數字 k 作為當前序列的 index 2. 將 S 中從頭數來第 k 個數取出並放入 S'最尾端 3. 回到步驟1. 直到所有元素皆被取出並放入 S' 4. S' 即為一個 random permutaion ``` * Modern method 和 Original最大差別在於沒有新建一個序列,而是在原序列中把挑中的 k 和由左至右(或相反)的元素作交換,參見如下維基百科中由右至左的演算法 : ``` -- To shuffle an array a of n elements (indices 0..n-1): for i from n−1 downto 1 do j ← random integer such that 0 ≤ j ≤ i exchange a[j] and a[i] ``` ### 程式碼 * singly-linked list 每次取得任意節點的時間複雜度為 O(n) 又執行 O(n),故時間複雜度為 O(n^2^) * 空間複雜度原作法為 O(n),modern 則為 O(1) * 透過減少變數的宣告來降低記憶體使用量 ```cpp= void FYshuffle(node_t **h) { /*Calculate length of the linked list*/ int len = 0; node_t **pivot = h; for (; *pivot; pivot = &(*pivot)->next) ++len; /*Reuse pivot pointer*/ pivot = h; for (int i = len; i>1; i--) { /*Pick a random number k from 1 to i*/ int k = (rand() % i) + 1; /*Find the kth node*/ node_t **kNode = pivot; while (k>1) { kNode = &(*kNode)->next; k--; } /*Swap values of pivot node and kNode*/ int val = (*pivot)->value; (*pivot)->value = (*kNode)->value; (*kNode)->value = val; pivot = &(*pivot)->next; } } ``` ## 靜態記憶體分析 ```shell $ cppcheck -DA quie1.c --enable=all --supㄣress=missingIncludeSystem Checking quiz1.c ... Checking quiz1.c: A=1... ``` * `--enable=all`: 檢查所有項目 * `--suㄣpress=missingIncludeSystem`: 根據 [這篇討論](https://) 提到 cppcheck 不擅長找標準函式庫的 path ,因此可用這個命令使它忽視這個問題,否則會跳出第四行: ```shell= $ cppcheck -DA quie1.c --enable=all Checking quiz1.c ... Checking quiz1.c: A=1... nofile:0:0: information: Cppcheck cannot find all the include files (use --check-config for details) [missingIncludeSystem] ``` ###### tags: `linux2020`