# 2020q3 Homework1 (quiz1)
contributed by < `Veternal1226` >
###### tags: `sysprog2020`
---
:::info
延伸問題:
- [x] 1. 解釋上述程式運作原理,搭配 Graphviz,比照 Linked List 練習題 在 HackMD 筆記上視覺化展現;
- [x] 2. 函式 swap_pair 和 reverse 對於指標的操作方式顯然異於 add_entry 及 remove_entry,需要額外做 head = ... 的更新,請用指標的指標來改寫,並避免回傳指標;
- [x] 3. 以遞迴改寫上述的 reverse,注意,你可能因此需要建立新的函式,如 rev_recursive,隨後在 reverse 函式中呼叫 rev_recursive;
- [ ] 4. 針對 singly-linked list 的節點,實作 Fisher–Yates shuffle,你應該儘量降低記憶體的使用量;
:::
## node_t架構:
```c
typedef struct __node {
int value;
struct __node *next;
} node_t;
```
```graphviz
digraph structs {
rankdir=LR;
node[shape=record]
struct0 [label="{<value> value|<next> next }"];
struct1 [label="{<value> ...|<next> ... }"];
struct0:next:struct1 -> struct1[ tailclip=true]
}
```
---
### add_entry
將新節點加至 list 尾端
```c=10
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
return;
}
```
indirect 這邊使用了 pointer to pointer ,代表指向 \*head 的指標
以下圖為例:
```graphviz
digraph structs {
node[shape=record]
pointer0 [label="{<pointer> indirect}"];
pointer1 [label="{<pointer> head}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label=NULL]
node0->node1->node2->NULL
pointer0 -> pointer1 [tailclip=true];
pointer1 -> node0 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
\*\*indirect 表 list node 0
\*indirect 表 head
indirect 表 &head
第19&20行用來將 indirect 指向 list 最尾端,因此判斷此 function 是實作將新 node 插入 list 尾端。
因此18行應為檢查新節點有沒有 malloc 成功,21行應為把新節點加入 list 中。
:::success
故AA1 = `(a)assert(new_node)`
AA2 = `(b)*indirect = new_node`
:::
圖解插入新 node 前的狀態:
```graphviz
digraph structs {
node[shape=record]
pointer0 [label="{<pointer> indirect}"];
pointer1 [label="{<pointer> ...}"];
pointer2 [label="{<pointer> head}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label=NULL]
node0->node1->node2->NULL
pointer0 -> pointer1 [tailclip=true];
pointer1 -> NULL [tailclip=true];
pointer2 -> node0 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
---
### find_entry
回傳指定值(value) 位在 list 中的哪個 node_t
```c=25
node_t *find_entry(node_t *head, int value)
{
node_t *current = head;
for (; current && current->value != value; current = current->next)
/* interate */;
return current;
}
```
實作方式為遍歷 list 直到 current->value == 指定值,
回傳 current 位置
---
### remove_entry
移除指定位置的一個節點
```c=33
void remove_entry(node_t **head, node_t *entry)
{
node_t **indirect = head;
while ((*indirect) != entry)
indirect = &(*indirect)->next;
*indirect = entry->next;
free(entry);
return;
}
```
實作方式為遍歷 list 直到 \*indirect == entry
透過將 \*indirect 指標指向 entry->next 來跳過 entry
---
### swap_pair
```c=45
node_t *swap_pair(node_t *head)
{
for (node_t **node = &head; *node && (*node)->next; /*BB1*/node = &(*node)->next->next/*BB1*/) {
node_t *tmp = *node;
*node = (*node)->next; // BB2
tmp->next = (*node)->next;
(*node)->next = tmp;
}
return head;
}
```
for 迴圈的第三個分區用來放做完一次內容後要執行的程式碼,經驗來說通常會放 index 移動或指標移動。
swap_pair 的作用是兩兩相鄰的交換,因此每次移動要移兩次 next ,因此 BB1 要填的內容我一開始的想法是填入`*node = (*node)->next->next`。
但實驗後發現 head 會跑掉,因為改動 \*node會改到 head 的值,因此應該要改的是再上一層的位址,也就是 `node = &(*node)->next->next`
:::success
因此 BB1 = `(e) node = &(*node)->next->next`
:::
BB2 在流程中比較好解釋
流程:
初始狀態:
```graphviz
digraph structs {
node[shape=record]
pointer0 [label="{<pointer> tmp}"];
pointer1 [label="{<pointer> node}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label="..."]
node0->node1
node1->node2
node2->NULL
pointer1 -> node0 [tailclip=true];
pointer0 -> node0 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
\*node = (\*node)->next; //BB2
因為這邊第一次就是要改 head 值,所以直接用 \*node 沒問題,把 \*node 改指到 \*node->next
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> tmp}"];
pointer1 [label="{<pointer> node}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label="..."]
node0->node1
node1->node2
node2->NULL
pointer1 -> node1 [tailclip=true];
pointer0 -> node0 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
tmp->next = (*node)->next;
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> tmp}"];
pointer1 [label="{<pointer> node}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label="..."]
node0->node2
node1->node2
node2->NULL
pointer0 -> node0 [tailclip=true];
pointer1 -> node1 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
(*node)->next = tmp;
```graphviz
digraph structs {
node[shape=record]
pointer0 [label="{<pointer> tmp}"];
pointer1 [label="{<pointer> node}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label="..."]
node0->node2
node1->node0
node2->NULL
pointer0 -> node0 [tailclip=true];
pointer1 -> node1 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
node = &(*node)->next->next
```graphviz
digraph structs {
node[shape=record]
pointer1 [label="{<pointer> node}"];
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL [shape=box,label="..."]
node0->node2
node1->node0
node2->NULL
pointer1 -> node2 [tailclip=true];
{rank=same;node0;node1;node2;NULL};
}
```
:::success
因此 BB2 = `(c)*node = (*node)->next`
:::
---
### reverse
```c=56
node_t *reverse(node_t *head)
{
node_t *cursor = NULL;
while (head) {
node_t *next = head->next;
/*CCC*/
head->next = cursor;
cursor = head;
/*CCC*/
head = next;
}
return cursor;
}
```
cursor 用來表示新 list 的開頭
每次迴圈將 next 記錄下來
把head所指節點的 next 轉向 cursor 所指位置
> head->next = cursor;
再把 cursor 指到此節點
> cursor = head;
:::success
因此 CCC = `(b)head->next = cursor; cursor = head`
:::
流程:
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> cursor}"];
pointer1 [label="{<pointer> head}"];
NULL0 [shape=box,label="NULL"]
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL1 [shape=box,label="NULL"]
node0->node1
node1->node2
node2->NULL1
pointer0 -> NULL0 [tailclip=true];
pointer1 -> node0[tailclip=true];
{rank=same;NULL0;node0;node1;node2;NULL1};
}
```
while (head) {
node_t *next = head->next;
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> cursor}"];
pointer1 [label="{<pointer> head}"];
pointer2 [label="{<pointer> next}"];
NULL0 [shape=box,label="NULL"]
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL1 [shape=box,label="NULL"]
node0->node1
node1->node2
node2->NULL1
pointer0 -> NULL0 [tailclip=true];
pointer1 -> node0[tailclip=true];
pointer2 -> node1 [tailclip=true];
{rank=same;NULL0;node0;node1;node2;NULL1};
}
```
head->next = cursor; //CCC
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> cursor}"];
pointer1 [label="{<pointer> head}"];
pointer2 [label="{<pointer> next}"];
NULL0 [shape=box,label="NULL"]
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL1 [shape=box,label="NULL"]
node0->NULL0
node1->node2
node2->NULL1
pointer0 -> NULL0 [tailclip=true];
pointer1 -> node0[tailclip=true];
pointer2 -> node1 [tailclip=true];
{rank=same;NULL0;node0;node1;node2;NULL1};
}
```
cursor = head; //CCC
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> cursor}"];
pointer1 [label="{<pointer> head}"];
pointer2 [label="{<pointer> next}"];
NULL0 [shape=box,label="NULL"]
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL1 [shape=box,label="NULL"]
node0->NULL0
node1->node2
node2->NULL1
pointer0 -> node0 [tailclip=true];
pointer1 -> node0[tailclip=true];
pointer2 -> node1 [tailclip=true];
{rank=same;NULL0;node0;node1;node2;NULL1};
}
```
head = next;
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> cursor}"];
pointer1 [label="{<pointer> head}"];
pointer2 [label="{<pointer> next}"];
NULL0 [shape=box,label="NULL"]
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL1 [shape=box,label="NULL"]
node0->NULL0
node1->node2
node2->NULL1
pointer0 -> node0 [tailclip=true];
pointer1 -> node1[tailclip=true];
pointer2 -> node1 [tailclip=true];
{rank=same;NULL0;node0;node1;node2;NULL1};
}
```
中間步驟省略,迴圈結束後應該會長這樣
```graphviz
digraph structs {
node[shape=record];
pointer0 [label="{<pointer> cursor}"];
pointer1 [label="{<pointer> head}"];
NULL0 [shape=box,label="NULL"]
node0 [shape=box,label=0];
node1 [shape=box,label=1];
node2 [shape=box,label=2];
NULL1 [shape=box,label="NULL"]
node0->NULL0
node1->node0
node2->node1
pointer0 -> node2 [tailclip=true];
pointer1 -> NULL1[tailclip=true];
{rank=same;NULL0;node0;node1;node2;NULL1};
}
```
cursor 指向 reverse 完後的 list ,這邊直接回傳 cursor。
( 在後面改寫成 pointer to pointer 時,要加一步 \*head=cursor )
---
## swap_pair 和 reverse 改寫
### swap_pair
```c=45
void 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 改寫成 \*(&head) 跟 head 都能動
### reverse
```c=56
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;
return;
}
```
把所有 head 改寫成 \*head ,並加上前面說到的 \*head=cursor 後就能動
---
### recursive reverse
參考自[chwchao](https://hackmd.io/@6R35tGUKTmC5_lms3b3V3g/SyGYIBDVw)
```c=56
node_t *rev_recursive(node_t *head)
{
if (!head || !(head)->next) {
return head;
}
node_t *new_head = rev_recursive(head->next);
if (head->next) {
head->next->next = head; // change head->next's next to previous layer's head
}
head->next = NULL; // always set new tail to NULL
return new_head;
}
```
先走到 list 尾端,將尾端指標作為回傳值回傳,每往上一層,將下方一層的 next (head->next->next) 轉向此層的head,所以程式碼為 `head->next->next = head;`
要確保 reverse 完的 list 最後指向 NULL: `head->next = NULL`
原本的reverse改寫成:
```c=70
void reverse(node_t **head)
{
*head = rev_recursive(*head);
return;
}
```