# 2021q1 Homework (quiz1)
contributed by <`roger90810`>
- [x] 完成測驗並解釋
- [ ] 引入 [Pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator)
- [ ] [Optimized Quciksort (Non-Recursive)](https://alienryderflex.com/quicksort/)
- [ ] 改寫 [linux-list](https://github.com/sysprog21/linux-list) 的 quick sort 範例
- [ ] [研讀 A Comparative Study of Linked List Sorting Algorithms](https://pages.mtu.edu/~shene/PUBLICATIONS/1996/3Conline.pdf)
## main 分析
* 程式一開始先建立有 20 個 node 的 list
* 這些 node 的值為 random 產生
```cpp
size_t count = 20;
node_t *list = NULL;
while (count--)
list = list_make_node_t(list, random() % 1024);
```
在給的 code 中沒有 list_make_node_t 這個 function,經過分析後得出此 function 為加入新的 node 到目前 list 的前面,並回傳目前 list 的第一個 node,因此自己加入 list_make_node_t :
```cpp
static node_t *list_make_node_t(node_t *list, int val)
{
node_t *node = (node_t *)malloc(sizeof(node_t));
node->value = val;
node->next = list;
return node;
}
```
此時已經可以透過 list_display 印出整個 list 的值
```
NOT IN ORDER : 1016 84 706 124 326 483 763 498 939 186 205 809 236 74 255 81 115 105 966 359
```
`NULL`, `EXIT_FAILURE`, `EXIT_SUCCESS` macro
在 C11 規格書中的 7.22 General utilities <stdlib.h> 提到 :
> The macros defined are NULL (described in 7.19);
EXIT_FAILURE
and
EXIT_SUCCESS
which expand to integer constant expressions that can be used as the argument to the
exit function to return unsuccessful or successful termination status
* 所以必須 `#include <stdlib.h>`
* 而 `NULL` 的定義則是在 `stddef.h` 中,但 `stdlib.h` 中已經有 `#include <stddef.h>`,所以可以不用自己再 include 一次
實際去查看 stdlib.h 中發現這兩個 macro 其實就是定義一個常數,和一般寫的 `return 0`; 是一樣意思
```cpp
/* We define these the same for all machines.
Changes from this to the outside world should be done in `_exit'. */
#define EXIT_FAILURE 1 /* Failing exit status. */
#define EXIT_SUCCESS 0 /* Successful exit status. */
```
實作 `free_list`
* 在給的程式碼中,有用到 `list_free`,但並沒有實作
* 因此自己加入這段程式碼,並透過 `valgrind` 測試確實 free 掉 list
```cpp
static void list_free(node_t **list)
{
node_t *p = *list;
while (p) {
node_t *n = p;
p = p->next;
free(n);
}
}
```
valgrind 輸出結果:
```shell
==1064== Memcheck, a memory error detector
==1064== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1064== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1064== Command: ./list
==1064==
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
==1064==
==1064== HEAP SUMMARY:
==1064== in use at exit: 0 bytes in 0 blocks
==1064== total heap usage: 21 allocs, 21 frees, 1,344 bytes allocated
==1064==
==1064== All heap blocks were freed -- no leaks are possible
==1064==
==1064== For lists of detected and suppressed errors, rerun with: -s
==1064== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```
## quicksort 分析
quicksort 使用 divide and conquer 的方法,先將問題拆成多個子問題,解決子問題後,再將這些子問題合併,因此朝這個方向去思考,程式應該可以分成兩個部分
1. divide
2. combine
由以下程式碼可得知 pivot 的選擇為 list 的第一個元素
```cpp
node_t *pivot = *list
int value = pivot->value;
```
### 1. divide
* 由以下程式碼推斷,這裡是要將原 list 拆成 left, pivot, right 三個 list
* 首先利用 `pivot->next = NULL` 即可拆出只有 pivot 這個節點的 list
* 接著在 while loop 中,透過和 pivot 比較,可以將所有剩下的 node 拆成兩個 list
* `list left`,此 list 中所有節點的值皆`小於等於 pivot 的值`
* `list right`,此 list 中所有節點的值皆`大於 pivot 的值`
```graphviz
digraph left {
rankdir=LR;
node[shape=box];
left->"..."
"..."->"NULL"
}
```
```graphviz
digraph pivot {
rankdir=LR;
node[shape=box];
pivot->"NULL"
}
```
```graphviz
digraph right {
rankdir=LR;
node[shape=box];
right->"..."
"..."->"NULL"
}
```
```cpp
node_t *p = pivot->next;
pivot->next = NULL;
node_t *left = NULL;
node_t *right = NULL;
// while loop ...
quicksort(&left);
quicksort(&right);
```
while loop 的部分,就是如何拆出 left 和 right 這兩個 list
* 由程式碼分析出,n 和 p 的用途為走訪 list
* n 表示當前的節點,p 表示下一個要進行比較的節點
* `list_add_node_t` 的用途為插入一個節點到 list 的前面,因此可推出這邊是要將節點 n 插入到 `list AAA` 或 `list BBB` 的前面
* 觀察程式碼也發現,n 要插到哪裡是由 `n->value > value?` ,也就是和 pivot 的值比較來決定,因此可得出
* 若 n 的值大於 pivot 的值,則需要插入到 `list right`,並且 n 成為 `list right` 的第一個節點
* 若 n 的值小於等於 pivot 的值,則需要插入到 `list left`,並且 n 成為 `list right` 的第一個節點
```cpp
while(p) {
node_t *n = p;
p = p->next;
list_add_node_t(n->value > value? AAA : BBB, n);
}
```
所以得出
* AAA = `(e) &right`
* BBB = `(c) &left`
### 2. Combine
* 將 list 拆開 sort 後,需要將結果合併回來
* 因此需要合併 ```left```, ```pivot```, ```right``` 三個 list
首先分析 `list_concat` 的作法
* 先猜測此函式的用途為在一個 list 後面接上另一個 list
* 只需要將 list 中最後一個節點的 `next` 接到另一個 list 中第一個節點即可
* 這裡的做法 :
* left 一開始指到 list1 的第一個節點,並透過 while loop,最終會指到 list1 `最後一個節點的 next,此時為 NULL`
* 將 `NULL 改成 list2 的第一個節點`,就會使 list1 的最後一個節點接到 list2 的第一個節點,即可完成 concat
```cpp
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
LLL;
*left = right;
}
```
```graphviz
digraph list {
rankdir=LR;
node[shape=box];
first2[color=blue]
"...."[color=blue]
"end2"[color=blue]
first2->"...."
"...."->end2
first1[color=red];
"..."[color=red];
end1[color=red];
first1->"..."
"..."->end1
}
```
```graphviz
digraph list {
rankdir=LR;
node[shape=box];
first1[color=red];
"..."[color=red];
end1[color=red];
first1->"..."
"..."->end1
first2[color=blue]
"...."[color=blue]
"end2"[color=blue]
first2->"...."
"...."->end2
end1->first2
}
```
所以得出
* LLL = `(c) left = &((*left)->next);`
分析完 list_concat 的方法後,即可得知只要依序將 left, pivot, right 接起來,就可以得到遞增順序排序的 list
```cpp
node_t *result = NULL;
list_concat(&result, left);
CCC;
*list = result;
```
所以得出
* CCC = `(b) list_concat(&result, pivot); list_concat(&result, right);`
經過程式碼驗證,可得到正確結果
完整程式碼:
* list.h
```cpp
#ifndef LIST_H_INCLUDE
#define LIST_H_INCLUDE
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct __node {
int value;
struct __node *next;
} node_t;
// add node to the front of the list
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); // 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 ? &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;
}
#endif
```
* list.c
```cpp
#include "list.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");
}
static node_t *list_make_node_t(node_t *list, int val)
{
node_t *node = (node_t *) malloc(sizeof(node_t));
node->value = val;
node->next = list;
return node;
}
static void list_free(node_t **list)
{
node_t *p = *list;
while (p) {
node_t *n = p;
p = p->next;
free(n);
}
}
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
```