# 2021q1 Homework1 (quiz1)
contributed by < `chiehen` >
###### tags: `linux2021`
## 作業要求
- [x] 解釋上述程式運作原理,搭配 Graphviz
- [x] 測試程式使用到 random,多執行幾次可發現輸出結果相仿,請修正,並引入其他 Pseudorandom number generator
- [x] 參考 Optimized QuickSort — C Implementation (Non-Recursive) 並重寫上述 quick sort 程式碼,避免使用遞迴呼叫
- [ ] 請探討 Linux 的 linked list 和上述程式碼的落差,並改寫 linux-list 的 quick sort 範例,避免使用遞迴呼叫
- [ ] 研讀 Ching-Kuang Shene (冼鏡光] 教授撰寫的 A Comparative Study of Linked List Sorting Algorithms,思考高效率的 linked list 排序演算法,並落實於上述 (3) 的程式碼中
## 解釋程式
程式為單向 linked-list 的 quicksort 實做
主要跟 quick sort 有關的函式有以下:
* `list_add_node_t`: 將一個 node 加入 list 開頭
* `list_concat`: 將兩個 list 以頭尾相接的方式連接
* `quicksor`: 執行 quicksort 演算法
#### list_concat
```cpp=
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
left = &((*left)->next);
*left = right;
}
```
```graphviz
digraph structs {
node[shape=record];
struct0 [label="<ele>1|<next>next"]
struct1 [label="<ele>5|<next>next"]
struct2 [label="<ele>6|<next>next"]
node[shape=box];
struct3 [label= "left"];
struct4 [label= "right"];
struct5 [label= "&result"];
rankdir=LR;
struct5 -> struct0[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct0:next -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct4 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct5[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
第一次迴圈完:
```graphviz
digraph structs {
node[shape=record];
struct0 [label="<ele>1|<next>next"]
struct1 [label="<ele>5|<next>next"]
struct2 [label="<ele>6|<next>next"]
node[shape=box];
struct3 [label= "left"];
struct4 [label= "right"];
struct5 [label= "&result"];
struct6 [label = "&next1"]
rankdir=LR;
struct5 -> struct0[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct0:next -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct4 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct6[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct6 -> struct0:next[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
第二次(全部)迴圈完:
```graphviz
digraph structs {
node[shape=record];
struct0 [label="<ele>1|<next>next"]
struct1 [label="<ele>5|<next>next"]
struct2 [label="<ele>6|<next>next"]
node[shape=box];
struct3 [label= "left"];
struct4 [label= "right"];
struct5 [label= "&result"];
struct6 [label = "&next2"]
rankdir=LR;
struct5 -> struct0[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct0:next -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct4 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct6[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct6 -> struct1:next[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
此時 left 中為指向元素(value=5)的next的指標, 透過此指標將 next 中的值改為指向 right 所指向的元素:
```c=4
*left = right;
```
```graphviz
digraph structs {
node[shape=record];
struct0 [label="<ele>1|<next>next"]
struct1 [label="<ele>5|<next>next"]
struct2 [label="<ele>6|<next>next"]
node[shape=box];
struct3 [label= "left"];
struct4 [label= "right"];
struct5 [label= "&result"];
struct6 [label = "&next2"]
rankdir=LR;
struct5 -> struct0[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct0:next -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct4 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct6[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct6 -> struct1:next[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct1:next -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
`result` 即為指向連接後 list 開頭的指標
#### quicksort
```cpp=
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 ? &right : &left, n);
}
quicksort(&left);
quicksort(&right);
node_t *result = NULL;
list_concat(&result, left);
list_concat(&result, pivot);
list_concat(&result, right);
*list = result;
}
```
假設有一 list:
```graphviz
digraph structs {
rankdir=LR;
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct4 [label= "NULL"];
{
rank="same";
struct0 -> struct1
}
struct1 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct2 -> struct3[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct4[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
到第 10 行結束時將如下:
```graphviz
digraph structs {
rankdir=LR;
node[shape=record];
struct8 [label="<value>value|<data>5"]
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct4 [label= "NULL"];
struct5 [label= "pivot"];
struct6 [label= "p"];
struct7 [label = "NULL"];
{
rank="same";
struct6 -> struct2
}
struct0 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct5 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct1 -> struct7[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct2 -> struct3[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct4[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
迴圈中將大於 value 的 node 分到 right, 小於的分到 left,
經過第一次迴圈(line 11 - 15):
```graphviz
digraph structs {
rankdir=LR;
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct4 [label= "NULL"];
struct5 [label= "pivot"];
struct6 [label= "p"];
struct7 [label = "NULL"];
struct9 [label = "n"];
struct10 [label = "left"];
struct11 [label = "NULL"];
{
rank="same";
struct9 -> struct2
}
{
rank="same";
struct6 -> struct3
}
struct2 -> struct11[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct10 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct0 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct5 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct1 -> struct7[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct4[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
結束迴圈:
```graphviz
digraph structs {
rankdir=LR;
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct4 [label= "NULL"];
struct5 [label= "pivot"];
struct6 [label= "p"];
struct7 [label = "NULL"];
struct9 [label = "n"];
struct10 [label = "left"];
struct11 [label = "NULL"];
struct12 [label = "right"]
{
rank="same";
struct9 -> struct3
}
{
rank="same";
struct6 -> struct4
}
struct12 -> struct3[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct2 -> struct11[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct10 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct0 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct5 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct1 -> struct7[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct3 -> struct4[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
接著分別排序 right, left
第 23 行前:
```graphviz
digraph structs {
rankdir=LR;
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct4 [label= "right"]
struct5 [label= "result"]
{
rank="same";
struct0 -> struct1
}
{
rank="same";
struct5 -> struct2
}
struct2 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct4 -> struct3[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
第 23 行後:
```graphviz
digraph structs {
rankdir=LR;
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct5 [label= "result"]
{
rank="same";
struct0 -> struct1
}
{
rank="same";
struct5 -> struct2
}
struct2 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct1 -> struct3[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
第 24 行後:
```graphviz
digraph structs {
rankdir=LR;
node[shape=box];
struct0 [label= "*list"];
struct1 [label= "5"];
struct2 [label= "1"];
struct3 [label= "6"];
struct5 [label= "result"]
{
rank="same";
struct5 -> struct2
}
struct0 -> struct2[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct2 -> struct1[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
struct1 -> struct3[arrowhead=vee, dir=LR, tailclip=true, arrowsize=1];
}
```
`list` 變為指向排序完 list 的 pointer to pointer
### 補完程式
* list_make_node_t: 建立一個 value 為 `val` 的 node , 並加在 list 前端
* list_free: 釋放 list 所配置的記憶體
```c
static node_t *list_make_node_t(node_t *list, int val) {
node_t *new = malloc(sizeof(node_t));
if (!new)
return list;
new->value = val;
new->next = list;
return new;
}
static void list_free(node_t **p) {
while (*p) {
node_t *curr = *p;
*p = curr->next;
free(curr);
}
}
```
### Random
原先 random() 將 回傳 一個介於 0 to RAND_MAX 的整數, 而根據 [維基百科](https://en.wikipedia.org/wiki/Pseudorandom_number_generator):
>The Pseudorandom number generator(PRNG)-generated sequence is not truly random, because it is completely determined by an initial value, called the PRNG's seed (which may include truly random values)
數列將被初始值(initial value)決定, 根據 Linux Programmer's Manual:
> The srandom() function sets its argument as the seed for a new sequence
of pseudo-random integers to be returned by random(). These sequences
are repeatable by calling srandom() with the same seed value. If no
seed value is provided, the random() function is automatically seeded
with a value of 1.
因此呼叫 srandom 時設定初始值, 而為了保證初始值不同, 則將現今時間作為初始值:
```cpp
srandom(time(NULL));
```
## 修改 quick sort 程式碼 避免遞迴
參考 [Optimized QuickSort — C Implementation (Non-Recursive)](https://alienryderflex.com/quicksort/) 實做 non-resursive quick sort。
因為在文章中的資料是以 array 的方式儲存, 如果是 linked-list 在實做上需要 doubly linked list 才有辦法實現, 因此參考[課程討論區](https://www.facebook.com/groups/system.software2021/permalink/857571485085909/)以 stack 的方式實做 non-resursive quick sort。
定義 stack 結構:
```c
typedef struct __snode {
node_t *value; // 儲存 sorted linked-list
struct __snode *next; // 指向下一個 stack 元素
} snode_t;
```
兩個輔助函式對 stack 操作
* stack_push: 將元素推入 stack
* stack_pop: 將元素彈出 stack, 並回傳儲存的 list 指標
```c
static void stack_push(snode_t **top, node_t *list) {
snode_t *new = malloc(sizeof(snode_t));
if (!new)
return;
new->value = list;
new->next = *top;
*top = new;
}
static node_t *stack_pop(snode_t **top) {
snode_t *curr = *top;
if (curr) {
node_t *list = curr->value;
*top = curr->next;
free(curr);
return list;
}
return NULL;
}
```
依 pivot 調整數列的部份, 沿用原先程式的方法, 分為 `right`, `left` 兩個 list。 但在調整完後, 將 `right`, `pivot`, `left`依序推入 stack:
```c
if(left) stack_push(&stack, left);
stack_push(&stack, pivot);
if(right) stack_push(&stack, right);
```
持續將 stack 最頂端的 list 進行 Quick Sort, 如果最頂端的 list 只有一個元素, 則將此元素加到 `result` 前端, 當 stack 為空時, 代表 sorting 完成:
```c
while (stack) {
node_t *pivot = stack_pop(&stack);
int value = pivot->value;
node_t *p = pivot->next;
if (!p) {
list_add_node_t(&result, pivot);
} else {
// 進行 quick sort
.....
}
}
*list = result;
```
完整 non-recursive quick sort:
```cpp
void optquicksort(node_t **list) {
snode_t *stack = NULL;
node_t *result = NULL;
stack_push(&stack, *list);
while (stack) {
node_t *pivot = stack_pop(&stack);
int value = pivot->value;
node_t *p = pivot->next;
if (!p) {
list_add_node_t(&result, pivot);
} else {
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 ? &right : &left, n);
}
if(left) stack_push(&stack, left);
stack_push(&stack, pivot);
if(right) stack_push(&stack, right);
}
}
*list = result;
}
```
:::warning
如何改進上述程式碼?
:notes: jserv
:::
## 改寫 linux-list 的 quick sort 範例
## 研讀 Ching-Kuang Shene (冼鏡光] 教授撰寫的 A Comparative Study of Linked List Sorting Algorithms