---
# System prepended metadata

title: 2020q3 Homework1 (quiz1)

---

# 2020q3 Homework1 (quiz1)
contributed by < `dalaoqi` >

## Outline
[TOC]

考慮一個單向 linked list，其結構定義為:
```cpp
typedef struct __node {                   
    int value;
    struct __node *next;
} node_t;
```
## 程式運作原理

### add_entry

```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;

    assert(new_node);
    while (*indirect)
        indirect = &(*indirect)->next;
    *indirect = new_node;
}
```

新增一個節點到 linked list 的尾端：

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    indirect[shape=none];
    head[shape=none];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none,label="NULL"];
    NULL_2[shape=none,label="NULL"];
    new_node[shape=box];
    


    node_0->node_1->node_2->NULL_2;
    new_node->NULL_1;
    
    {rank=same;indirect->head->node_0;}
}
```

在這函式裡，首先建立了一個 `indirect` 指標指向 `head` 指標， `head` 則指向 linked list 的起點。 


接著透過 malloc 新增一個節點的記憶體空間，這個節點會指向 NULL，經過 while 迴圈 ， `indirect` 會指向 linked list 最尾端NULL，此時將新節點指派給 `indirect` 完成新增節點。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    indirect[shape=none];
    head[shape=none];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none,label="NULL"];
    NULL_2[shape=none,label="NULL"];
    new_node[shape=box];
    


    node_0->node_1->node_2->NULL_2;
    new_node->NULL_1;
    
    {rank=same;head->node_0;}

    {rank=same;indirect->NULL_2;}
}
```

```graphviz
digraph G {
    graph[pencolor=transparent];

    indirect[shape=none];
    head[shape=none];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none,label="NULL"];
    new_node[shape=box];
    

    {rank=same;
    node_0->node_1->node_2->new_node->NULL_1;}
    head->node_0;

    indirect->new_node;
}
```
* 此函式中的 `assert(new_node);` 目的是為了要確保新的節點有成功 malloc (NULL 失敗)，因此 `AA1` 為 `assert(new_node);`
* `AA2` 答案為 `*indirect = new_node` 是因為需要得知 linked list 最尾端的位置在何處。


### find_entry

```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;
}
```
從 linked list 的頭開始遍歷，直到找到符合的節點後回傳該節點。

### remove_entry

```cpp
void remove_entry(node_t **head, node_t *entry)
{
    node_t **indirect = head;

    while ((*indirect) != entry)
        indirect = &(*indirect)->next;

    *indirect = entry->next;
    free(entry);
}
```

移除某個節點（entry），類似 `add_entry` 的做法，詳細如下：

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    indirect[shape=none];
    head[shape=none];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    delete[shape=none,label="want_to_delete"];
    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;indirect->head->node_0;}
    {rank=same; delete->node_2}
}
```

經由 while 迴圈找到該 entry 

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    indirect[shape=none];
    head[shape=none];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    delete[shape=none,label="want_to_delete_entry"];
    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;head->node_0;}
    {rank=same; indirect->delete->node_2}
}
```

更換 indirect 指向的記憶體空間，再 free entry。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    indirect[shape=none];
    head[shape=none];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    delete[shape=none,label="want_to_delete_entry"];
    node_0->node_1->node_3->NULL_1; 
    {rank=same;head->node_0;}
    {rank=same; delete->node_2}
    {rank=same; indirect->node_3}
}
```

### 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;
}
```

`node_0`  和 `node_1` 交換， `node_2` 和 `node_3` 交換，以次類推。
這裡的做法也是使用指標的指標來做操作。


一開始進入迴圈時長這樣， node 指標指向 head 指標。
```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    n[shape=none,label="node"];
    node_0[shape=box];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];

    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;n->head->node_0;}
}
```

宣告 tmp 指向 *node（head 指標）。


```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    n[shape=none,label="node"];
    node_0[shape=box];
    tmp[shape=none]
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];

    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;n->head->node_0;}
    tmp->node_0;
}
```

`*node = (*node)->next;`

node 新位址指向 node 的下個節點的位址。因此  `BB2` 答案為 `*node = (*node)->next;`


