# 2021q1 Homework1 (quiz1)
contributed by < `gyes00205` >
###### tags: `linux2021`
>[作業要求](https://hackmd.io/@sysprog/SJlXFVMzMu)
## 程式碼原理
考慮一個單向 linked list,其結構定義為:
```cpp
typedef struct __node {
int value;
struct __node *next;
} node_t;
```
### 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;
}
```
I. `node_t->next = *list;`
```graphviz
digraph linkedlist {
node [shape=box];
list [label="*list",shape=plaintext, fontcolor=red];
rankdir=LR
{
rankdir=LR
a [label="node_t"];
b [label="0"];
c [label="1"];
d [label="NULL"];
}
{
rank="same";
list [label="*list",shape=plaintext, fontcolor=red];
list->b
}
a->b->c->d
}
```
II. `*list = node_t;`
```graphviz
digraph linkedlist {
node [shape=box];
list [label="*list",shape=plaintext, fontcolor=red];
rankdir=LR
{
rankdir=LR
a [label="node_t"];
b [label="0"];
c [label="1"];
d [label="NULL"];
}
{
rank="same";
list [label="*list",shape=plaintext, fontcolor=red];
list->a
}
a->b->c->d
}
```
### list_concat
將兩個 list 連接起來。
```cpp
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
left = &((*left)->next)
*left = right;
}
```
I. 用 while 迴圈讓 left 指向 left 的尾端
```cpp
while (*left)
left = &((*left)->next)
```
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
rankdir=LR
{
rankdir=LR
a [label="L1"];
b [label="L2"];
c [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
left->a
}
a->b->c
}
```
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
rankdir=LR
{
rankdir=LR
a [label="L1"];
b [label="L2"];
c [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
left->c
}
a->b->c
}
```
II. 將指到的 NULL 節點替換成 right 節點
`*left = right;`
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="L1"];
b [label="L2"];
c [label="NULL"];
d [label="R1"];
e [label="R2"];
f [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
left->c
right->d
}
a->b->c
d->e->f
}
```
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="L1"];
b [label="L2"];
d [label="R1"];
e [label="R2"];
f [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
left->right
right->d
}
a->b->d->e->f
}
```
### quicksort
將 list 分成兩個部分, 比 pivot 小的在 left, 比 pivot 大的在 right
```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;
}
```
I. 挑選 list 第一個節點當作 pivot
`node_t *pivot = *list;`
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
l1 [label="NULL"];
r1 [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
pivot [label="pivot",shape=plaintext, fontcolor=purple];
left->l1
right->r1
pivot->a
}
a->b->d->e->f->g
}
```
II.將 pivot 從 list 中拿出, p 指向剩下的 list
```cpp
node_t *p = pivot->next;
pivot->next = NULL;
node_t *left = NULL, *right = NULL;
```
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
l1 [label="NULL"];
r1 [label="NULL"];
n1 [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
p [label="p",shape=plaintext, fontcolor=orange];
pivot [label="pivot",shape=plaintext, fontcolor=purple];
left->l1
right->r1
p->b
pivot->a
}
a->n1
b->d->e->f->g
}
```
III. 從 p 拿出節點與 pivot 比較,比 pivot 小的在 left,比 pivot 大的在 right
```cpp
while (p) {
node_t *n = p;
p = p->next;
list_add_node_t(n->value > value ? &right : &left, n);
}
```
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
n2 [label="NULL"];
n3 [label="NULL"];
n1 [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
p [label="p",shape=plaintext, fontcolor=orange];
pivot [label="pivot",shape=plaintext, fontcolor=purple];
left->b
right->n3
p->d
pivot->a
}
a->n1
b->n2
d->e->f->g
}
```
IIII. 繼續比較 pivot 和 p 節點的大小
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
n2 [label="NULL"];
n3 [label="NULL"];
n1 [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
p [label="p",shape=plaintext, fontcolor=orange];
pivot [label="pivot",shape=plaintext, fontcolor=purple];
left->b
right->d
p->e
pivot->a
}
a->n1
b->n2
d->n3
e->f->g
}
```
V. 繼續比較 pivot 和 p 節點的大小
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
n2 [label="NULL"];
n3 [label="NULL"];
n1 [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
p [label="p",shape=plaintext, fontcolor=orange];
pivot [label="pivot",shape=plaintext, fontcolor=purple];
left->b
right->e
p->f
pivot->a
}
a->n1
b->n2
e->d->n3
f->g
}
```
VI. 繼續比較 pivot 和 p 節點的大小
```graphviz
digraph linkedlist {
node [shape=box];
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
n2 [label="NULL"];
n3 [label="NULL"];
n1 [label="NULL"];
}
{
rank="same";
left [label="left",shape=plaintext, fontcolor=red];
right [label="right",shape=plaintext, fontcolor=blue];
p [label="p",shape=plaintext, fontcolor=orange];
pivot [label="pivot",shape=plaintext, fontcolor=purple];
left->b
right->f
p->g
pivot->a
}
a->n1
b->n2
f->e->d->n3
g
}
```
VII. 假設做完所有遞迴後
```cpp
list_concat(&result, left);
list_concat(&result, pivot);
list_concat(&result, right)
```
* 結果
```graphviz
digraph linkedlist {
node [shape=box];
result [label="result",shape=plaintext, fontcolor=red];
rankdir=LR
{
rankdir=LR
a [label="2"];
b [label="1"];
d [label="3"];
e [label="4"];
f [label="5"];
g [label="NULL"];
}
{
rank="same";
result [label="result",shape=plaintext, fontcolor=red];
result->b
}
b->a->d->e->f->g
}
```
### list_make_node
第 4 ~ 9 行先判斷 list 是否為 NULL,只有在第一次才會有 NULL 的情況。
```cpp=
node_t *list_make_node_t(node_t *list, int value) {
node_t *head = list;
node_t *tmp;
if (!list) {
list = (node_t*)malloc(sizeof(node_t));
list->next = NULL;
list->value = value;
return list;
}
while (list->next)
list = list->next;
tmp = (node_t*)malloc(sizeof(node_t));
tmp->next = NULL;
tmp->value = value;
list->next = tmp;
return head;
}
```
* while 迴圈將 list 指到串列最末端
```graphviz
digraph linkedlist {
node [shape=box];
list [label="list",shape=plaintext, fontcolor=red];
head [label="head",shape=plaintext, fontcolor=blue];
NULL [label="NULL",shape=plaintext];
rankdir=LR
{
rank="same";
head->list->1
}
1->2->NULL
}
```
```graphviz
digraph linkedlist {
node [shape=box];
list [label="list",shape=plaintext, fontcolor=red];
head [label="head",shape=plaintext, fontcolor=blue];
NULL [label="NULL",shape=plaintext];
rankdir=LR
{
rank="same";
list->2
}
{
rank="same"
head->1
}
1->2->NULL
}
```
* `list->next` 接到 `tmp`
```graphviz
digraph linkedlist {
node [shape=box];
list [label="list",shape=plaintext, fontcolor=red];
head [label="head",shape=plaintext, fontcolor=blue];
tmp [label="tmp",shape=plaintext, fontcolor=blue];
NULL [label="NULL",shape=plaintext];
rankdir=LR
{
rank="same";
list->b
}
{
rank="same"
tmp->c
}
{
rank="same"
head->a
}
a->b->c->NULL
}
```
### list_free
在寫 list_free 時有遇到以下問題
* 錯誤版: 一開始寫的方式如下
```cpp
static void list_free(node_t **list) {
while (*list) {
node_t *tmp = *list;
printf("*list %p\n", *list);
list = &((*list)->next);
printf("(*list)->next %p\n", *list);
printf("free tmp %p\n", tmp);
free(tmp);
printf("(*list)->next %p\n", *list);
printf("-----------------------------\n");
}
}
```
:::warning
錯誤訊息:
free(): double free detected in tcache 2
Aborted (core dumped)
:::
> 後來試著將 *list 與 (*list)->next 印出, 發現釋放完 tmp 的記憶體後, (*list)->next 的記憶體竟然與釋放前不同。
*list 0x561b16144360
(*list)->next 0x561b16144320
free tmp 0x561b16144360
(*list)->next 0x561b16144010
---------------------------------------->
*list 0x561b16144010
(*list)->next (nil)
free tmp 0x561b16144010
(*list)->next 0x561b16144010
---------------------------------------->
*list 0x561b16144010
(*list)->next 0x561b16144010
free tmp 0x561b16144010
* 正確版
```cpp
static void list_free(node_t **list) {
while (*list) {
node_t *tmp = *list;
*list = (*list)->next;
free(tmp);
}
}
```
## random 輸出結果相仿?
參考 [random](https://linux.die.net/man/3/random) 得知可以設定 `void srandom(unsigned int seed)` 讓 random 的結果不同, 如果沒有設定 seed value, random 會預設 seed value 為 1。其中 `time(NULL)` 透過 time 函式來獲得系統時間, 它的返回值為從 00:00:00 GMT, January 1, 1970 到現在所持續的秒數。
```cpp
int main(int argc, char **argv) {
size_t count = 20;
node_t *list = NULL;
srandom(time(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;
}
```
## iterative 版本
參考 [Optimized QuickSort — C Implementation (Non-Recursive)](https://alienryderflex.com/quicksort/) 和 [hankluo6](https://hackmd.io/@hankluo6/2021q1quiz1#Optimized-QuickSort)
,參考連結的資料結構為陣列,我們的資料結構為 linked list ,所以我新增兩個 function 。
### find_value
找到 list 中第 index+1 個節點的 value
```cpp
int find_value(node_t *list, int index) {
while (index--)
list = list->next;
return list->value;
}
```
### assign_value
更改 list 中第 index+1 個節點的 value
```cpp
void assign_value(node_t *list, int index, int value) {
while (index--)
list = list->next;
list->value = value;
}
```
### quicksort_iterative
參考 [Optimized QuickSort — C Implementation (Non-Recursive)](https://alienryderflex.com/quicksort/) 並將資料結構改為 linked list。
```cpp
bool quicksort_iterative(node_t **list, int elements) {
#define MAX_LEVELS 1000
int piv, beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R ;
beg[0]=0; end[0]=elements;
while (i >= 0) {
L=beg[i]; R=end[i]-1;
if (L < R) {
piv = find_value(*list, L);
if (i == MAX_LEVELS-1)
return false;
while (L < R) {
while (find_value(*list, R) >= piv && L < R)
R--;
if (L < R)
assign_value(*list, L++, find_value(*list, R));
while (find_value(*list, L) <= piv && L < R)
L++;
if (L < R)
assign_value(*list, R--, find_value(*list, L));
}
assign_value(*list, L, piv); beg[i+1]=L+1; end[i+1]=end[i]; end[i++]=L;
}
else
i--;
}
return true;
}
```
### main
新增變數 `success` 判斷 quick sort 是否成功
```cpp
int main(int argc, char **argv) {
size_t count = 20;
bool success;
node_t *list = NULL;
srandom(time(NULL));
for (int i = 0; i < count; i++)
list = list_make_node_t(list, random() % 1024);
list_display(list);
success = quicksort_iterative(&list, count);
if(!success)
return EXIT_FAILURE;
list_display(list);
if (!list_is_ordered(list))
return EXIT_FAILURE;
list_free(&list);
return EXIT_SUCCESS;
}
```