# 2024q1 Homework2 (quiz1+2) contributed by < [`Wufangni`](https://github.com/Wufangni) > ## 第一週測驗題 ### 測驗 1 #### list_tail ``` c node_t *list_tail(node_t **left) { while ((*left) && (*left)->next) left = &((*left)->next); return *left; } ``` 找出 list 最尾端的節點,```*left``` 指向 list 中的第一個節點依序往下走,直到 ```*left->next``` 遇到 NULL 為止。 ```graphviz digraph name{ node[shape=box] rankdir = LR n1[label=node1] n2[label=node2] NULL[label=NULL,shape=plaintext] n1 -> n2 ->NULL { rank="same"; L[label="**left", shape=plaintext, fontcolor=blue] left[label="*left", shape=plaintext, fontcolor=blue] left -> n1[color=blue] L -> left[color=blue]; } { rank="same"; left_n[label="(*left)->next", shape=plaintext, fontcolor=blue] left_n -> n2[color=blue] ; } } ``` ```graphviz digraph name{ node[shape=box] rankdir = LR n1[label=node1] n2[label=node2] NULL[label=NULL,shape=plaintext] n1 -> n2 ->NULL { rank="same"; L[label="**left", shape=plaintext, fontcolor=blue] left[label="*left", shape=plaintext, fontcolor=blue] left -> n2[color=blue] L -> left[color=blue]; } { rank="same"; left_n[label="(*left)->next", shape=plaintext, fontcolor=red] left_n -> NULL[color=red] ; } } ``` #### list_length ``` c int list_length(node_t **left) { int n = 0; while (*left) { ++n; left = &((*left)->next); } return n; } ``` 與上述方法類似,因為計算總節點數須走完全部節點所以判斷條件到 ```*left``` 指到 NULL 為止。 #### quick_sort ``` c void quick_sort(node_t **list) { 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); while (i >= 0) { node_t *L = begin[i], *R = end[i]; if (L != R) { node_t *pivot = L; value = pivot->value; node_t *p = pivot->next; pivot->next = NULL; while (p) { node_t *n = p; p = p->next; list_add(n->value > value ? &right : &left, n); } 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; } else { if (L) list_add(&result, L); i--; } } *list = result; } ``` begin[0] 和 end[0] 初始值設置為 list 的頭及尾端,分別以 L 及 R 兩個 ```*node_t``` 型態包裝,先將掃過 list 將小於 pivot 的節點以 list_add 方式加到 left 前端,若大於 pivot 則加到 right 前端。 - Step 1 begin[0] 及 end[0] 紀錄 left 頭及尾端節點, begin[1] 及 end[1] 紀錄當前 pivot 節點,begin[2] 及 end[2] 紀錄 right 頭及尾端節點。 ```graphviz digraph name{ node[shape=box] rankdir = LR n11[label=7] n12[label=5] n10[label=4] n7[label=2] n8[label=3] n9[label=1] n1[label=4] n2[label=1] n3[label=3] n4[label=5] n5[label=2] n6[label=7] n1 -> n2 -> n3 -> n4 -> n5 -> n6 n7 -> n8 -> n9 n10 n11 -> n12 { rank="same"; pivot[label="pivot", shape=plaintext, fontcolor=red] L[label="L", shape=plaintext] L -> n1 pivot -> n1[color=red] } { rank="same"; R[label="R", shape=plaintext] R -> n6 } { rank="same"; begin0[label="begin[0]", shape=plaintext] begin0 -> n7 } { rank="same"; end0[label="end[0]", shape=plaintext] end0 -> n9 } { rank="same"; end1[label="begin[1] \\ end[1]", shape=plaintext] end1 -> n10 } { rank="same"; begin2[label="begin[2]", shape=plaintext] begin2 -> n11 } { rank="same"; end2[label="end[2]", shape=plaintext] end2 -> n12 } } ``` - Step 2 重複上一步驟,pivot 變為 L 所在節點。 ```graphviz digraph name{ node[shape=box] rankdir = LR n11[label=7] n10[label=5] n1[label=7] n2[label=5] n1 -> n2 n10 n11 { rank="same"; pivot[label="pivot", shape=plaintext, fontcolor=red] L[label="L", shape=plaintext] L -> n1 pivot -> n1[color=red] } { rank="same"; R[label="R", shape=plaintext] R -> n2 } { rank="same"; end1[label="begin[2] \\ end[2]", shape=plaintext] end1 -> n10 } { rank="same"; begin2[label="begin[3] \\ end[3]", shape=plaintext] begin2 -> n11 } } ``` - Step 3 此時 begin[3] 及 end[3] 都指向同一節點,表示只有一個節點在 begin[3] 當中(後半部分已完成排序),將 L 加入 result 前端再繼續往下判斷。 ```graphviz digraph name{ node[shape=box] rankdir = LR n1[label=4] n2[label=5] n3[label=7] n1 -> n2 -> n3 { rank="same"; L[label="result", shape=plaintext] L -> n1 } } ``` - Step 4 begin[0] 及 end[0] 不相等,將 list 從頭到尾掃一次,比 pivot 小記錄到 begin[0],反之則記錄到 begin[2]。 ```graphviz digraph name{ node[shape=box] rankdir = LR n12[label=3] n11[label=2] n10[label=1] n7[label=2] n8[label=3] n9[label=1] n7 -> n8 -> n9 n10 { rank="same"; pivot[label="pivot", shape=plaintext, fontcolor=red] L[label="L", shape=plaintext] L -> n7 pivot -> n7[color=red] } { rank="same"; R[label="R", shape=plaintext] R -> n9 } { rank="same"; end0[label="begin[0] \\ end[0]", shape=plaintext] end0 -> n10 } { rank="same"; end1[label="begin[1] \\ end[1]", shape=plaintext] end1 -> n11 } { rank="same"; end2[label="begin[2] \\ end[2]", shape=plaintext] end2 -> n12 } } ``` - Step 5 因 begin[0]~begin[2] 都只有一個節點,依序將 L 加入 result。 ```graphviz digraph name{ node[shape=box] rankdir = LR n1[label=1] n2[label=2] n3[label=3] n4[label=4] n5[label=5] n6[label=7] n1 -> n2 -> n3 -> n4 -> n5 -> n6 { rank="same"; L[label="result", shape=plaintext] L -> n1 } } ``` #### `改進方法` 若遇到特殊情況(例如降冪排序或升冪排序),每次故左邊第一個做 `pivot` 會導致 worse case 產生,即時間複雜度為 $O(n^2)$,在挑選 `pivot` 時參考 [`ChiTingCheng`](https://hackmd.io/@r36EKfH2TwalEWG_fUuSyg/linux2024-homework2) 同學的改進方案,每輪重新選擇 `pivot` 時以隨機取代固定挑選,更大程度避免陷入 worse case 。 ``` c node_t *pivot = pivot_select(L); ``` ### 測驗 2 #### timesort ``` c #include <stdint.h> #include <stdlib.h> #include "list.h" #include "sort_impl.h" static inline size_t run_size(struct list_head *head) { if (!head) return 0; if (!head->next) return 1; return (size_t) (head->next->prev); } struct pair { struct list_head *head, *next; }; static size_t stk_size; static struct list_head *merge(void *priv, list_cmp_func_t cmp, struct list_head *a, struct list_head *b) { struct list_head *head; struct list_head **tail = &head; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { *tail = a; tail = &a->next; a = a->next; if (!a) { *tail = b; break; } } else { *tail = b; tail = &b->next; b = b->next; if (!b) { *tail = a; break; } } } return head; } static void build_prev_link(struct list_head *head, struct list_head *tail, struct list_head *list) { tail->next = list; do { list->prev = tail; tail = list; list = list->next; } while (list); /* The final links to make a circular doubly-linked list */ tail->next = head; head->prev = tail; } static void merge_final(void *priv, list_cmp_func_t cmp, struct list_head *head, struct list_head *a, struct list_head *b) { struct list_head *tail = head; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { tail->next = a; a->prev = tail; tail = a; a = a->next; if (!a) break; } else { tail->next = b; b->prev = tail; tail = b; b = b->next; if (!b) { b = a; break; } } } /* Finish linking remainder of list b on to tail */ build_prev_link(head, tail, b); } static struct pair find_run(void *priv, struct list_head *list, list_cmp_func_t cmp) { size_t len = 1; struct list_head *next = list->next, *head = list; struct pair result; if (!next) { result.head = head, result.next = next; return result; } if (cmp(priv, list, next) > 0) { /* decending run, also reverse the list */ struct list_head *prev = NULL; do { len++; list->next = prev; prev = list; list = next; next = list->next; head = list; } while (next && cmp(priv, list, next) > 0); list->next = prev; } else { do { len++; list = next; next = list->next; } while (next && cmp(priv, list, next) <= 0); list->next = NULL; } head->prev = NULL; head->next->prev = (struct list_head *) len; result.head = head, result.next = next; return result; } static struct list_head *merge_at(void *priv, list_cmp_func_t cmp, struct list_head *at) { size_t len = run_size(at) + run_size(at->prev); struct list_head *prev = at->prev->prev; struct list_head *list = merge(priv, cmp, at->prev, at); list->prev = prev; list->next->prev = (struct list_head *) len; --stk_size; return list; } static struct list_head *merge_force_collapse(void *priv, list_cmp_func_t cmp, struct list_head *tp) { while (stk_size >= 3) { if (run_size(tp->prev->prev) < run_size(tp)) { tp->prev = merge_at(priv, cmp, tp->prev); } else { tp = merge_at(priv, cmp, tp); } } return tp; } static struct list_head *merge_collapse(void *priv, list_cmp_func_t cmp, struct list_head *tp) { int n; while ((n = stk_size) >= 2) { if ((n >= 3 && run_size(tp->prev->prev) <= run_size(tp->prev) + run_size(tp)) || (n >= 4 && run_size(tp->prev->prev->prev) <= run_size(tp->prev->prev) + run_size(tp->prev))) { if (run_size(tp->prev->prev) < run_size(tp)) { tp->prev = merge_at(priv, cmp, tp->prev); } else { tp = merge_at(priv, cmp, tp); } } else if (run_size(tp->prev) <= run_size(tp)) { tp = merge_at(priv, cmp, tp); } else { break; } } return tp; } void timsort(void *priv, struct list_head *head, list_cmp_func_t cmp) { stk_size = 0; struct list_head *list = head->next, *tp = NULL; if (head == head->prev) return; /* Convert to a null-terminated singly-linked list. */ head->prev->next = NULL; do { /* Find next run */ struct pair result = find_run(priv, list, cmp); result.head->prev = tp; tp = result.head; list = result.next; stk_size++; tp = merge_collapse(priv, cmp, tp); } while (list); /* End of input; merge together all the runs. */ tp = merge_force_collapse(priv, cmp, tp); /* The final merge; rebuild prev links */ struct list_head *stk0 = tp, *stk1 = stk0->prev; while (stk1 && stk1->prev) stk0 = stk0->prev, stk1 = stk1->prev; if (stk_size <= FFFF) { build_prev_link(head, head, stk0); return; } merge_final(priv, cmp, head, stk1, stk0); } ``` ## 第二週測驗題 ### 測驗 1 #### hlist_add_head ``` 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 = h->first; n->pprev = &h->first; h->first = n; } ``` - Step1: 如果 ```h->first``` 存在,則將此節點的 ```pprev``` 指向 ```&n->next``` <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/rJfzk-9T6.png" alt="Image Alt Text" style="width: 70%; height: auto;margin: 10px;"> </div> - Step2: 新增 n 節點的 ```*next``` 指向 ```h->first``` <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BJuq1b9aa.png" alt="Image Alt Text" style="width: 70%; height: auto;margin: 10px;"> </div> - Step3: n 節點的 ```*pprev``` 指向 ```&h->first``` <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/B1MMxZ5pa.png" alt="Image Alt Text" style="width: 70%; height: auto;margin: 10px;"> </div> - Step4: ```h->first``` 指向 n 節點 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/HkhOgZ5aa.png" alt="Image Alt Text" style="width: 70%; height: auto;margin: 10px;"> </div> #### find 計算 ```hash``` 值後用 ```hlist_for_each``` 從對應雜湊值 ```&heads[hash]``` 開始往下找尋,使用 ```container_of``` 找出此節點的實體位置跟 ```num``` 進行比對,若相同則回傳該節點的 ```inx``` 。 ``` 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, &heads[hash]) { struct order_node *on = container_of(p, struct order_node, node); if (num == on->val) return on->idx; } return -1; } ``` #### node_add 使用 ```malloc``` 分配空間給 ```*on``` ,填入對應的 ```val``` 及 ```idx``` 並計算 ```hash``` 值,將其加入 ```&heads[hash]``` 位置。 ``` 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, &heads[hash]); } ``` #### dfs 利用中序數列創建一個以中序排列的鏈結串列 ```*in_heads``` 。 ```c struct hlist_head *in_heads = malloc(inorderSize * sizeof(*in_heads)); for (int i = 0; i < inorderSize; i++) INIT_HLIST_HEAD(&in_heads[i]); for (int i = 0; i < inorderSize; i++) node_add(inorder[i], i, inorderSize, in_heads); ``` 用 ```find``` 找到在中序裡的 ```idx``` ,使用 ```pre_low``` \ ```pre_high``` \ ```in_low``` \ ```in_high``` 分別對左子樹及右子樹做遞迴。 ``` 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; } ``` - 左子樹 1. ```pre_low``` : 為原本自己再加一,根據前序性質為根節點的下一節點。 2. ```pre_high``` : 使用 ```idx - in_low``` 計算出根節點左子樹節點數,從 ```pre_low``` 往後加即為前序最高索引點。 3. ```in_low``` : 原先最低索引位置。 4. ```in_high``` : 根節點左邊位置。 - 右子樹 1. ```pre_low``` : ```in_high - idx``` 算出根結點右子樹節點數,再以原最高索引點 ```pre_high``` 減掉右邊總數再加回一,代表前序最低索引點位置。 2. ```pre_high``` : 原前序最高索引點。 3. ```in_low``` : 根節點右邊位置。 4. ```in_high``` : 原中序最高索引位置。 ### 測驗 2 #### Least Recently Used (LRU) 實作出 LRU 策略的雜湊儲存,先介紹 ```LRUCache``` 和 ```LRUNode``` 兩種結構。 ``` c struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; typedef struct { int capacity; int count; struct list_head dhead; struct hlist_head hhead[]; } LRUCache; typedef struct { int key; int value; struct hlist_node node; struct list_head link; } LRUNode; ``` ```LRUCache``` 中使用了 ```list_head``` 結構的 ```dhead``` 用來串接最近一次存取的節點, ```hhead[]``` 則用來串接雜湊的 ```LRUNode``` 。 ```graphviz digraph { rankdir=LR; subgraph cluster_1{ node [shape=record]; label = "LRUCache" list_head [label = "<h> dhead|{<next> next|<prev> prev}", group = list]; other[label = "<cap> capacity"] other2[label = "<count> count"] hlist_head [label = "<h> hhead[]|...|... |... "]; } } ``` ```graphviz digraph { rankdir=LR; subgraph cluster_1{ node [shape=record]; label = "LRUNode" other[label = "<cap> key"] other2[label = "<count> value"] list_head [label = "<h> link|{<prev> prev | <next> next}", group = list]; hlist_head [label = "<h> node|{<prev> pprev | <next> next}"]; } } ``` #### lRUCacheCreate 使用 ```malloc``` 分配空間給 ```*cache``` ,將兩型態為 ```int``` 的變數初始值定義完後使用 ```INIT_LIST_HEAD``` 初始化 ```dhead``` 及 ```hhead[]``` 。 ``` c LRUCache *lRUCacheCreate(int capacity) { LRUCache *cache = malloc(2 * sizeof(int) + sizeof(struct list_head) + capacity * sizeof(struct hlist_head)); cache->capacity = capacity; cache->count = 0; INIT_LIST_HEAD(&cache->dhead); for (int i = 0; i < capacity; i++) INIT_HLIST_HEAD(&cache->hhead[i]); return cache; } ``` #### lRUCacheFree 使用 ```list_for_each_safe``` 逐一釋放 ```dhead``` 串聯的 ```LRUNode``` , 最後釋放整個 ```LRUCache``` 。 ``` c void lRUCacheFree(LRUCache *obj) { struct list_head *pos, *n; list_for_each_safe (pos, n, &obj->dhead) { LRUNode *cache = list_entry(pos, LRUNode, link); list_del(&cache->link); free(cache); } free(obj); } ``` #### lRUCacheGet 利用 ```key``` 的值除以總容量取餘數算出雜湊值 ```hash``` ,從 ```&obj->hhead[hash]``` 開始找若此 ```LRUNode``` 的值與 ```key``` 相符,則從 ```dhead``` 中移除,並回傳此 ```LRUNode``` 的 ```value``` 。 ``` c int lRUCacheGet(LRUCache *obj, int key) { int hash = key % obj->capacity; struct hlist_node *pos; hlist_for_each (pos, &obj->hhead[hash]) { LRUNode *cache = list_entry(pos, LRUNode, node); if (cache->key == key) { list_move(&cache->link, &obj->dhead); return cache->value; } } return -1; } ``` #### lRUCachePut 先計算出雜湊值 ```hash``` ,從 ```&obj->hhead[hash]``` 逐一找尋若有相同資料已存在在 ```*obj``` 中則從 ```&obj->dhead``` 中移除,無相同值需判斷目前容量是否已滿。 容量已滿的情況,將 ```dhead``` 最前端紀錄的 ```LRUNode``` (表示最久未被存取的資料)移除,並將新節點加入 ```&obj->hhead[hash]``` 位置。 尚有容量則直接分配記憶體空間給新增節點,將其加入 ```&obj->hhead[hash]``` 位置並更新 ```&obj->dhead``` 。 ``` c void lRUCachePut(LRUCache *obj, int key, int value) { LRUNode *cache = NULL; int hash = key % obj->capacity; struct hlist_node *pos; hlist_for_each (pos, &obj->hhead[hash]) { LRUNode *c = list_entry(pos, LRUNode, node); if (c->key == key) { list_move(&c->link, &obj->dhead); cache = c; } } if (!cache) { if (obj->count == obj->capacity) { cache = list_last_entry(&obj->dhead, LRUNode, link); list_move(&cache->link, &obj->dhead); hlist_del(&cache->node); hlist_add_head(&cache->node, &obj->hhead[hash]); } else { cache = malloc(sizeof(LRUNode)); hlist_add_head(&cache->node, &obj->hhead[hash]); list_add(&cache->link, &obj->dhead); obj->count++; } cache->key = key; } cache->value = value; } ``` ### 測驗3 #### __ffs 先以 ```#if BITS_PER_LONG == 64``` 判斷是否有 64 位元,可一次與 ```0xffffffff``` 做 ```&``` 判斷是否吋在 ```set bit``` ,接著由高往低判斷一次word可前進多少,最後回傳第一個遇到的 ```set bit``` 位置。 ```c #if BITS_PER_LONG == 64 if ((word & 0xffffffff) == 0) { num += 32; word >>= 32; } #endif if ((word & 0xffff) == 0) { num += 16; word >>= 16; } if ((word & 0xff) == 0) { num += 8; word >>= 8; } if ((word & 0xf) == 0) { num += 4; word >>= 4; } if ((word & 0x3) == 0) { num += 2; word >>= 2; } if ((word & 0x1) == 0) num += 1; return num; } ``` #### __clear_bit 使用 ```BIT_MASK``` 建立只有第 nr 的 bit 為 1 的 ```mask``` ,對 ```*p``` 與 ```~mask``` 做 ```&``` 得到只有第 nr 個 bit 被清空的狀態。 ``` c static inline void __clear_bit(unsigned long nr, volatile unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = ((unsigned long *) addr) + BIT_WORD(nr); *p &= ~mask; } ``` #### FIND_NTH_BIT ``` c #define FIND_NTH_BIT(FETCH, size, num) \ ({ \ unsigned long sz = (size), nr = (num), idx, w, tmp; \ \ for (idx = 0; (idx + 1) * BITS_PER_LONG <= sz; idx++) { \ if (idx * BITS_PER_LONG + nr >= sz) \ goto out; \ \ tmp = (FETCH); \ w = hweight_long(tmp); \ if (w > nr) \ goto found; \ \ nr -= w; \ } \ \ if (sz < BITS_PER_LONG) \ tmp = (FETCH) & BITMAP_LAST_WORD_MASK(sz); \ found: \ sz = min(idx * BITS_PER_LONG + fns(tmp, nr), sz); \ out: \ sz; \ }) ```