```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    n[shape=none,label="node"];
    node_0[shape=box];
    tmp[shape=none]
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];

    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;n->head->node_1}
    {rank=same;tmp->node_0;}
}
```
`tmp->next = (*node)->next;`
tmp 新位址指向 node 下一個的位址。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    n[shape=none,label="node"];
    node_0[shape=box];
    tmp[shape=none]
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];

    node_1->node_2->node_3->NULL_1; 
    {rank=same;n->head->node_1}
    {rank=same;tmp->node_0->node_2;}
}
```
node 指向 tmp 完成交換。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    n[shape=none,label="node"];
    node_0[shape=box];
    tmp[shape=none]
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];

    node_1->node_0->node_2->node_3->NULL_1; 
    {rank=same;n->head->node_1}
    {rank=same;tmp->node_0}
}
```

因為是兩倆交換，因此 node 在每次 iteration 完都要 往前兩格，所以 `BB1` 為 `node = &(*node)->next->next`

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    n[shape=none,label="node"];
    node_0[shape=box];
    tmp[shape=none]
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];

    node_1->node_0->node_2->node_3->NULL_1; 
    {rank=same;head->node_1}
    {rank=same;tmp->node_0}
    {rank=same;n->node_2}
}
```

### reverse

```cpp
node_t *reverse(node_t *head)
{
    node_t *cursor = NULL;
    while (head) {
        node_t *next = head->next;
        head->next = cursor; cursor = head;
        head = next;
    }
    return cursor;
}
```

做 linked list 反轉，一開始大概是長這樣。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    node_0[shape=box];
    cursor[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    NULL_2[shape=none,label="NULL"];
    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;head->node_0}
    rank=same;cursor->NULL_2
}
```

`node_t *next = head->next;` ， next 指標指向 head 的 next。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    next[shape=none];
    node_0[shape=box];
    cursor[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    NULL_2[shape=none,label="NULL"];
    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;head->node_0}
    {rank=same;next->node_1}
    cursor->NULL_2
}
```

`CCC` 是 `head->next = cursor; cursor = head;` ，其中 cursor 是用來記住 head 前一個節點使得之後可以被指向，達成反轉的效果。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    head[shape=none];
    next[shape=none];
    node_0[shape=box];
    cursor[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    NULL_2[shape=none,label="NULL"];
    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;head->node_0}
    {rank=same;next->node_1}
    {rank=same;cursor->node_0;}
    rank=same;node_0->NULL_2;
}
```

`head = next;` ，遍歷下去後直到 head = NULL，回傳 cursor 做為新的 head。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR

    next[shape=none,label="next(head)"];
    node_0[shape=box];
    cursor[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_3[shape=box];
    NULL_1[shape=none,label="NULL"];
    NULL_2[shape=none,label="NULL"];
    node_0->node_1->node_2->node_3->NULL_1; 
    {rank=same;next->node_1}
    {rank=same;cursor->node_0;}
    rank=same;node_0->NULL_2;
}
```

## 延伸問題

避免函式回傳指標。

### swap_pair

```cpp
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;
    }
}
```
其實原本的寫法就可以說是指標的指標的操作，只是還需要回傳 head ，因此稍微改一下，將傳入的參數改成是傳位址，用 node 去接這個傳入的參數，變相利用 node 當作 head 進行操作。

### reverse

```cpp
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;
}
```
一樣也是傳 head 的位址參數到函式中，概念就和原先的方式一樣，比較要注意的地方是在最後要把 cursor 指標 指派給 *head 。 

### 遞迴版 reverse

```cpp
void reverse(node_t **head)
{
    rev_recurive(*head,  head);
}

void rev_recurive(node_t *head, node_t **headRef)
{
    if (head == NULL) {
        return;
    } 
    node_t *first = head;
    node_t *rest = head->next;
    if (rest == NULL) {
        *headRef = first;
        return;
    }
    rev_recurive(rest, headRef);
    rest->next = first;
    first->next = NULL;
}
```

遞迴的概念是將 linked list 分成 第一個節點 ( `first` ) 和剩下其他的節點們 ( `rest` ) 遞迴拆解下去。遞迴函式傳入的會是 `head` 指標和 `head` 指標的位址，此方式是為了避免回傳指標。
從 `reverse` call `rev_recurive` 函式後，在進入 `rev_recurive` 函式之前的狀態會長這樣。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    rest[shape=none];
    node_0[shape=box];
    head[shape=none,label="first"];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none, label="NULL"];
    node_0->node_1->node_2->NULL_1; 
    {rank=same;head->node_0}
    {rank=same;rest->node_1}
}
```

