# [2020q3](http://wiki.csie.ncku.edu.tw/sysprog/schedule) 第 1 週測驗題
###### tags: `sysprog2020`
:::info
目的: 檢驗學員對 **[linked list](https://hackmd.io/@sysprog/c-linked-list)** 的認知
:::
==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSf2ujBrZ87K_cN-iFEziq2FR44kt8XlCkB2x3aZx7NehUDtNg/viewform?usp=sf_link)==
### 測驗 `1`
考慮一個單向 linked list,其結構定義為:
```cpp
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](https://leetcode.com/problems/swap-nodes-in-pairs/),給定 `1->2->3->4`,則該回傳 `2->1->4->3`
* `reverse`: 將給定的 linked list 其內節點予以反向,即 `1->2->3->4`,則該回傳 `4->3->2->1`
:::info
:information_source: "delete" 和 "remove" 看似都是「刪去某物」,在 Linux 核心的 [include/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 中,這二個動作並存,但實際行為卻不同。[Difference between "delete" and "remove"](https://english.stackexchange.com/questions/52508/difference-between-delete-and-remove) 其中的解釋可知:
* "remove" 這動作對於 linked list 來說,是斷開節點和節點的關聯,也就是說原本若是 A $\to$ B $\to$ C,在 `remove(B)` 操作完成後,就會變成 A $\to$ C 的節點關係。特別注意到 B 這個節點其實還存在於記憶體中,只是我們無法從節點間的關係看出來。於是 "remove" 可解讀為 "take away" (將某物帶走)
* "delete" 則有 "erase" 的涵義,若用於 linked list,則指某個節點將被「抹除」,對應的記憶體就會有變化
:::
對應的程式碼如下:
```cpp=
#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 行是換行符號)
```=
72 101 108 109 110 111
72 108 109 110
108 72 110 109
109 110 72 108
```
請補完程式碼。
==作答區==
AA1 = ?
* `(a)` `assert(new_node)`
* `(b)` `*indirect = new_node`
AA2 = ?
* `(a)` `assert(new_node)`
* `(b)` `*indirect = new_node`
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)`
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`
:::success
延伸問題:
1. 解釋上述程式運作原理,搭配 [Graphviz](https://graphviz.org/),比照 [Linked List 練習題](https://hackmd.io/@sysprog/linked-list-quiz) 在 HackMD 筆記上視覺化展現;
2. 函式 `swap_pair` 和 `reverse` 對於指標的操作方式顯然異於 `add_entry` 及 `remove_entry`,需要額外做 `head = ...` 的更新,請用指標的指標來改寫,並避免回傳指標;
3. 以遞迴改寫上述的 `reverse`,注意,你可能因此需要建立新的函式,如 `rev_recursive`,隨後在 `reverse` 函式中呼叫 `rev_recursive`;
4. 針對 singly-linked list 的節點,實作 [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle),你應該儘量降低記憶體的使用量;
:::