# 2021q1 Homework1 (quiz1)
contributed by < `WayneLin1992` >
###### tags: `linux2021`
:::danger
作業規範要求中英文間用一個半形空白字元區隔,請留意。
征服 Linux 核心這樣的龐然巨物前,我們需要掌握各項細節。
:notes: jserv
:::
## 延伸問題
- [x] 使用 HackMD 筆記,並使用 Graphviz 達到視覺化
- [x] 考慮到 PNRG 修改 random()
- [x] 改寫 quick sort 避免遞迴呼叫
- [ ] 改寫 Linux quick sort 避免遞迴呼叫
- [ ] 思考高效 Linked List sort 並實作其中
## 測驗1
考慮一個單向 linked list ,其結構定義為:
```cpp
typedef struct __node {
int value;
struct __node *next;
} node_t;
```
已知不存在 circular (環狀結構),下方程式碼嘗試對該單向 linked list 進行 Quick sort,預期依據遞增順序。
```cpp
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;
}
```
## 思考過程
### **理解題目**
* 由題目及結構可以知道,這是一個單向的 linked list,並且不是環狀結構,下面為 quick sort,輸出的結果必須由小到大排列。
### **理解 quicksort 函式**
```cpp
static inline void list_add_node_t(node_t **list, node_t *node_t) {
node_t->next = *list;
*list = node_t;
```
* 由 `list_add_node_t` 名稱可以知道此函式為增加 node,輸入的參數為目前`list`和想要新增的 node `node_t`由`node_t->next = *list;`可以知道新增 node 的位置為`list`的頭部,`*list = node_t;`將 `list` 的指標重新指向首位。
```cpp
node_t->next = *list;
```
:::warning
需要說明 `list_add_node_t` 所新增的節點對應的方向,例如 Linux 核心的 `<linux/list.h>` 就有 `list_add` 和 `list_add_tail`,可見 https://github.com/sysprog21/linux-list/blob/master/include/list.h
工程人員說話應當精準,從而降低日後釐清問題的成本,避免說「增加 node」這樣含糊的話。
:notes: jserv
:::
```graphviz
digraph list_add_node_t {
rankdir=LR;
nodespe = 1.0
node[shape = box]
list[color = blue]
edge[color = blue]
list -> node1
edge[color = black]
node_t-> node1;
node1->node2;
}
```
```
*list = node_t;
```
```graphviz
digraph list_add_node_t {
rankdir=LR;
nodespe = 1.0
node[shape = box]
list[color = blue]
edge[color = blue]
list -> node_t;
edge[color = black]
node_t-> node1;
node1->node2;
}
```
```cpp
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
LLL;
*left = right;
}
```
* 由`list_concat`名稱可以知道此函式為連接兩個`node_t`參數為兩個`node_t`分別為 left 和 right 。
* `while(*left)`代表只要`*left`不是`NULL`就會不斷執行`while`因此可以推測出`LLL`應該是要不斷接續`next`直到尾部。
* `LLL=>left = &((*left)->next);`
會以`(*left)`表示是因為->的percedence比*及&大所以需要加括號,同理要`(*left)->next`的address一樣需要括號來表示`&((*left)->next)`,最後將`left`接上`right`。
```cpp
while (*left)
left = &((*left)->next);
```
```graphviz
digraph list_concat{
rankdir=LR;
nodespe = 1.0
node[shape = box]
left[color = blue]
edge[color = blue]
left -> node1;
edge[color = black]
node1-> node2;
node2->node3;
node3->NULL
}
```
```graphviz
digraph list_concat{
rankdir=LR;
nodespe = 1.0
node[shape = box]
edge[color = black]
node1-> node2;
node2->node3;
node3->NULL
left[color = blue]
edge[color = blue]
left->NULL;
}
```
```cpp
*left = right;
```
```graphviz
digraph list_concat{
rankdir=LR;
nodespe = 1.0
node[shape = box]
edge[color = black]
node1-> node2;
node2->node3;
node3->NULL
left[color = blue]
edge[color = blue]
left->NULL;
right[color = blue]
edge[color = blue]
right -> node_1;
edge[color = black]
node_1-> node_2;
node_2->node_3;
}
```
變成
```graphviz
digraph list_concat{
rankdir=LR;
nodespe = 1.0
node[shape = box]
edge[color = black]
node1-> node2;
node2->node3;
node3->node_1;
left[color = blue]
edge[color = blue]
left->node_1;
right[color = blue]
right -> node_1;
edge[color = black]
node_1-> node_2;
node_2->node_3;
}
```
```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 ? AAA : BBB, n);
}
quicksort(&left);
quicksort(&right);
node_t *result = NULL;
list_concat(&result, left);
CCC;
*list = result;
}
```
* 由quicksort名稱可以知道這是實做 quick sort 的函式,參數為`node_t **list`一開始先選定一個`pivot`當中間值,並將`list`中的`value`與`pivot`的`value`比較並把結果放入`left`和`right`中,因此可以知道`list_add_node_t(n->value > value ? AAA : BBB, n);`當`n->value > vale`時就應該放入`right`當中,反之經應該放入`left`當中。
* `AAA => &right`
* `BBB => &left`
之後就使用遞迴的方式處理`quicksort(&left)`和`quicksort(&right)`直到`list`為`NULL`每一次遞迴都會將其`right`和`right`與`result`連接,並注意`pivot`必須在中間所以只以知道`CCC`
* `CCC=>list_concat(&result, pivot);list_concat(&result, right);`
執行環境介紹
```shell
* Distributor ID: Ubuntu
* Description: Ubuntu 20.04.1 LTS
* Release: 20.04
* Codename: focal
```
LinkedList_quicksort.h
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct __node {
int value;
struct __node *next;
} node_t;
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)
left = &((*left)->next);
*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 ? &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;
}
```
由`main`部分可以知道,缺少了`list_make_node_t` `list_free` 我將其增加後
LinkedList_quicksort.c
```cpp
#include "LinkedList_quicksort.h"
static bool list_is_ordered(node_t *list) {
bool first = true;
int value;
while (list) {
if (first) {
value = list->value;
first = false;
} else {
if (list->value < value)
return false;
value = list->value;
}
list = list->next;
}
return true;
}
static void list_display(node_t *list) {
printf("%s IN ORDER : ", list_is_ordered(list) ? " " : "NOT");
while (list) {
printf("%d ", list->value);
list = list->next;
}
printf("\n");
}
/*
int random(void){
int a;
a = rand();
return a;
}
*/
static void list_free(node_t **list){
node_t *ptr = *list;
while(*list){
ptr = (*list)->next;
free(*list);
*list = ptr;
}
}
static inline node_t* list_make_node_t(node_t *list, int num){
node_t *p = malloc(sizeof(node_t));
if(p==NULL){
return list;
}else{
p->value = num;
p->next = NULL;
list_add_node_t(&list,p);
}
return list;
}
int main(int argc, char **argv) {
size_t count = 20;
node_t *list = NULL;
while (count--)
list = list_make_node_t(list, random() % 1024);
list_display(list);
quicksort(&list);
list_display(list);
if (!list_is_ordered(list))
return EXIT_FAILURE;
list_free(&list);
return EXIT_SUCCESS;
}
```
得到的結果:
```
NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359
IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016
```
使用 valgrind memcheck 確定記憶體空間是否有成功釋放出來,結果如下
```
==7547== Memcheck, a memory error detector
==7547== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==7547== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==7547== Command: ./LinkedList_quicksort
==7547==
NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359
IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016
==7547==
==7547== HEAP SUMMARY:
==7547== in use at exit: 0 bytes in 0 blocks
==7547== total heap usage: 21 allocs, 21 frees, 1,344 bytes allocated
==7547==
==7547== All heap blocks were freed -- no leaks are possible
==7547==
==7547== For lists of detected and suppressed errors, rerun with: -s
==7547== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```
有成功釋放記憶體空間。
## 實作`random()`
我們將程式重複執行後發現 ramdom 數都相同。
```shell
$ ./LinkedList_quicksort
NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359
IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016
$./LinkedList_quicksort
NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359
IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016
$ ./LinkedList_quicksort
NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359
IN ORDER : 74 81 84 105 115 124 186 205 236 255 326 359 483 498 706 763 809 939 966 1016
```
我們可以透過 `include <time.h>` 使用 `time()` 來影響 PRNG's seed 用`srandom(time(NULL))`再使`ramdom()`可以產生出隨機數,其結果如下
```
$ ./LinkedList_quicksort
NOT IN ORDER : 987 432 910 798 474 571 643 396 105 287 715 680 843 689 941 292 417 464 81 965
IN ORDER : 81 105 287 292 396 417 432 464 474 571 643 680 689 715 798 843 910 941 965 987
$ ./LinkedList_quicksort
NOT IN ORDER : 480 613 592 736 1 837 870 43 176 271 897 762 19 875 320 577 996 27 305 708
IN ORDER : 1 19 27 43 176 271 305 320 480 577 592 613 708 736 762 837 870 875 897 996
$ ./LinkedList_quicksort
NOT IN ORDER : 181 391 449 556 964 420 396 592 659 896 877 599 485 795 795 589 386 709 652 297
IN ORDER : 181 297 386 391 396 420 449 485 556 589 592 599 652 659 709 795 795 877 896 964
```
## 改寫 quick sort non recursive
參考 [Optimized QuickSort — C Implementation (Non-Recursive)](https://alienryderflex.com/quicksort/) 後可以知道作者實作出非遞迴的 quick sort 作者主要是在 stack 資料結構上進行,我們這邊是使用 linked list 進行,所以要進行修改,所以我可以將`value`存入 stack 中,並排列後再將 stack 依序存入 linked list value 中
non_recursive_quicksort
```cpp
void quicksort_nonrecursive(node_t **list){
node_t *ptr = *list;
int arr[20] ;
int j = 0;
while(ptr){
arr[j++] = ptr->value;
ptr = ptr->next;
}
int element = 20;
int piv, beg[element], end[element], i=0, L, R ;
beg[0]=0; end[0]=element;
while (i>=0) {
L=beg[i]; R=end[i]-1;
if (L<R) {
piv=arr[L]; if (i==element-1) return ;
while (L<R) {
while (arr[R]>=piv && L<R)
R--;
if (L<R)
arr[L++]=arr[R];
while (arr[L]<=piv && L<R)
L++;
if (L<R)
arr[R--]=arr[L]; }
arr[L]=piv;
beg[i+1]=L+1;
end[i+1]=end[i];
end[i++]=L;
}
else {
i--; }
}
node_t*ptr1 = *list;
i = 0;
while(ptr1){
ptr1->value = arr[i++];
ptr1 = ptr1->next;
}
}
```
執行結果下
```
NOT IN ORDER : 223 908 855 946 379 612 456 262 707 374 225 734 68 123 279 442 666 64 928 9
IN ORDER : 9 64 68 123 223 225 262 279 374 379 442 456 612 666 707 734 855 908 928 946
```