進入 `rev_recurive` 後， `first` 和 `rest` 會更新成下面這樣。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    rest[shape=none];
    node_0[shape=box];
    head[shape=none,label="first"];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none, label="NULL"];
    node_0->node_1->node_2->NULL_1; 
    {rank=same;head->node_1}
    {rank=same;rest->node_2}
}
```

一直遞迴下去，直到 `rest` 碰到 `NULL` 進入 if 判斷式。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    rest[shape=none];
    node_0[shape=box];
    head[shape=none,label="first"];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none, label="NULL"];
    node_0->node_1->node_2->NULL_1; 
    {rank=same;head->node_2}
    {rank=same;rest->NULL_1}
}
```

在 if 在判斷式中將目前 `first` 節點的位址指派給 `headRef` 目的是為了將 linked list 最後面節點的位址指派給原先傳進來的 head 位址。之後return 。


```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    rest[shape=none];
    node_0[shape=box];
    head[shape=none,label="first"];
    headRef[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none, label="NULL"];
    node_0->node_1->node_2->NULL_1; 
    {rank=same;headRef->head->node_2}
    {rank=same;rest->NULL_1}
}
```

return 之後，繼續往下做還沒做完的事情，因為 return 回到遞迴的前一層，所以 `rest` 跑回 `first` 的位置， `first` 跑回指向前一個 node（回到進到函式前的狀態）。


```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR
    rest[shape=none];
    node_0[shape=box];
    head[shape=none,label="first"];
    headRef[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    NULL_1[shape=none, label="NULL"];
    node_0->node_1->node_2->NULL_1;
    {rank=same;headRef->rest->node_2}
    {rank=same;head->node_1}
}
```

繼續完成沒做完的事， `rest` 指向 `first` 指向的節點， `first` 指向的節點指向 `NULL`。此次的函式執行完畢，返回上一層遞迴。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR;
    rest[shape=none];
    node_0[shape=box];
    NULL_1[shape=none, label="NULL"];
    head[shape=none,label="first"];
    headRef[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_0->node_1;
    
    {rank=same;headRef->rest->node_2}
    {rank=same;head->node_1}
    node_2->node_1->NULL_1
}
```

一樣 `rest` 跑回 `first` 的位置， `first` 跑回指向前一個 node。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR;
    rest[shape=none];
    node_0[shape=box];
    NULL_1[shape=none, label="NULL"];
    head[shape=none,label="first"];
    headRef[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_0->node_1;
    {rank=same;headRef->node_2};
    {rank=same;rest->node_1}
    {rank=same;head->node_0}
    node_2->node_1->NULL_1
}
```

和前一次的遞迴一樣做完剩下的工作。

```graphviz
digraph G {
    graph[pencolor=transparent];
    rankdir=LR;
    rest[shape=none];
    node_0[shape=box];
    NULL_1[shape=none, label="NULL"];
    head[shape=none,label="first"];
    headRef[shape=none];
    node_1[shape=box];
    node_2[shape=box];
    node_1->node_0->NULL_1;
    {rank=same;headRef->node_2};
    {rank=same;rest->node_1}
    {rank=same;head->node_0}
    node_2->node_1
}
```

### Fisher–Yates shuffle

參考 [wiki, Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)進行實作。

```cpp
void shuffle(node_t **head)
{
    int len = 0;
    srand(time(NULL));
    node_t **cursor = head;
    while(*cursor) {
        len++;
        cursor = &(*cursor)->next;
    }
    
    node_t *first = NULL;
    for (int i = len; i > 0; i--) {
        int random = rand() % i;
        cursor = head;
        while (random--) {
            cursor = &(*cursor)->next;
        }
        node_t *new_node = *cursor;
        *cursor = (*cursor)->next;
        new_node->next = NULL;
        if (first) {
            new_node->next = first;
            first = new_node;
        } else {
            first = new_node;
        }
    }
    *head = first;
}
```

* 一開始會去計算整個 linked list 的長度有多長。知道長度後就開始進入該演算法的實作。
* 一開始會宣告 `first` ，這是待會要指派給 head 的新的 linked list 的頭。
* 隨後 `cursor` 會去找被挑選中的 node 。
* 找到後會有一個 `new_node` 指標去指向這個被找到的 node 。而 `cursor` 則將此 node 的前一個和後一個的 node 連接。
* `new_node` append 到剛剛的 `first` 。直到迴圈結束。
* 迴圈結束後，將原先的 `head` 的更換為 `first` 。

:::warning
延伸問題 :fire: :fire: :fire: 
* 思考可能的更好的寫法，能否降低時間複雜度。
* 或是其他更好的洗牌演算法。
:::




