# 2021q1 Homework1 (quiz1)
contributed by < `mahavishnuj` >
:::danger
留意共筆書寫規範:中英文間用一個半形空白區隔
:notes: jserv
:::
## 測驗題目
### 測驗 1
考慮一個單向 linked list,其結構定義為:
```cpp
typedef struct __node {
int value;
struct __node *next;
} node_t;
```
已知不存在 circular (環狀結構),下方程式碼嘗試對該單向 linked list 進行 Quick sort,預期依據遞增順序。
```c
static inline void list_add_node_t(node_t **list, node_t *node_t) {
node_t->next = *list;
*list = node_t;
}
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
LLL;
*left = right;
}
void quicksort(node_t **list)
{
if (!*list)
return;
node_t *pivot = *list;
int value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
node_t *left = NULL, *right = NULL;
while (p) {
node_t *n = p;
p = p->next;
list_add_node_t(n->value > value ? AAA : BBB, n);
}
quicksort(&left);
quicksort(&right);
node_t *result = NULL;
list_concat(&result, left);
CCC;
*list = result;
}
```
## QuickSort 原理
使用 `Divided and Conquer` 將原本的大問題切分成若干個子問題,並逐一解答,最終彙整成完整答案。以 `quicksort` 而言,將數列中一值定為 `pivot` 將數量從頭尾循序逼近,比 `pivot` 數值小的放置左邊,大的放置右邊,直到頭尾交會處與`pivot`如此結果出來的兩邊數列迭代執行下去。
#### list_add_node_t
```cpp
static inline void list_add_node_t(node_t **list, node_t *node_t) {
node_t->next = *list;
*list = node_t;
}
```
function吃的是一個`pointer to pointer to node`跟`pointer to node`
- 假設一開始初始狀態為下
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
list[shape=plaintext,fontcolor=blue]
node_t[shape=plaintext,fontcolor=red]
rankdir=LR
{
rankdir = LR
A[label=5]
B[label=6]
C[label=8]
D[label=1]
NULL1[label=NULL,shape=plaintext]
NULL2[label=NULL,shape=plaintext]
}
{
rank="same";
list->head->A
node_t->D
}
A->B->C->NULL1
D->NULL2
}
```
經過`node_t->next = *list`將 `node_t` 所指向的那個節點的 `next` 指到 `list` 所指的那個 `head` 所指的節點
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
list[shape=plaintext,fontcolor=blue]
node_t[shape=plaintext,fontcolor=red]
rankdir=LR
{
rankdir = LR
A[label=5]
B[label=6]
C[label=8]
D[label=1]
NULL1[label=NULL,shape=plaintext]
}
{
rank="same";
list->head->A
node_t->D->A
}
A->B->C->NULL1
}
```
最後經過`*list = node_t;`把`*list`指導新加入的node上面
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
list[shape=plaintext,fontcolor=blue]
node_t[shape=plaintext,fontcolor=red]
rankdir=LR
{
rankdir = LR
A[label=5]
B[label=6]
C[label=8]
D[label=1]
NULL1[label=NULL,shape=plaintext]
}
{
rank="same";
list->node_t->D
}
head->A
D->A->B->C->NULL1
}
```
再來看看另一個function
### list_concat()
```c=
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
LLL;
*left = right;
}
```
可以看的出來,function的目的是想要將`*left`所指向的`list`所指向的兩個list串起來,我們已經知道`left`是一個`Pointer to a Pointer to node_t`因此我們要做的是循序找到最後一個節點並將他與right連起來
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
left[shape=plaintext,fontcolor=blue]
right[shape=plaintext,fontcolor=red]
rankdir=LR
{
rankdir = LR
A[label=5]
B[label=6]
C[label=8]
D[label=1]
E[label=4]
NULL1[label=NULL,shape=plaintext]
NULL2[label=NULL,shape=plaintext]
}
{
rank="same";
left->head->A
right->D
}
A->B->C->NULL1
D->E->NULL2
}
```
- 最終目標是要將`left`停在最後一個節點上端最後一個尾巴節點所指的地方,while loop才會結束,並且將位置assign給`right`
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
left[shape=plaintext,fontcolor=blue]
right[shape=plaintext,fontcolor=red]
rankdir=LR
{
rankdir = LR
A[label=5]
B[label=6]
C[label=8]
D[label=1]
E[label=4]
NULL1[label=NULL,shape=plaintext]
NULL2[label=NULL,shape=plaintext]
}
{
rank="same";
left->NULL1
right->D
}
A->B->C->NULL1
D->E->NULL2
head->A
}
```
而迭代的過程是不斷的assigh新的value給`left`前面我們提到`left`是一個`Pointer to Pointer to node_t`因此他儲存的內容應該是指向該節點指摽的address,以head來說的話內容應該是儲存`&head`,因此可以以`&(*left)`來表示,因此LLL為`left = &((*left)->next)`直到指向圖中NULL的位置
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
left[shape=plaintext,fontcolor=blue]
right[shape=plaintext,fontcolor=red]
rankdir=LR
{
rankdir = LR
A[label=5]
B[label=6]
C[label=8]
D[label=1]
E[label=4]
NULL2[label=NULL,shape=plaintext]
}
{
rank="same";
left->right
right->D
}
A->B->C->D->E->NULL2
head->A
}
```
再來我們可以進入到quicksort的內容
```c=
void quicksort(node_t **list)
{
if (!*list)
return;
node_t *pivot = *list;
int value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
node_t *left = NULL, *right = NULL;
while (p) {
node_t *n = p;
p = p->next;
list_add_node_t(n->value > value ? AAA : BBB, n);
}
quicksort(&left);
quicksort(&right);
node_t *result = NULL;
list_concat(&result, left);
CCC;
*list = result;
}
```
其中的方法是將`pivot`指向第一個元素
```graphviz
digraph graphname{
node[shape=box]
head[shape=plaintext,fontcolor=red]
pivot[shape=plaintext,fontcolor=blue]
rankdir=LR
{
rankdir = LR
A[label=2]
B[label=3]
C[label=1]
D[label=4]
NULL1[label=NULL,shape=plaintext]
}
{
rank="same";
pivot->head->A
}
A->B->C->D->NULL1
}
```
再來把`pivot`所指向的點獨立出來
```graphviz
digraph graphname{
node[shape=box]
p[shape=plaintext,fontcolor=red]
pivot[shape=plaintext,fontcolor=blue]
rankdir=LR
{
rankdir = LR
A[label=2]
B[label=3]
C[label=1]
D[label=4]
NULL1[label=NULL,shape=plaintext]
NULL2[label=NULL,shape=plaintext]
}
{
rank="same"
pivot->A
p->B
}
B->C->D->NULL1
A->NULL2
}
```
再來後面的迴圈,就是把`pivot`的值取出來,然後在繼續看剩下的node,把比`pivot`小的地方放在`*left`的list裡面,價值比`pivot`大的放在`*right`裡面
```c=
node_t *left = NULL, *right = NULL;
while (p) {
node_t *n = p;
p = p->next;
list_add_node_t(n->value > value ? AAA : BBB, n);
}
```
再來看到重點的function,前面我的知道`list_add_node_t()`的這個function所傳入的必須是一個`Pointer to Pointer`因此AAA所傳入的必須是所指的點的address因此應該是&(ptr)
```c=
list_add_node_t(n->value > value ? &right : &left, n);
```
最後我們看到的部分,除了對左右的兩個數列在進行quicksort之外,還要把結果鏈結起來,首先給予一個`NULL Pointer Result`再把left加在list的最右邊,因此照順序來說應該是先加入`left`再來`pivot`最後再加入`right`因此CCC的部分應該為
```c=
quicksort(&left);
quicksort(&right);
node_t *result = NULL;
list_concat(&result, left);
//CCC;
list_concat(&result, pivot);
list_concat(&result, right);
*list = result;
```
```graphviz
digraph graphname{
node[shape=box]
pivot[shape=plaintext,fontcolor=red]
left[shape=plaintext,fontcolor=blue]
right[shape=plaintext,fontcolor=orange]
result[shape=plaintext,fontcolor=purple]
rankdir=LR
{
rankdir = LR
A[label=1]
B[label=2]
C[label=3]
D[label=4]
E[label=5]
NULL1[label=NULL,shape=plaintext]
NULL2[label=NULL,shape=plaintext]
NULL3[label=NULL,shape=plaintext]
}
{
rank="same"
pivot->C
left->A
right->D
result->NULL3
}
A->B->NULL1
D->E->NULL2
}
```
依序慢慢加入後變成這個型態
```graphviz
digraph graphname{
node[shape=box]
left[shape=plaintext,fontcolor=blue]
right[shape=plaintext,fontcolor=orange]
result[shape=plaintext,fontcolor=purple]
rankdir=LR
{
rankdir = LR
A[label=1]
B[label=2]
C[label=3]
D[label=4]
E[label=5]
NULL1[label=NULL,shape=plaintext]
}
{
rank="same"
left->D
right->D
result->D
}
A->B->C->D->E->NULL1
}
```
## Random Function Issiue
- 根據 [random(3)](https://man7.org/linux/man-pages/man3/random.3.html)
> The random() function uses a nonlinear additive feedback random number generator employing a default table of size 31 long integers to return successive pseudo-random numbers in the range from 0 to RAND_MAX. The period of this random number generator is very large, approximately 16 * ((2^31) - 1).
`Random()`透過一個seed來決定產生變數的sequence,由wiki上面所說 [PRNG-generated sequence](https://en.wikipedia.org/wiki/Pseudorandom_number_generator)並不真的是完全亂數產生數字,而是透過ㄧ開始所給定Initial Value做為Seed來決定產生出來數字的順序。
比較直觀的解決方法就是將Seed設定成某個隨機的數字,看到說明是2的31次方,我的想法是如果是以記憶體的address做為一個seed,就算是一個NULL的值記憶體還是會宣告一個位址來儲存他,如果再隨機記憶體分配的狀況下可能會分配到不同的記憶體位置,這樣的方法可能就可以增加產生出來數字的複雜度
```graphviz
digraph g {
graph [
rankdir = "LR"
];
node [
fontsize = "16"
shape = "ellipse"
];
edge [
];
"node0" [
label = "<f0> SEED| <f1>"
shape = "record"
];
"node1" [
label = "<f0> 0xf7fc4380| <f1> | <f2> |"
shape = "record"
];
"node0":f0 -> "node1":f0 [
id = 0
];
}
```
因此用像是這樣的function來增加隨機數字的複雜性
```c=
srand(time(NULL))
```
### Quick Sort改寫