owned this note
owned this note
Published
Linked with GitHub
# 2024q1 Homework2 (quiz1+2)
contributed by < `w96086123` >
## [第一週測驗](https://hackmd.io/@sysprog/linux2024-quiz1)
### 測驗一
#### 了解資料結構
``` C
typedef struct __node {
struct __node *left, *right;
struct __node *next;
long value;
} node_t;
```
`left` 代表比 value 小的集合。
`right` 代表比 value 大的集合。
`next` 表示此 node 的下一個 node 。
`value` 表示此 node 的數值。
#### 了解操作函數
##### list_add
將一個 node_t 加入至 list 的 head 。
##### list_tail
尋找此 node_t 中 left 的最後一個 node_t ,並且回傳該指標。
其中的 `AAAA` 為 (*left)->next。
##### list_length
尋找此 node_t 中 left 的長度。
其中的 `BBBB` 為 (*left)->next。
##### list_construct
建立一個 value 為 n 的 node_t ,並且將 node_t 加入至 list 的頭,且回傳該指標。
##### list_free
將 list 中的已配置記憶體的全部釋放。
##### list_is_ordered
檢查此 list 是否已完成排序。
##### shuffle
將 array 中的資料已亂數交換的方式打亂。
##### quick_sort
此方法利用堆疊取代原本的遞迴呼叫。
1. 將 DDDD 和 EEEE 完成,與了解 max_level 的數值定義
```c
// Area 1
begin[i] = left;
end[i] = DDDD;
// Area 2
begin[i + 1] = pivot;
end[i + 1] = pivot;
// Area 3
begin[i + 2] = right;
end[i + 2] = EEEE;
```
此區主要作為切割並且儲存的區域,因此我將此區分成三個區域了解。
```
區域一:
紀錄比 pivot 還要小的數值,因此範圍要為 left 至 list_tail(&left)。
因此 `DDDD` 為 list_tail(&left) 。
區域二:
紀錄 pivot 的位置,因此範圍要為 pivot 至 pivot 。
由於該位置已經進行分類確定好位置,因此 begin 和 end 為相同,在最後進行放置時,就可以判斷為可以放置的狀態了。
區域三:
紀錄比 pivot 還要大的數值,因此範圍要為 right 至 list_tail(&right)。
因此 `EEEE` 為 list_tail(&right) 。
```
藉由上面解析,可以知道每次的切割會新增兩個區塊,因此最大堆疊的空間需要 2 * n 。
2. 完成 CCCC
```C
while (p) {
node_t *n = p;
p = CCCC;
list_add(n->value > value ? &right : &left, n);
}
```
此區將 list 中進行分類。
逐一利用 `list_add` 進行分類,分類條件為 `n->value > value` ,若為真,則表示較大,將 n 分類為 right,否則,將 n 分類為 left 。
因此 `CCCC` 為 p->next 。
3. 將 1 與 2 一起觀看
原本的狀態
```graphviz
digraph G
{
rankdir=LR
node [shape=box];
NULL [shape=plaintext];
pivot [shape=plaintext, fontcolor=red];
L [shape=plaintext, fontcolor=green];
R [shape=plaintext, fontcolor=green];
4 -> 1;
1 -> 3;
3 -> 5;
5 -> 2;
2 -> 7;
7 -> NULL;
{
rank="same"
pivot -> 4;
L -> 4;
}
{
rank="same"
R -> 7;
}
}
```
進行分類過後的狀態
```graphviz
digraph G{
rankdir = LR;
node [shape=box];
NULL1[label="NULL", shape=plaintext]
NULL2[label="NULL", shape=plaintext]
NULL3[label="NULL", shape=plaintext]
Left [shape=plaintext, fontcolor=red];
Right [shape=plaintext, fontcolor=red];
pivot [shape=plaintext, fontcolor=red];
Left -> 1 -> 3 -> 2 -> NULL1;
pivot -> 4 -> NULL2;
Right -> 5 -> 7 -> NULL3;
}
```
4. 觀看整體
由於先進行 right 的切割,因此第一次將 L 儲存為 result 的一定為最大值,第二次則為第二大值,依序下來,直到堆疊的索引小於 0 ,結果必為完成排序。
#### main
建立一個長度為 100000 的 array ,順序則為 1 至 100000,並且利用 `shuffle` 進行打亂,後建立打亂後順序的 list ,並進行 `quick_sort` 排序,排序後利用 `list_is_ordered` 進行排序結果的判斷,最後利用 `list_free` 將記憶體釋放,並且釋放該 array 。
#### 延伸問題 1
因為每次的切割都需要使用兩次 `list_tail` ,但每次使用都需要 O(N) ,如果改成使用雙向鏈結串列的方式實做,時間複雜度將降低為 O(1) 。
##### 修改資料結構
``` C
typedef struct __node
{
struct __node *left, *right;
struct __node *prev, *next;
long value;
} node_t;
```
新增 `prev` 節點。
##### 修改操作函數
###### list_add
``` c
if (*list != NULL)
(*list)->prev = node_t;
node_t->next = *list;
node_t->prev = NULL;
*list = node_t;
```
###### list_tail
``` c
node_t *list_tail(node_t **left)
{
if (*left){
return (*left)->prev;
}else{
return *left;
}
}
```
藉由新的結構,可以直接訪問最後的節點,無須逐步訪問,因此時間複雜度可變為 O(1) 。
###### list_construct
``` c
node_t *list_construct(node_t *list, int n)
{
node_t *node = malloc(sizeof(node_t));
node->next = list;
node->prev = NULL;
node->value = n;
if (list != NULL)
list->prev = node;
return node;
}
```
由於修改為新的結構,因此在建立的過程中也需要做相對應的修改。
##### 比較(10000 個節點)
| | 單向鏈結 | 雙向鏈結 |
| -------- | -------- | -------- |
| 處理時間 (s) | 0.049148 | 0.036808 |
由上表可知,這樣得修改方式是有提昇處理時間。
#### 延伸問題 2
若想要避免最壞狀況,可以使用 Median of Three 的方式取得 pivot ,這樣可以避免一直取得最大或是最小值。
### 測驗二
#### Timsort 步驟
1. 先尋找 run
由左到右尋找非嚴格遞增或遞減的序列,每個 run 的長度盡量符合 minrun 跟 2 的冪。
2. 內部 run 進行插入排序法
3. 利用合併排序的方式進行合併
#### 程式碼解讀
##### find_run
## [第二週測驗](https://hackmd.io/@sysprog/linux2024-quiz2)
### 測驗一
#### 原版
利用深度優先搜尋的方式建立樹。
##### AAAA
``` c
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
if (h->first)
h->first->pprev = &n->next;
n->next = AAAA;
n->pprev = &h->first;
h->first = n;
}
```
此函數主要作用為將 n 插入至 h 的頭。
因此 `AAAA` 為 h->first 。
##### BBBB 和 CCCC
``` c
static int find(int num, int size, const struct hlist_head *heads)
{
struct hlist_node *p;
int hash = (num < 0 ? -num : num) % size;
hlist_for_each (p, BBBB) {
struct order_node *on = CCCC(p, struct order_node, node);
if (num == on->val)
return on->idx;
}
return -1;
}
```
此函數為尋找特定值。
`BBBB` 這裡要遍歷 hlist_node ,因此 `BBBB` 為 &heads 。
每次的遍歷都需要將當前指針轉換為 order_node,因此 `CCCC` 為 list_entry 。
##### DDDD
``` c
static inline void node_add(int val,
int idx,
int size,
struct hlist_head *heads)
{
struct order_node *on = malloc(sizeof(*on));
on->val = val;
on->idx = idx;
int hash = (val < 0 ? -val : val) % size;
hlist_add_head(&on->node, DDDD);
}
```
此函數主要將創立一個新的 order_node ,並且將此 node 放入置 hlist_head。
因此 `DDDD` 為 heads 。
##### dfs
``` c
static struct TreeNode *dfs(int *preorder,
int pre_low,
int pre_high,
int *inorder,
int in_low,
int in_high,
struct hlist_head *in_heads,
int size)
{
if (in_low > in_high || pre_low > pre_high)
return NULL;
struct TreeNode *tn = malloc(sizeof(*tn));
tn->val = preorder[pre_low];
int idx = find(preorder[pre_low], size, in_heads);
tn->left = dfs(preorder, pre_low + 1, pre_low + (idx - in_low), inorder,
in_low, idx - 1, in_heads, size);
tn->right = dfs(preorder, pre_high - (in_high - idx - 1), pre_high, inorder,
idx + 1, in_high, in_heads, size);
return tn;
}
```
利用深度優先搜尋法的方式建立 Tree。
先利用 `find` 尋找出 `preoder[pre_low]` 在 inorder 中的索引值,此索引值稱為 `idx` 。
切割方式:
左子樹:
preorder : pre_low + 1 至 pre_low + (idx - in_low) 。
(idx - in_low) 表示有多少個節點。
inorder : in_low 至 idx - 1 。
右子樹:
preorder : pre_high - (in_high - idx - 1) 至 pre_high 。
(in_high - idx - 1) 表示有多少個節點。
inorder : idx + 1 至 in_high 。
循環利用上面的方式,就可以將有 preorder 與 inorder 的序列,轉換為 Tree 了。
### 測驗二
#### 了解資料架構
##### LRUCache
``` c
typedef struct {
int capacity;
int count;
struct list_head dhead;
struct hlist_head hhead[];
} LRUCache;
```
`capacity` 用於儲存 LRU 緩存的容量。
`count` 用於紀錄當前 LRU 緩存中儲存的數量。
`dhead` 用於紀錄 LRU 中節點的訪問順序,用於尋找替換順序。
`hhead` 用於紀錄 LRU 中節點的哈希表連結。
##### LRUNode
``` c
typedef struct {
int key;
int value;
struct hlist_node node;
struct list_head link;
} LRUNode;
```
`key` 節點的鍵值。
`value` 節點的值。
`node` 與其他節點的指標。
`link` 用於與 LRU 的連結。
### 測驗三