# 2020q3 Homework1 (quiz1) contributed by < `ZhuMon` > ###### tags: `sysprog2020`, `quiz` ## :memo: Table of Contents [TOC] --- ## :page_facing_up: 題目 :::spoiler 考慮一個單向 linked list,其結構定義為: ```c typedef struct __node { int value; struct __node *next; } node_t; ``` 已知不存在 circular (環狀結構),接下來將對該單向 linked list 進行下列操作: * `add_entry`: 新增節點,當 linked list 沒有內容時,必須由開發者更新指向開頭的指標。因此實際得到 reference,而非 copy * `remove_entry`: 移除指定節點,指向開頭的指標可能因此變更,所以需要用到 a pointer to a pointer (指標的指標) * `swap_pair`: 交換一對相鄰的節點,取自 LeetCode: Swap Nodes in Pairs,給定 `1->2->3->4`,則該回傳 `2->1->4->3` * `reverse`: 將給定的 linked list 其內節點予以反向,即 `1->2->3->4`,則該回傳 `4->3->2->1` 對應的程式碼如下: ```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; } ``` 參考執行輸出: (第 1 行是換行符號) ```c= 72 101 108 109 110 111 72 108 109 110 108 72 110 109 109 110 72 108 ``` 請補完程式碼。 ::: --- ## 程式運作原理 以 `add_entry()`, `find_entry()`, `remove_entry()`, `swap_pair()`, `reverse()`, `print_list()` 等六個 function 組成 ---- ### `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; assert(new_node); while (*indirect) indirect = &(*indirect)->next; *indirect = new_node; } ``` * 目的: 在 `*head` 所指向 linked list 的尾端插入一個 value 為 `new_value` 的 node * 流程: 使用 pointer to pointer (`**indirect`) 來找尋 linked list (`**head`) 的尾端,當 `*indirect` 到達尾端(`*indirect == NULL`)後,將新的 node (`new_node`) 插入linked list * 詳細說明: :::info 以三個 column 分別代表某一變數的 address, name, value ```graphviz digraph { node [shape=record]; rankdir=LR; ex [label="{address|name|value}"] } ``` ::: 在呼叫 `add_entry()` 之前,可以看到 `main()` 先定義了一個指標 `*head`,並且沒有分配記憶體。 接著呼叫 `print_list(head)`,將 `head` 所指向的 linked list 印出來,由於目前`head` 為 NULL 因此只會印出一個換行符號。(稍後介紹 `print_list`) 然後呼叫 `add_entry(&head, 72)`,將 72 作為第一個 node 的值插入 linked list,並且藉由 pointer to pointer 的技巧,傳入 `&head` 直接在 `add_entry()` 中修改 `main()` 的 `head` 所指向的空間 ```c=71 int main(int argc, char const *argv[]) { node_t *head = NULL; print_list(head); add_entry(&head, 72); ``` | Frame |Address | Parameter name | Value | | --- | ----- | -------- | -------- | | main |0x7fffffffe2e8 | head | 0x0 | ```graphviz digraph add_entry{ rankdir=LR; node [shape=record]; head [label="{ <addr> 0x7fffffffe2e8|(main)head|0x0}"]; } ``` --- ```c=9 void add_entry(node_t **head, int new_value) { node_t **indirect = head; ``` 進入 `add_entry` 後,將 `indirect` 存放原先 `head` 的 address | Frame | Address | Parameter name | Value | | ---- | -------- | -------- | -------- | | main | 0x7fffffffe2e8 | head | 0x0 | | add_entry | 0x7fffffffe2a8 | head | 0x7fffffffe2e8 | | add_entry | 0x7fffffffe2b0 | indirect | 0x7fffffffe2e8 | ```graphviz digraph add_entry{ rankdir=LR; node [shape=record]; head [label="{ <addr> 0x7fffffffe2e8|<data> (main)head| <ref> 0x0}"]; now_head [label="{ <addr> 0x7fffffffe2a8|<data> (add_entry)head| <ref> }"]; indirect [label="{<addr> 0x7fffffffe2b0| <data> indirect| <ref> }"]; now_head:ref -> head:addr; indirect:ref -> head:addr; } ``` --- ```c=13 node_t *new_node = malloc(sizeof(node_t)); new_node->value = new_value; new_node->next = NULL; ``` 接著建立一個新的 node (`new_node`),並且分配記憶體 | Frame | Address | Parameter name | Value | | ---- | -------- | -------- | -------- | | main | 0x7fffffffe2e8 | head | 0x0 | | add_entry | 0x7fffffffe2a8 | head | 0x7fffffffe2e8 | | add_entry | 0x7fffffffe2b0 | indirect | 0x7fffffffe2e8 | | add_entry | 0x7fffffffe2b8 | new_node | 0x555555757670 | | add_entry | 0x555555757670 | *new_node | {value = 72, next = 0x0} | ```graphviz digraph add_entry{ rankdir=LR; node [shape=record]; now_head [label="{ <addr> 0x7fffffffe2a8|<data> (add_entry)head| <ref> }"]; indirect [label="{<addr> 0x7fffffffe2b0| <data> indirect| <ref> }"]; head [label="{ <addr> 0x7fffffffe2e8|<data> (main)head| <ref> 0x0}"]; new_node [label="{ <addr> 0x7fffffffe2b8 | <data> new_node| <ref> }"]; node_value [label="{ <addr> 0x555555757670 | <data> \{value = 72, next = 0x0\}}"] now_head:ref -> head:addr; indirect:ref -> head:addr; new_node:ref -> node_value; } ``` --- ```c=17 assert(new_node); while (*indirect) indirect = &(*indirect)->next; *indirect = new_node; } ``` `assert` 確保 `new_node` 正確分配記憶體(也可以將 `assert` 移至 `malloc` 後) 由於是建立第一個 node,所以會直接跳到第 21 行,將 `indirect` 所存放的 value 作為 adrress,更改該 address 存放的 value,將該 value 改為 `new_node` 的 value | Frame | Address | Parameter name | Value | | ---- | -------- | -------- | -------- | | main | 0x7fffffffe2e8 | head | 0x555555757670 | | add_entry | 0x7fffffffe2a8 | head | 0x7fffffffe2e8 | | add_entry | 0x7fffffffe2b0 | indirect | 0x7fffffffe2e8 | | add_entry | 0x7fffffffe2e8 | *indirect | 0x555555757670| | add_entry | 0x7fffffffe2b8 | new_node | 0x555555757670 | | add_entry | 0x555555757670 | *new_node | {value = 72, next = 0x0} | 由於 address 太長,會導致圖變小,因此將 address 省略前半段 ```graphviz digraph add_entry{ rankdir=LR; node [shape=record]; now_head [label="{ <addr> e2a8|<data> (add_entry)head| <ref> }"]; indirect [label="{<addr> e2b0| <data> indirect| <ref> }"]; head [label="{ <addr> e2e8|<data> (main)head| <ref> }"]; new_node [label="{ <addr> e2b8 | <data> new_node| <ref> }"]; node_value [label="{ <addr> 7670 | <data> \{value = 72, next = 0x0\}}"] now_head:ref -> head:addr; indirect:ref -> head:addr; new_node:ref -> node_value:addr; head:ref->node_value:addr; } ``` --- ### `find_entry()` ```c=23 node_t *find_entry(node_t *head, int value) { node_t *current = head; for (; current && current->value != value; current = current->next) /* interate */; return current; } ``` 從傳入的 linked list node (`*head`),以 for-loop 找到在該 node 後,傳回存放的值為 `value` 的 node,用於刪除某一個 node --- ### `remove_entry()` ```c=31 void remove_entry(node_t **head, node_t *entry) { node_t **indirect = head; while ((*indirect) != entry) indirect = &(*indirect)->next; *indirect = entry->next; free(entry); } ``` * 目的:在 linked list (`**head`) 中,找到 node (`*entry`),並且刪除 * 方法:因為需要更動的 pointer,只有指向 `entry` 的 pointer,因此以 `**indirect` 存放該 pointer 的 address,最後只要藉由將該 pointer 指向 `entry->next`,便沒有其他 pointer 指向該處,因此便可以優雅地達到目的 * 實例: :::info * 由於不管是變數或指標都是由 `address` 和 `value` 所組成,因此以兩個 column 分別代表 `address` 和 `value` * 以下用箭頭連起來的兩個格子內容相同,並且格子左邊代表 address,右邊代表 value 若有第二個 row 則代表存放 next 的資訊 ```graphviz digraph b{ node [shape=record]; rankdir=UD; ptr [label="{addr}|{value}"] ex [label="{addr|next_addr}|{value|next}"]; } ``` ::: ```c=77 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); ``` 此處先執行到 86 行,接著將 `head` 的 address,與要刪除的 node 的 address 作為參數,一起傳入`remove_entry()` ```graphviz digraph a{ node [shape=record]; rankdir=LR entry [label="{<addr>(entry)|<ref>}"] head [label="{<addr>(main_head)|}"]; A [label="{{<addr>|<next_addr>}|{72|<ref>}}"]; B [label="{{<addr>|<next_addr>}|{101|<ref>}}"]; C [label="{{<addr>|<next_addr>}|{108|<ref>}}"]; D [label="{{<addr>|<next_addr>}|{109|<ref>}}"]; E [label="{{<addr>|<next_addr>}|{110|<ref>}}"]; F [label="{{<addr>|<next_addr>}|{111|<ref>}}"]; head->A:addr; A:ref->B:addr; entry:ref->B:addr; B:ref->C:addr; C:ref->D:addr; D:ref->E:addr; E:ref->F:addr; /*head [label="{<addr>(head)|<data>}"]; head:data->A:addr; entry [label="{<addr>(entry)|<data>}"]; entry:data->B:addr;*/ } ``` --- 傳入的 `head` 的 value 是 `main` 的 `head` 的 address 此處讓 `indirect` 存放 `head` 存放的 value ```c=31 void remove_entry(node_t **head, node_t *entry) { node_t **indirect = head; ``` ```graphviz digraph a{ node [shape=record]; rankdir=LR A [label="{{<addr>|<next_addr>}|{72|<ref>}}"]; B [label="{{<addr>|<next_addr>}|{101|<ref>}}"]; C [label="{{<addr>|<next_addr>}|{108|<ref>}}"]; D [label="{{<addr>|<next_addr>}|{109|<ref>}}"]; E [label="{{<addr>|<next_addr>}|{110|<ref>}}"]; F [label="{{<addr>|<next_addr>}|{111|<ref>}}"]; A:ref->B:addr; B:ref->C:addr; C:ref->D:addr; D:ref->E:addr; E:ref->F:addr; now_head [label="{<addr>(head)|<data>}"] head [label="{<addr>(main_head)|<data>}"]; now_head:data->head:addr; head:data->A:addr; indirect [label="{<addr>(indirect)|<data>}"]; indirect:data->head:addr; entry [label="{<addr>(entry)|<data>}"]; entry:data->B:addr; } ``` --- 若是 `(*indirect) != entry` 便會進入 while,並且讓 `indirect` 的值為 `*indirect` 所指向的 object 的 `next` address,接著因為 `*indirect` 的值是否與 entry 的值相同,所以跳出 while ```c=35 while ((*indirect) != entry) indirect = &(*indirect)->next; ``` ```graphviz digraph a{ node [shape=record]; rankdir=LR A [label="{{<addr>|<next_addr>}|{72|<ref>}}"]; B [label="{{<addr>|<next_addr>}|{101|<ref>}}"]; C [label="{{<addr>|<next_addr>}|{108|<ref>}}"]; D [label="{{<addr>|<next_addr>}|{109|<ref>}}"]; E [label="{{<addr>|<next_addr>}|{110|<ref>}}"]; F [label="{{<addr>|<next_addr>}|{111|<ref>}}"]; A:ref->B:addr; B:ref->C:addr; C:ref->D:addr; D:ref->E:addr; E:ref->F:addr; main_head [label="{<addr>(main_head)|<data>}"]; main_head->A:addr; head [label="{<addr>(head)|<data>}"]; head:data->main_head:addr; indirect [label="{<addr>(indirect)|<data>}"]; indirect:data->A:next_addr; entry [label="{<addr>(entry)|<data>}"]; entry:data->B:addr; } ``` --- 讓 `*indirect` 所存放的值換為 `entry->next`,如此便可以達到刪除的目的 ```c=38 *indirect = entry->next; ``` ```graphviz digraph a{ node [shape=record]; rankdir=LR A [label="{{<addr>|<next_addr>}|{72|<ref>}}"]; B [label="{{<addr>|<next_addr>}|{101|<ref>}}"]; C [label="{{<addr>|<next_addr>}|{108|<ref>}}"]; D [label="{{<addr>|<next_addr>}|{109|<ref>}}"]; E [label="{{<addr>|<next_addr>}|{110|<ref>}}"]; F [label="{{<addr>|<next_addr>}|{111|<ref>}}"]; A:ref->C:addr [color=red]; B:ref->C:addr; C:ref->D:addr; D:ref->E:addr; E:ref->F:addr; main_head [label="{<addr>(main_head)|<data>}"]; main_head->A:addr; head [label="{<addr>(head)|<data>}"]; head:data->main_head:addr; indirect [label="{<addr>(indirect)|<data>}"]; indirect:data->A:next_addr; entry [label="{<addr>(entry)|<data>}"]; entry:data->B:addr; } ``` --- 將刪除的 node 的記憶體釋放,由於只是釋放 `entry` 所指向的記憶體空間,因此存放 pointer `entry` 的空間並沒有釋放,不過在這個 function 結束後,就會自動釋放 ```c=39 free(entry); ``` ```graphviz digraph a{ node [shape=record]; rankdir=LR A [label="{{<addr>|<next_addr>}|{72|<ref>}}"]; //B [label="{{<addr>|<next_addr>}|{101|<ref>}}"]; C [label="{{<addr>|<next_addr>}|{108|<ref>}}"]; D [label="{{<addr>|<next_addr>}|{109|<ref>}}"]; E [label="{{<addr>|<next_addr>}|{110|<ref>}}"]; F [label="{{<addr>|<next_addr>}|{111|<ref>}}"]; A:ref->C:addr; //B:ref->C:addr; C:ref->D:addr; D:ref->E:addr; E:ref->F:addr; main_head [label="{<addr>(main_head)|<data>}"]; main_head->A:addr; head [label="{<addr>(head)|<data>}"]; head:data->main_head:addr; indirect [label="{<addr>(indirect)|<data>}"]; indirect:data->A:next_addr; entry [label="{<addr>(entry)|<data>}"]; //entry:data->B:addr; } ``` --- ### `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; } ``` :::info 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` BB2 = ? * `(a)` `node = (*node)->next` * `(b)` `node = &(*node)->next` * `(c)` `*node = (*node)->next` * `(d)` `*node = &(*node)->next` * `(e)` `*node = &((*node)->next)` ::: * 目的:交換一對相鄰的節點,取自 LeetCode: Swap Nodes in Pairs,給定 1->2->3->4,則該回傳 2->1->4->3 * 方法 ```graphviz digraph swap{ node [shape="record"] rankdir=LR; A [label="{{<addr>|<next_addr>}|{72|<ref>}}"]; //B [label="{{<addr>|<next_addr>}|{101|<ref>}}"]; C [label="{{<addr>|<next_addr>}|{108|<ref>}}"]; D [label="{{<addr>|<next_addr>}|{109|<ref>}}"]; E [label="{{<addr>|<next_addr>}|{110|<ref>}}"]; //F [label="{{<addr>|<next_addr>}|{111|<ref>}}"]; A:ref->C:addr; //B:ref->C:addr; C:ref->D:addr; D:ref->E:addr; //E:ref->F:addr; main_head [label="{<addr>(main_head)|<data>}"]; main_head->A:addr; //head [label="{<addr>(head)|<data>}"]; //head:data->main_head:addr; //indirect [label="{<addr>(indirect)|<data>}"]; //indirect:data->A:next_addr; //entry [label="{<addr>(entry)|<data>}"]; } ```