# 2024q1 Homework2 (quiz1+2)
contributed by <[`padaray`](https://github.com/padaray)>
## 第一週測驗題
[題目連結](https://hackmd.io/@sysprog/linux2024-quiz1)
### 測驗 `1`
參考資料是 [Optimized QuickSort](https://alienryderflex.com/quicksort/) ,此方法實作非遞迴的快速排序法
**鏈結串列結構**
```graphviz
digraph G {
subgraph cluster_1 {
node [shape=record];
hn1 [label="{ {left | right | next } | value : long }"];
label="node_t 1";
}
subgraph cluster_2 {
node [shape=record];
hn2 [label="{ {left | right | next } | value : long }"];
label="node_t 2";
}
hn1:next -> hn2
}
```
**快速排序法使用的函式**
1. `list_add`
此函式將節點 `node_t` 插入串列 `list` ,使節點 `node_t` 成為 `list` 串列的第一個節點,傳入的參數為 `**list` 原因是使用 `*list` 會複製一個 `list` 串列副本,因此要使用指標的指標來避免此問題
2. `list_tail`
此函式會逐一走訪每個節點,走訪到最後一個節點時,回傳該節點的記憶體位置,傳入的參數為 `**left` 原因是避免使用到條件判斷式,以此精簡程式碼
完整程式碼,已填空遮蔽部分 `AAAA`
```c
node_t *list_tail(node_t **left)
{
while ((*left) && (*left)->next)
left = &(*left)->next;
return *left;
}
```
3. `list_length`
初始化變數 `n` 為 0,逐一走訪每個節點,每走訪一個節點變數 `n` + 1,最後回傳變數 `n`
完整程式碼,已填空遮蔽部分 `BBBB`
```c
int list_length(node_t **left)
{
int n = 0;
while (*left) {
++n;
left = &(*left)->next;
}
return n;
}
```
</br>
**快速排序法程式碼**
`quick_sort`
先呼叫函式 `list_length` 計算串列長度 `n`,以此得知堆疊所需要的配置的空間大小, `begin[]` 和 `end[]` 分別儲存串列的頭和尾,`result` 儲存排序後的串列, `left` 儲存小於 pivot 的節點, `right` 儲存大於 pivot 的節點
```c
int n = list_length(list);
int value;
int i = 0;
int max_level = 2 * n;
node_t *begin[max_level], *end[max_level];
node_t *result = NULL, *left = NULL, *right = NULL;
begin[0] = *list;
end[0] = list_tail(list);
```
</br>
初始化變數 `L` 和 `R` 為 `begin[]` 和 `end[]` , `L` 和 `R` 指向不同節點時,代表該串列節點數大於一,節點數不足二時,直接將該節點加入 `result` 串列
```cpp
if (L != R) {
...
} else {
if (L)
list_add(&result, L);
i--;
}
```
</br>
```graphviz
digraph{
rankdir=LR
node [shape=box,color=black]
pivot [shape=none,label=pivot]
p [shape=none,label=p]
pivot->node1
{rank=same;pivot;node1}
p->node2
{rank=same;p;node2}
node1->node2->node3->node4->node5[arrowhead=vee]
}
```
```graphviz
digraph{
rankdir=TD
node [shape=box,color=black]
pivot [shape=none,label=pivot]
p [shape=none,label=p]
pivot->node1
p->node2
node2->node3->node4->node5[arrowhead=vee]
{rank=same; node1; node2; node3; node4; node5;}
}
```
```c
node_t *pivot = L;
value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
```
</br>
從第二個節點開始逐一走訪,當此節點 `value` 小於 `pivot->value` 加入 `left` 串列,反之加入 `right` 串列,下圖為範例:
```graphviz
digraph{
rankdir=LR
node [shape=box,color=black]
{
left [shape=none,label=left]
pivot [shape=none,label=pivot]
right [shape=none,label=right]
}
right->9->8->10
left->2->6->4
pivot->7
}
```
```c
while (p) {
node_t *n = p;
p = p->next;
list_add(n->value > value ? &right : &left, n);
}
```
</br>
將 `left` 串列也就是比 `pivot` 小的節點儲存在堆疊下方,比 `pivot` 大的節點儲存在堆疊下方,並且把 `left` 和 `right` 串列清空,之後重複執行前面的步驟,下圖為範例:
```graphviz
digraph{
rankdir=LR
node [shape=box,color=black]
{
NULL1 [shape=none,label=NULL]
NULL2 [shape=none,label=NULL]
left [shape=none,label="left"]
stack0 [shape=none,label="stack[0]"]
stack1 [shape=none,label="stack[1]"]
stack2 [shape=none,label="stack[2]"]
right [shape=none,label="right"]
pivot [shape=none,label="pivot"]
}
right->NULL1
left->NULL2
stack0->2->6->4
stack1->7
pivot->7
stack2->9->8->10
}
```
```c
begin[i] = left;
end[i] = list_tail(&left);
begin[i + 1] = pivot;
end[i + 1] = pivot;
begin[i + 2] = right;
end[i + 2] = list_tail(&right);
left = right = NULL;
i += 2;
```
</br></br>
### 測驗 `2`
## 第二週測驗題
[題目連結](https://hackmd.io/@sysprog/linux2024-quiz2)
### 測驗 `1`
題目為給定一個二元樹的前序(preorder)和中序(inorder),以此重建二元樹
**hash key 結構**
```graphviz
digraph G {
subgraph cluster_1 {
node [shape=record];
hn1 [label="{ {**pprev | *next } }"];
label="hlish_node";
}
}
```
**hash table 結構**
![image](https://hackmd.io/_uploads/SyAmmf9p6.png)
**使用的函式:**
**`hlist_add_head`**
此函式是為了將一個 hash_key 插入 `h->first`。
- 若列表不為空,將 `first->pprev` 也就是本來節點一的 `pprev` 指向 `n->next` , `n->next` 指向 `h->first` , `n->pprev` 指向 `&h->first` ,最後 `h->first` 指向 `n`,即完成將新的節點,加入 `first` 第一個指向的地點。
- 實際方式如下方圖示:
```graphviz
digraph G {
rankdir = LR;
node [shape=record]
{
table [label="hash_table | |<ht1>hlist_head.first 1 | |..."];
hn0 [label="hlist_node_new | { <prev>pprev | <next>next}"];
hn1 [label="hlist_node1 | { <prev>pprev | <next>next }"];
hn2 [label="hlist_node2 | { <prev>pprev | <next>next}"];
NULL1 [shape=none,label=NULL];
}
table:ht1 -> hn1;
hn1:next -> hn2;
hn1:prev -> table:ht1;
hn2:next -> NULL1;
hn2:prev -> hn1:next;
}
```
```graphviz
digraph G {
rankdir = LR;
node [shape=record]
{
table [label="hash_table | |<ht1>hlist_head.first 1 | |..."];
hn0 [label="hlist_node_new | { <prev>pprev | <next>next}"];
hn1 [label="hlist_node1 | { <prev>pprev | <next>next }"];
hn2 [label="hlist_node2 | { <prev>pprev | <next>next}"];
NULL1 [shape=none,label=NULL];
}
table:ht1 -> hn1;
hn1:next -> hn2;
hn1:prev -> hn0:next [color=red];
hn2:next -> NULL1;
hn2:prev -> hn1:next;
}
```
```graphviz
digraph G {
rankdir = LR;
node [shape=record]
{
table [label="hash_table | |<ht1>hlist_head.first 1 | |..."];
hn0 [label="hlist_node_new | { <prev>pprev | <next>next}"];
hn1 [label="hlist_node1 | { <prev>pprev | <next>next }"];
hn2 [label="hlist_node2 | { <prev>pprev | <next>next}"];
NULL1 [shape=none,label=NULL];
}
table:ht1 -> hn1;
hn0:next -> hn1 [color=red];
hn0:prev -> table:ht1 [color=red];
hn1:next -> hn2;
hn1:prev -> hn0:next;
hn2:next -> NULL1;
hn2:prev -> hn1:next;
}
```
```graphviz
digraph G {
rankdir = LR;
node [shape=record]
{
table [label="hash_table | |<ht1>hlist_head.first 1 | |..."];
hn0 [label="hlist_node_new | { <prev>pprev | <next>next}"];
hn1 [label="hlist_node1 | { <prev>pprev | <next>next }"];
hn2 [label="hlist_node2 | { <prev>pprev | <next>next}"];
NULL1 [shape=none,label=NULL];
}
table:ht1 -> hn0 [color=red];
hn1:next -> hn2;
hn1:prev -> hn0:next;
hn2:next -> null;
hn2:prev -> hn1:next;
hn0:next -> hn1;
hn0:prev -> table:ht1;
}
```
**`node_add`**
此函式目的為加入一個新的節點進入 hash table。
- 初始化 `order_node` 的記憶體空間,傳入 `idx` 和 `val`,使用 hash 函式將存入的位置打亂,hash 函式是將 `val` 絕對值後除餘 `size` 值
- 最後用 `hlist_add_head` 函式插入 hash table
- 下方以 hash table `size` 為 5 ,`val` 為 4 為例:
```graphviz
digraph G {
rankdir = LR;
node [shape=record]
{
rank="same"
hash[label = "<h0>hlist_head.first 0 | <h1>hlist_head.first 1 | <h2>hlist_head.first 2 | <h3>hlist_head.first 3 | <h4>hlist_head.first 4"];
tex[label="hash table",shape=plaintext,fontcolor=black];
}
subgraph cluster_1 {
node [shape=record];
hn1 [label="{ {**pprev | *next } }"];
label="hlish_node";
node1[label="{val=4 | idx=0}",fontcolor=black];
}
hash:h4->hn1:next;
hn1:next->hash:h4;
}
```
**`dfs`**
此函式為深度優先演算法,主要目的是建構二元樹。
- 先使用 `find` 函式查找 `tn` 在 `inorder` 內的位置 `idx`
- 接著利用 `idx` 就可以知道左子樹是`in_low` 到 `idx-1`,右子樹是 `idx+1` 到 `in_hight` 。
- 最後將左子樹和右子樹放進 `dfs` 函式,回傳 `tn`
**主程式:**
**`buildTree`**
此函式目的為根據給定的前序和中序,建構一個二元樹,也就是題目要求。
- 使用 hash table 的原因是為了將搜尋的時間複雜度下降,因此一開始會將所有的值加入到 hash table 中
- 遞迴呼叫 `dfs` 函式建構左右子樹
</br>
### 測驗 `2`
實作 [LRU Cache](https://leetcode.com/problems/lru-cache/)
測驗二的 hash table 程式碼與測驗一相同,因此不多贅述。
**LRUNode 結構**:LRU Cache 中 Node 的結構,其中包含 key-value 對、list 結構和 hlist 結構
```graphviz
digraph G {
rankdir = LR;
node [shape=record]
subgraph cluster_1 {
node [shape=record];
label="LRUNode";
hn [label="{ **pprev | *next }"];
lh [label="{ *prev | *next }"];
node1[label="{ key | value }"];
}
}
```
**LRUCache 結構**:整個 LRU Cache,包括容量、當前儲存的數量...等
```graphviz
digraph LRUCache {
rankdir = LR;
node [shape=record]
subgraph cluster_1 {
node [shape=record];
label="LRUCache";
node1[label="{ capacity | count }"];
lh [label="{ *prev | *next }"];
hn [label="{ hhead[] }"];
}
hln1 [label="{ **pprev | *next }"];
hln2 [label="{ **pprev | *next }"];
hn->hln1->hln2
}
```
**LRU Cache 使用的函式:**
**`lRUCacheCreate`**
此函式用於創建一個空的 LRUCache。
- 輸入參數為 `capacity` 也就是 cache 的容量( `hlist_head` 串列的長度 ),初始化所有的變數,回傳一個完整 LRU Cache
**`lRUCacheFree`**
此函式用於刪除整個 LRU Cache。
- 使用 `list_for_each_safe` 函式走訪每個節點,同時斷開目前節點與串列的連結,並且釋放此節點空間
**`lRUCacheGet`**
此函式用於取得 key 在 LRUCache 中所對應的 value。
- 先將輸入的 `key` 值除餘 `LRUCache->capacity` 得到變數 `hash`
- 使用 `hlist_for_each` 函式走訪 `LRUCache->hhead[hash]` 的每個節點
- 若有和輸入變數 `key` 值相同的節點,則將此節點使用 `list_move` 函式移動到 `hhead[hash]` 串列的第一個節點
**`lRUCachePut`**
此函式用於在 LRUCache 中放入 key-value。
- 前半部操作與 `lRUCacheGet` 相似,若此 key 已經存在,則將此節點移動到串列的第一個。
- 當節點不存在時,判斷 LRUCache 是否已滿 ( count == capacity ),若已滿則使用 `list_move` 函式將最後節點移至第一個節點,使用 `hlist_del` 函式刪除此節點,最後用 `hlist_add_head` 函式加入新的節點。
- LRUCache 沒滿則直接插入新節點即可
### 測驗 `3`
**`const_hweightn`**: n = 8, 16, 32, 64
這四個巨集用來計算在一段二進制數列中,共有幾個位元為 1
**`FIND_NTH_BIT`**
這個巨集用來查看第 N 個 bit 為 0 或 1。
- 先計算 word 中被設置的位元數目,比較查找第 num 個被設置的位元。若找到則回傳該位元的索引值;否則,繼續往下個位元找。
**`__ffs`**
此函式全名為 find first bit,目的為找到第一個位元數為 1 的位置。
- `num` 變數用來計算第一個位元 1 的位置。先檢查第一個位元是否在後 32 個 bits,如果是 `num` + 32
- 檢查第一個位元 1 的位置是否在後 16 個 bits,如果是 `num` + 16,依此類推,持續執行檢查到最後一位。
**`fns`**
此函式全名為 find N'th set bit,目的為找到第 N 個位元數為 1 的位置。
- 先呼叫 `__ffs` 函式找到第一個位元數 1
- 呼叫 `__clear_bit` 函式清除該位元並且將 N - 1,持續上述動作直到 N 為 0
**主程式:**
**`find_nth_bit`**
此函式目的為在一段二進制數列中,找到第 N 個位元為 1 的位置。
- 先檢查要查找的第 `n` 位是否超過傳入的參數 `size` 的範圍
- 使用 `small_const_nbits` 確認要搜索的範圍是否不超過一個 word,是的話使用 `fnc` 函式找到 bit 為 1 的位置
- 要搜索的範圍超過一個 word 時,使用 `FIND_NTH_BIT` 函式確認第一個 bit 為 1 的位置