# 2020q3 Homework1 (quiz1)
contributed by < `shauming1020` >
###### tags: `homework`
## Q1. 解釋程式運作原理
> 解釋上述程式運作原理,搭配 Graphviz,比照 Linked List 練習題 在 HackMD 筆記上視覺化展現;
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> value | <next> next} | node name "];
ptr1 [label= "<name> pointer_name | <value> &node"];
pptr1 [label= "<name> pointer to pointer_name | <value> Address"];
ptr1:value -> node1:value
pptr1:value -> ptr1:name
pptr1:value -> node1:value
pptr1:value -> node1:next
}
```
---
### add_entry
> 新增節點,當 linked list 沒有內容時,必須由開發者更新指向開頭的指標。因此實際得到 reference,而非 copy
``` c=
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); // AA1
while (*indirect)
indirect = &(*indirect)->next;
*indirect = new_node; // AA2
}
```
- AA1 檢查是否成功 malloc 空間給 new_node 指向
:::info
ISO/IEC 9899:TC3 規格書 p.169 提到 assert
if expression (which shall have a scalar type) is false (that is, compares equal to 0),
the assert macro writes information about the particular call that failed on the standard error stream in an implementation-defined format.165).
It then calls the abort function.
:::
- AA2 當 *indirect 取值為 NULL 時,表 indirect 正指向尾端節點的 next,因此要讓尾端節點指向 新節點的位址,故選 *indirect = new_node;
- 示意流程
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> NULL} | node2"];
node3 [label= " {<value> new_value | <next> NULL} | node3 "];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> new_node | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &head"];
node1:next -> node2:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> ptr1:name
}
```
``` #10 condition: while (*indirect)```
- 從 indirect 所存位址取值不為 NULL 時,表尚未走訪到尾端節點
``` #11 indirect = &(*indirect)->next```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> NULL} | node2 "];
node3 [label= " {<value> new_value | <next> NULL} | node3 "];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> new_node | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &(node1.next)"];
node1:next -> node2:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> node1:next
}
```
- 取的值為 NULL 時,表走訪到尾端節點的 next
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> NULL} | node2 "];
node3 [label= " {<value> new_value | <next> NULL} | node3 "];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> new_node | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &(node2.next)"];
node1:next -> node2:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> node2:next
}
```
``` #12 *indirect = new_node```
- 讓他的 next 指向 new_node (即 &node3)
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2 "];
node3 [label= " {<value> new_value | <next> NULL} | node3 "];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> new_node | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &(node2.next)"];
node1:next -> node2:value
node2:next -> node3:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> node2:next
}
```
---
### find_entry
> 搜尋節點,成功則回傳找到的目標節點,否則回傳 NULL
``` c=
node_t *find_entry(node_t *head, int value)
{
node_t *current = head;
for (; current && current->value != value; current = current->next)
/* interate */;
return current;
}
```
```#4 condition: current && current->value != value ```
- 若 current 未指向 NULL 且 current 的 value 不為目標值,則往 next 繼續走訪
- 當找到目標值,表 current 指向目標節點位址,回傳 current,否則回傳 NULL
---
### remove_entry
> 移除指定節點,指向開頭的指標可能因此變更,所以需要用到 a pointer to a pointer (指標的指標)
``` c=
void remove_entry(node_t **head, node_t *entry)
{
node_t **indirect = head;
while ((*indirect) != entry)
indirect = &(*indirect)->next;
*indirect = entry->next;
free(entry);
}
```
- 示意流程
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> NULL} | node3"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> entry | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> ptr1
}
```
``` #5 condition: while ((*indirect) != entry) ```
- 當 *indirect 與 entry 指向不同的節點位址時
``` #6 indirect = &(*indirect)->next ```
- 則往下繼續遍歷,直到 *indirect 恰指向目標節點的位址
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> NULL} | node3"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> entry | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &(node2.next)"];
node1:next -> node2:value
node2:next -> node3:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> node2:next
}
```
``` #8 *indirect = entry->next ```
- 讓 *indirect 指向 entry 的 next
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> NULL} | node2"];
node3 [label= " {<value> 3 | <next> NULL} | node3"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> entry | <value> &node3"];
pptr1 [label= "<name> indirect | <value> &(node2.next)"];
node1:next -> node2:value
ptr1:value -> node1:value
ptr2:value -> node3:value
pptr1:value -> node2:next
}
```
``` #9 free(entry) ```
- 釋放 entry 所指向 heap 的空間
---
### swap_pair
> 交換一對相鄰的節點,取自 LeetCode: Swap Nodes in Pairs,給定 1->2->3->4,則該回傳 2->1->4->3
``` 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;
}
```
- BB1 解讀成 node = &(((*node)->next)->next),完成一對的交換後,將 *node 往下走訪兩節點
- BB2 *node 為新一對當中的開頭者,因此要先讓 *node 往下一個節點去抓新的開頭
- 示意流程
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2 "];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
pptr1:value -> ptr1
}
```
``` #3 condition: if *node && (*node)->next ```
* 判斷當前與下一個指向是否為空,若非空則可以進行成對的交換
``` #4 node_t *tmp = *node```
- 讓一個 tmp 指標指向與 *node 相同的位址
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> tmp | <value> &node1"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
ptr2:value -> node1:value
pptr1:value -> ptr1
}
```
``` #5 *node = (*node)->next```
- (*node)->next 為 &node2 指派給 *node,即讓 *node,也就是 head 往下走訪一個節點
- 往下個節點抓新對的開頭
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> tmp | <value> &node1"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` #6 tmp->next = (*node)->next```
- 這時 (*node)->next 為 &node3,指派到 tmp->next,也就讓 (*node) 的下一個節點成為 tmp 的下一個節點
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node3} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> tmp | <value> &node1"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node3:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` #7 (*node)->next = tmp```
- 將 tmp 的值指派到 (*node)->next,也就是 (*node)->next 的值改為 tmp 所存的節點位址
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node3} | node1 "];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> tmp | <value> &node1"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node3:value
node2:next -> node1:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` node = &(*node)->next->next```
- &(*node)->next->next 解讀成 &(((*node)->next)->next)
- 因此先得到 (*node)->next) 的值 &node1
- 再將 node1->next 的位址指派給 node
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node3} | node1 "];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> tmp | <value> &node1"];
pptr1 [label= "<name> node | <value> &(node1.next)"];
node1:next -> node3:value
node2:next -> node1:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr2:value -> node1:value
pptr1:value -> node1:next
}
```
- 執行直到跳出迴圈 ,最後回傳 head
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node4} | node1 "];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> NULL} | node3 "];
node4 [label= " {<value> 4 | <next> &node3} | node4 "];
ptr1 [label= "<name> head | <value> &node2"];
pptr1 [label= "<name> node | <value> &(node3.next)"];
node1:next -> node4:value
node2:next -> node1:value
node4:next -> node3:value
ptr1:value -> node2:value
pptr1:value -> node3:next
}
```
---
### reverse
> 將給定的 linked list 其內節點予以反向,即 1->2->3->4,則該回傳 4->3->2->1
``` 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;
}
```
- 從最後 return cursor 得知,cursor 是新 list 的 head,因此 head->next = cursor 是讓舊的 head 去指向新的 head (i.e. cursor),再透過 cursor = head 讓 cursor 移動到新接上來的節點上,達到反向的效果
- 示意流程
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> NULL"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
}
```
```#5 node_t *next = head->next;```
- head->next 取值 &node2,指派到 next
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4 "];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> NULL"];
ptr3 [label= "<name> next | <value> &node2"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
ptr3:value -> node2:value
}
```
```#6 head->next = cursor```
- 將 cursor 值 NULL,指派到 head->next
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> NULL"];
ptr3 [label= "<name> next | <value> &node2"];
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
ptr3:value -> node2:value
}
```
```#6 cursor = head ```
- 將 head 值 &node1,指派給 cursor
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4 "];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> &node1"];
ptr3 [label= "<name> next | <value> &node2"];
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
ptr3:value -> node2:value
ptr2:value -> node1:value
}
```
```#7 head = next```
- 將 next 值 &node2,指派給 head
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2 "];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4 "];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> cursor | <value> &node1"];
ptr3 [label= "<name> next | <value> &node2"];
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr3:value -> node2:value
ptr2:value -> node1:value
}
```
- 直到 head 值為 NULL,跳出迴圈並回傳 cursor,即成為新的 head
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 4 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> NULL"];
ptr2 [label= "<name> cursor | <value> &node4"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr2:value -> node4:value
}
```
---
## Q2. 請用指標的指標來改寫,並避免回傳指標
> 函式 swap_pair 和 reverse 對於指標的操作方式顯然異於 add_entry 及 remove_entry,需要額外做 head = ... 的更新,請用指標的指標來改寫,並避免回傳指標;
### 1. 改寫 swap_pair
```c=
void swap_pair_v2(node_t **node)
{
for (; *node && (*node)->next; node = &(*node)->next->next) {
node_t *tmp = *node;
*node = (*node)->next;
tmp->next = (*node)->next;
(*node)->next = tmp;
}
}
...
swap_pair_v2(&head);
```
- 原來的程式碼輸入 node_t *head 後再調用 node_t **node = &head 去進行操作,最後回傳 head 指標
- 因為 call-by-value,執行函式呼叫後會宣告一個 node_t *head 與輸入的指標指向相同的節點,函式內是對該 head 進行操作,因此實際上並沒有更動到原先輸入的指標,故最後還要回傳完成操作後的 head
- 參考 [你所不知道的C語言:指標篇](https://hackmd.io/@sysprog/c-pointer?type=view#%E6%B2%92%E6%9C%89%E3%80%8C%E9%9B%99%E6%8C%87%E6%A8%99%E3%80%8D%E5%8F%AA%E6%9C%89%E3%80%8C%E6%8C%87%E6%A8%99%E7%9A%84%E6%8C%87%E6%A8%99%E3%80%8D)
- 而一開始若把 &head 當作引數輸入給 node_t **node,則會直接對原 head 操作
### 2. 改寫 reverse
```c=
void reverse_v2(node_t **node)
{
node_t *cursor = NULL;
while (*node) {
node_t *next = (*node)->next;
(*node)->next = cursor; cursor = *node;
*node = next;
}
*node = cursor;
}
...
reverse_v2(&head);
```
- 原理與上述相同,而最後操作完 head 會指向 NULL,因此要讓 head 重新指向新的 head (i.e. cursor)
- 示意流程
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1 "];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> NULL"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` #5 node_t *next = (*node)->next ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2 "];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> NULL"];
ptr3 [label= "<name> next | <value> &node2"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
ptr3:value -> node2:value
pptr1:value -> ptr1:name
}
```
``` #6 (*node)->next = cursor; cursor = *node; ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
ptr2 [label= "<name> cursor | <value> &node1"];
ptr3 [label= "<name> next | <value> &node2"];
pptr1 [label= "<name> node | <value> &head"];
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
ptr3:value -> node2:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` #7 *node = next ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> cursor | <value> &node1"];
ptr3 [label= "<name> next | <value> &node2"];
pptr1 [label= "<name> node | <value> &head"];
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr3:value -> node2:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
- 下個迴圈
``` #5 node_t *next = (*node)->next ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> cursor | <value> &node1"];
ptr3 [label= "<name> next | <value> &node3"];
pptr1 [label= "<name> node | <value> &head"];
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr3:value -> node3:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` #6 (*node)->next = cursor ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> cursor | <value> &node1"];
ptr3 [label= "<name> next | <value> &node3"];
pptr1 [label= "<name> node | <value> &head"];
node2:next -> node1:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr3:value -> node3:value
ptr2:value -> node1:value
pptr1:value -> ptr1:name
}
```
``` #6 cursor = *node ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node2"];
ptr2 [label= "<name> cursor | <value> &node2"];
ptr3 [label= "<name> next | <value> &node3"];
pptr1 [label= "<name> node | <value> &head"];
node2:next -> node1:value
node3:next -> node4:value
ptr1:value -> node2:value
ptr3:value -> node3:value
ptr2:value -> node2:value
pptr1:value -> ptr1:name
}
```
``` #7 *node = next ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node3"];
ptr2 [label= "<name> cursor | <value> &node2"];
ptr3 [label= "<name> next | <value> &node3"];
pptr1 [label= "<name> node | <value> &head"];
node2:next -> node1:value
node3:next -> node4:value
ptr1:value -> node3:value
ptr3:value -> node3:value
ptr2:value -> node2:value
pptr1:value -> ptr1:name
}
```
- 依序做下去直到跳出迴圈
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 4 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> NULL"];
ptr2 [label= "<name> cursor | <value> &node4"];
pptr1 [label= "<name> node | <value> &head"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr2:value -> node4:value
pptr1:value -> ptr1:name
}
```
``` #9 *node = cursor```
- 最後要將 cursor 值指派給 *node
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 4 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
ptr2 [label= "<name> cursor | <value> &node4"];
pptr1 [label= "<name> node | <value> &head"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
ptr2:value -> node4:value
pptr1:value -> ptr1:name
}
```
---
## Q3. 以遞迴改寫上述的 reverse
> 以遞迴改寫上述的 reverse,注意,你可能因此需要建立新的函式,如 rev_resersive,隨後在 reverse 函式中呼叫 rev_resersive;
>
```c=
node_t *rev_resersive(node_t **node, node_t **cursor) {
if (*node) {
node_t *next = (*node)->next;
(*node)->next = *cursor; *cursor = *node;
*node = next;
*cursor = rev_resersive(node, cursor);
}
return *cursor;
}
void reverse_v3(node_t **node)
{
node_t *cursor = NULL;
*node = rev_resersive(node, &cursor);
}
...
reverse_v3(&head);
```
- 將 while 迴圈內會重複執行的程式部份改用遞迴方式呼叫執行,最後在回傳 cursor 即可
---
## Q4. Fisher–Yates shuffle
> 針對 singly-linked list 的節點,實作 Fisher–Yates shuffle,你應該儘量降低記憶體的使用量;
:::info
The modern algorithm
```
-- 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]
```
假設rand()時間複雜度為O(1)
採用array實作時間複雜度為O(n),但需要一額外與數組大小相同的空間,用來存放隨機取出的值,因此空間複雜度為O(2n)
---
| Range | Roll | Scratch | Result |
| -------- | -------- | -------- | -------- |
| | |1 2 3 4 5 6 7 8 | |
| 1–8 | 6 |1 2 3 4 5 **8** 7 | **6** |
| 1–7 | 2 |1 **7** 3 4 5 8 | **2** 6 |
| 1-6 | 6 |1 7 3 4 5 | **8** 2 6 |
| ... |
:::
### 實作
#### create_list
> 生成 n 個值為 {1, ..., n} 升冪排列的 list node
``` c=
void create_list(node_t **node, int n)
{
for (int i = 1; !(*node) && i <= n; node = &(*node)->next, i++) {
node_t *new_node = malloc(sizeof(node_t));
new_node->value = i;
new_node->next = NULL;
*node = new_node;
}
}
```
- n = 4
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> &node2} | node1"];
node2 [label= " {<value> 2 | <next> &node3} | node2"];
node3 [label= " {<value> 3 | <next> &node4} | node3"];
node4 [label= " {<value> 4 | <next> NULL} | node4"];
ptr1 [label= "<name> head | <value> &node1"];
pptr1 [label= "<name> node | <value> &head"];
node1:next -> node2:value
node2:next -> node3:value
node3:next -> node4:value
ptr1:value -> node1:value
pptr1:value -> ptr1:name
}
```
#### swap_value
> 交換兩 node_t 的值
``` c=
void swap_value(node_t **a, node_t **b) {
int tmp = (*a)->value;
(*a)->value = (*b)->value;
(*b)->value = tmp;
}
```
#### FYshuffle
- 參考 [Shuffle a given array using Fisher–Yates shuffle Algorithm](https://www.geeksforgeeks.org/shuffle-a-given-array-using-fisher-yates-shuffle-algorithm/) 矩陣版本修改
```c=
void FYshuffle (node_t **head, int n)
{
srand(time(NULL));
reverse(head);
node_t **start = head;
node_t **pick = head;
for (int i = n-1; i > 0; i--) {
// select a random index from 0 to i
int j = rand() % (i+1);
// shift pick to this index
for (;j && (*pick); j--, pick = &(*pick)->next);
// swap the pick value with start value.
swap_value(start, pick);
// update the start and pick
start = &(*start)->next; pick = start;
}
}
```
| Range | Roll | Scratch |
| -------- | -------- | -------- |
| | |1 2 3 4 5 6 7 8 |
| reverse | - |8 7 6 5 4 3 2 1 |
| 1–8 | 3 |[6] 7 8 5 4 3 2 1 |
| 1–7 | 6 |[6 2] 8 5 4 3 7 1 |
| 1-6 | 6 |[6 2 1] 5 4 3 7 8 |
| 1-5 | 0 |[6 2 1 5] 4 3 7 8 |
| ... | | |
``` #4 reverse(head) ```
- 演算法會從**數組尾端往前取值**,但作業要求是單向的list,因此需先對 list 反向
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 4 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
pptr1 [label= "<name> head | <value> &head"];
pptr2 [label= "<name> start | <value> &head"];
pptr3 [label= "<name> pick | <value> &head"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
pptr1:value -> ptr1:name
pptr2:value -> ptr1:name
pptr3:value -> ptr1:name
}
```
``` #5 condition: for (int i = n-1; i > 0; i--) ```
``` #11 int j = rand() % (i+1) ```
- 從 [0, i] 隨機取出一個值,i + 1 為當前可取的 list 長度,且會逐漸縮短
``` #14 for (;j && (*pick); j--, pick = &(*pick)->next); ```
- 移動 pick 使其指向目標 node
- 假設 j = 3,則將 pick 往 next 移動 3 次
- 若 j = 0,則不需要移動 pick
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 1 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 4 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
pptr1 [label= "<name> head | <value> &head"];
pptr2 [label= "<name> start | <value> &head"];
pptr3 [label= "<name> pick | <value> &(node2.next)"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
pptr1:value -> ptr1:name
pptr2:value -> ptr1:name
pptr3:value -> node2:next
}
```
``` #17 swap_value(start, pick) ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 4 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 1 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
pptr1 [label= "<name> head | <value> &head"];
pptr2 [label= "<name> start | <value> &head"];
pptr3 [label= "<name> pick | <value> &(node2.next)"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
pptr1:value -> ptr1:name
pptr2:value -> ptr1:name
pptr3:value -> node2:next
}
```
``` #20 start = &(*start)->next ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 4 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 1 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
pptr1 [label= "<name> head | <value> &head"];
pptr2 [label= "<name> start | <value> &(node4.next)"];
pptr3 [label= "<name> pick | <value> &(node2.next)"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
pptr1:value -> ptr1:name
pptr2:value -> node4:next
pptr3:value -> node2:next
}
```
``` #20 pick = start ```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 4 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 1 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
pptr1 [label= "<name> head | <value> &head"];
pptr2 [label= "<name> start | <value> &(node4.next)"];
pptr3 [label= "<name> pick | <value> &(node4.next)"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
pptr1:value -> ptr1:name
pptr2:value -> node4:next
pptr3:value -> node4:next
}
```
- 假設每回都取到自己,即 j = 0
- 執行完 n - 1 個 iteration,此例 n = 4,故 start 會移動 3 次
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
node1 [label= " {<value> 4 | <next> NULL} | node1"];
node2 [label= " {<value> 2 | <next> &node1} | node2"];
node3 [label= " {<value> 3 | <next> &node2} | node3"];
node4 [label= " {<value> 1 | <next> &node3} | node4"];
ptr1 [label= "<name> head | <value> &node4"];
pptr1 [label= "<name> head | <value> &head"];
pptr2 [label= "<name> start | <value> &(node2.next)"];
pptr3 [label= "<name> pick | <value> &(node2.next)"];
node4:next -> node3:value
node3:next -> node2:value
node2:next -> node1:value
ptr1:value -> node4:value
pptr1:value -> ptr1:name
pptr2:value -> node2:next
pptr3:value -> node2:next
}
```
### 分析
- 假設 rand() 為 $O(1)$ 和 reverse() 為 $O(n)$
- 時間複雜度
* **Worst Case** : 每次取到最遠的值做交換,每回移動 pick $O(n)$,共 n 回,$O(n^2)$
* **Best Case** : 每次取到最近的值交換,每回移動 pick $O(1)$,共 n 回,$O(n)$
* **Average Case** : $O$(($n^2$ + $n$) / 2) = $O(n^2)$
- 空間複雜度
* 只使用 n 個 node 和 3 個 pointer to pointer 變數,故 $O(n)$
### 完整測試程式碼如下
```c=
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct __node {
int value;
struct __node *next;
} node_t;
void create_list(node_t **node, int n)
{
for (int i = 1; !(*node) && i <= n; node = &(*node)->next, i++) {
node_t *new_node = malloc(sizeof(node_t));
new_node->value = i;
new_node->next = NULL;
*node = new_node;
}
}
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;
}
void swap_value(node_t **a, node_t **b) {
int tmp = (*a)->value;
(*a)->value = (*b)->value;
(*b)->value = tmp;
}
void FYshuffle (node_t **head, int n)
{
srand(time(NULL));
reverse(head); printf("reverse as initial ... "); print_list(*head);
node_t **start = head;
node_t **pick = head;
for (int i = n-1; i > 0; i--) {
// select a random index from 0 to i
int j = rand() % (i+1);
// shift pick to this index
for (;j && (*pick); j--, pick = &(*pick)->next);
printf("[iter %d] start at %d ,and swap with %d ... ", n-i, (*start)->value, (*pick)->value);
// swap the pick value with start value.
swap_value(start, pick); print_list(*head);
// update the start and pick
start = &(*start)->next; pick = start;
}
printf("[final] start at %d \n", (*start)->value);
}
void print_list(node_t *head)
{
for (node_t *current = head; current; current = current->next)
printf("%d ", current->value);
printf("\n");
}
int main() {
int len = 8;
node_t *head = NULL;
create_list(&head, len);
printf("before shuffle "); print_list(head);
FYshuffle(&head, len);
printf("after shuffle "); print_list(head);
}
```
```
before shuffle 1 2 3 4 5 6 7 8
reverse as initial ... 8 7 6 5 4 3 2 1
[iter 1] start at 8 ,and swap with 8 ... 8 7 6 5 4 3 2 1
[iter 2] start at 7 ,and swap with 4 ... 8 4 6 5 7 3 2 1
[iter 3] start at 6 ,and swap with 3 ... 8 4 3 5 7 6 2 1
[iter 4] start at 5 ,and swap with 6 ... 8 4 3 6 7 5 2 1
[iter 5] start at 7 ,and swap with 5 ... 8 4 3 6 5 7 2 1
[iter 6] start at 7 ,and swap with 7 ... 8 4 3 6 5 7 2 1
[iter 7] start at 2 ,and swap with 1 ... 8 4 3 6 5 7 1 2
[final] start at 2
after shuffle 8 4 3 6 5 7 1 2
```
---