# 2024q1 Homework2 (quiz1+2)
contributed by < `PochariChun `>
## Quiz 1
### Quiz 1 -1 程式碼解釋
Quiz 1 的函式是一個使用非遞迴方法實做的快速排序, 其中以自定義的結構體 node_t, 作為鏈結串列的節點, 並透過間接指標 `**list` 來操作 `node_t` 節點的內部元素:
```graphviz
digraph node_t {
rankdir=LR;
"**list" [fontcolor=red, color=red, shape=plaintext];
"*head" [shape=plaintext];
{
"**list" -> "*head" [color=red];
"*head" -> node1 [color=blue];
}
subgraph cluster_0 {
label="node_t";
node1 [shape=record, label="long value | *next | {*left | *right}"];
}
}
```
#### What is 'AAAA'?
**AAAA** 出現在 list_length 函式中, 這個函式先 **判斷** 間接指標**現在指向的點** `*left` 和**指向的點的下一個點** `(*left)->next` **是否存在**, 存在則重複執行 AAAA 這個動作. **不存在就回傳現在指向的節點位址** `*left`.
已知 `*left` 指向尾端時為 NULL, 迴圈中止. 可判斷 AAAA 這個動作是 **"透過間接指標走訪"**, 可以推斷是 [linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) 中提過的間接指標指向 `next` 的走訪技術, 也就是 `AAAA = (*left)->next`.
#### What is 'BBBB'?
**BBBB** 出現在 list_length 函式中, 最後回傳的是**數字 n**, 因為 n 的初始值為 0, 加上函式名稱為計算長度, 故推斷 n 是用於計算走訪節點數量的計數器.
函式接著 **判斷** 間接指標 **現在指向的點** `*left` **是否存在**, 存在則重複執行計數器加一, 和 BBBB 這個動作.
已知 `*left` 指向尾端時為 NULL, 迴圈中止. 故 BBBB 這個動作同樣是 **"透過間接指標走訪"**, 也就是 `BBBB = (*left)->next`.
#### What is 'CCCC', 'DDDD', 'EEEE'?
這三個敘述都在非遞迴快速排序的主函式中, 這個函式用兩個儲存指標的陣列 begin[ ] 和 end[ ] 作為堆疊,去儲存尚未排列好的串列, 以 stack 達到取代遞迴的方式。
begin[ ] 代表尚未排列好的串列開頭, end[ ]則代表尚未排列好的串列結尾,
一開始先假定輸入的整串連結串列是未排序的, 就將整串串列開頭放入 begin[0], 尾端節點放入 end[0]。
```graphviz
digraph nodet {
begin [shape = none, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR>
<TD PORT="a0"> begin[0]</TD>
<TD PORT="a1"> begin[1]</TD>
<TD PORT="a2"> begin[2]</TD>
</TR> </TABLE>>];
end[shape = none, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR>
<TD PORT="a0"> end[0]</TD>
<TD PORT="a1"> end[1]</TD>
<TD PORT="a2"> end[2]</TD>
</TR> </TABLE>>];
n1[label = "4", fontcolor=red];
n2[label = "1"];
n3[label = "3"];
n4[label = "5"];
n5[label = "2"];
n6[label = "7", fontcolor=green];
"*list" [fontcolor=red,color=red];
"**list" [fontcolor=red,color=red];
{
begin:a0->"*list"
end:a0->n6
n1->n2;
n2->n3;
n3->n4;
n4->n5;
n5->n6;
}
{
rank="same";
"*list" -> n1
"**list" -> "*list"
}
}
```
接著從 begin 和 end 兩個 **代表未排序的 stack** 之中 "**取出**" 最上方串列, 使用 **L** 和 **R** 兩個指標分別指向現在處理的串列的開頭和結尾. 也就是取出的 begin 和 end.
如果 L 不等於 R, 就根據現在未排序串列中的 pivot **分類**串列, 分別成為**數值小於 pivot 的串列, pivot ,和數值大於 pivot 的串列**.
如果 L, R 指向同個位置,就表示現在 stack 最上層的串列是已經排序完畢,可以把這個節點加入結果串列最後 (最大)的位置. 於是透過 `i--` 可以做到從 stack 中 pop 的效果,
> 取出: 並非真正意義上從 stack 陣列取出, 而是使用 L 和 R 指標分別指向這些開頭節點, 最後要將分類結果存入 stack 時, 無視 stack 最上方那筆資料, 從那個位置開始存入三個分類結果
```graphviz
digraph list{
node [shape=box];
rankdir = "LR"
n1[label = "4"];
n2[label = "1"];
n3[label = "3"];
n4[label = "5"];
n5[label = "2"];
n6[label = "7"];
"*list" [fontcolor=red,color=red];
"**list" [fontcolor=red,color=red];
begin[label = "begin[0]"]
end[label = "end[0]"]
value[fontcolor=orange,color=orange];
value[label = "piviot value = 4", group = g3]
L[fontcolor=orange,color=orange];
R[fontcolor=orange,color=orange];
{
n1->n2;
n2->n3;
n3->n4;
n4->n5;
n5->n6;
}
{
rank="same";
"*list" -> n1
"**list" -> "*list"
}
{
begin->"*list"
L->begin
R->end
}
{
rank="same";
end->n6
}
}
```
確定 L 和 R 之後, 使用底下迴圈走訪 pivot 後續的節點的值, 如果比較大, 就把這個節點加入 right 連結串列之中, 沒有比較大就加入 left 連結串列,
由於是走訪, 故 `CCCC = p->next`
```C
while (p) {
node_t *n = p;
p = CCCC;
list_add(n->value > value ? &right : &left, n);
}
```
```graphviz
digraph list{
node [shape=box];
rankdir = "RL"
n1[label = "4"];
n2[label = "1"];
n3[label = "3"];
n4[label = "5"];
n5[label = "2"];
n6[label = "7"];
pivot [fontcolor=red,color=red];
value[fontcolor=orange,color=orange];
value[label = "piviot value = 4", group = g3]
left[fontcolor=red,color=red];
right[fontcolor=red,color=red];
{
pivot->n1;
left->n5->n3->n2;
right->n6->n4;
}
}
```
將這個串列 **分類** 成 **"小於 pivot 的 left"**, **"pivot"**, **"大於 pivot 的 right"** 的三個串列, 將這些串列的開頭和結尾指標 **依序存入** begin 和 end 兩個 stack 的 i ~i +2 層.
故 `DDDD = list_tail(&left)`,
故 `EEEE = list_tail(&right)`,
這樣可以用 stack 模擬原本使用遞迴呼叫來實做快速排序的方式,
```graphviz
digraph nodet {
rankdir = "RL";
n1[shape = box, label = "4", fontcolor=red];
n2[shape = box, label = "1", fontcolor=green];
n3[shape = box, label = "3"];
n4[shape = box, label = "5", fontcolor=green];
n5[shape = box, label = "2", fontcolor=red];
n6[shape = box, label = "7", fontcolor=red];
n7[shape = box, label = "4", fontcolor=green];
n8[shape = box, label = "1", fontcolor=green];
n9[shape = box, label = "5", fontcolor=green];
pivot [shape = box,fontcolor=red,color=red];
left[shape = box,fontcolor=red,color=red];
right[shape = box,fontcolor=red,color=red];
{
begin:a0 -> left -> n5->n3->n2;
begin:a1 -> pivot -> n1;
begin:a2 -> right -> n6->n4;
end:a0 -> n8;
end:a2 -> n9;
end:a1 -> n7;
}
begin [shape = none, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR>
<TD PORT="a0"> begin[0]</TD>
<TD PORT="a1"> begin[1]</TD>
<TD PORT="a2"> begin[2]</TD>
</TR> </TABLE>>];
end[shape = none, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR>
<TD PORT="a0"> end[0]</TD>
<TD PORT="a1"> end[1]</TD>
<TD PORT="a2"> end[2]</TD>
</TR> </TABLE>>];
}
```
最後可歸納出這個函式的運行原理:
- 從 begin 和 end 兩個 **代表未排序的 stack** 之中 "**取出**" 最上方串列, 將此串列開頭和結尾分別作為 L 和 R.
1. **如果 L 不等於 R**, 將這個串列 **分類** 成 **"小於 pivot"**, **"pivot"**, **"大於 pivot"** 的三個串列, 將這些串列的開頭和結尾指標 **依序存入** begin 和 end 兩個 stack .
2. **如果 L, R 指向同個位置**,就把這個 stack 的節點加入結果串列之前.
重複這兩個過程, 直到清空整個 stack, 最後回傳 result 就是由小到大排序好的結果.
### Linux 核心風格的List API 改寫
### 改進實作
### 延伸問題
> 使用 Linux 核心風格的 List API 改寫上述程式碼,並針對鏈結串列,
> 提出可避免最差狀況的快速排序實作,應設計效能評比的測試程式。
> 最差狀況:
### Quiz 1 -2
> a >>= 1; 意思 跟a >>1
>
a >>= 1;
a >>= 1; 是就地修改變數 a 的值,而 a >> 1 僅僅是一個右移運算的結果,不會修改 a 的值。
## Quiz 2
### Quiz 2 -1
### 延伸問題
> 解釋上述程式碼的運作,撰寫完整的測試程式,指出其中可改進之處並實作
在 Linux 核心的 cgroups 相關原始程式碼,找出 pre-order walk 程式碼並探討
### Quiz 2 -2
### 延伸問題
> 解釋上述程式碼的運作,撰寫完整的測試程式,指出其中可改進之處並實作
在 Linux 核心找出 LRU 相關程式碼並探討
### Quiz 2 -3
### 延伸問題
>解釋上述程式碼的運作原理
在 Linux 核心原始程式碼找出 find_nth_bit 的應用案例,並予以解說和分析,需要涵蓋 CPU affinity 相關的原始程式碼。