# 2025q1 Homework2 (quiz1+2) contributed by < `Brianpan` > {%hackmd NrmQUGbRQWemgwPfhzXj6g %} Repo: [Link](https://github.com/Brianpan/kernel-hw2) # Quiz 1 ## Q1 題目要寫一個list_insert_before函式, p是指到next指標的地址,所以終止條件是 *p 代表指到的那個元素 ```cpp! static inline void list_insert_before(list_t *l, list_item_t *before, list_item_t *item) { list_item_t **p; for (p = &l->head; *p != before || *p != NULL; p = &(*p)->next) ; *p = item; item->next = before; } ``` 延伸問題: 撰寫合併排序 [commit](https://github.com/Brianpan/kernel-hw2/blob/03707a9fbd2a362b18c931f4b73742429d0971f1/list.c#L44) ## Q2 #### 演算法精神 演算法主要是在做移除符合適當大小的節點,這個設計沒有考慮樹的再平衡 移除的情況有三種 1. 左右子節點皆存在 2. 只有一個左右子節點 3. 沒有左右子節點 情況2,3比較直觀 情況2直接把那個子節點拉上來 ```cpp! block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r; *node_ptr = child; ``` 情況3就直接把目標節點從樹中移除 ```cpp! *node_ptr = NULL ``` 情況1的狀況是要思考這個刪除的節點要怎麼取代 有兩種方法 1. 左子樹最大值的節點 2. 右子樹最小值的節點 本題實作採取1.的做法 接下來找最右節點又有兩種情況 1. 直接是刪除節點的左節點 2. 不是左節點 是情況1.好解決,我們直接把刪除的節點的指標指給左節點 ```cpp! block_t *old_right = (*node_ptr)->r; *node_ptr = *pred_ptr; / ``` 情況2.以這個子樹為例我們要刪除9, 我們找到最大右節點5 做以下步驟 - 紀錄9的左右子節點 - 把5節點從樹中移除,這個動作代表5的左節點會取代5的位置 - 把5節點取代9節點的位置 ```graphviz digraph BST { node [shape=circle]; 9 -> 3; 9 -> 10; 3 -> 2; 5 [style=filled fillcolor=red]; 3 -> 5; 5 -> 4; } ``` 實作中透過指標的指標去間接更新刪除節點的母節點指標 #### 實作 找左子樹最右邊的節點 ```cpp! /* * Structure representing a free memory block in the memory allocator. * The free tree is a binary search tree that organizes free blocks (of type block_t) * to efficiently locate a block of appropriate size during memory allocation. */ void remove_free_tree(block_t **root, block_t *target) { /* Locate the pointer to the target node in the tree. */ block_t **node_ptr = find_free_tree(root, target); /* If the target node has two children, we need to find a replacement. */ if ((*node_ptr)->l && (*node_ptr)->r) { /* Find the in-order predecessor: * This is the rightmost node in the left subtree. */ block_t **pred_ptr = &(*node_ptr)->l; while (*pred_ptr && (*pred_ptr)->right) pred_ptr = &(*pred_ptr)->r; /* Verify the found predecessor using a helper function (for debugging). */ block_t *expected_pred = find_predecessor_free_tree(root, *node_ptr); assert(expected_pred == *pred_ptr); /* If the predecessor is the immediate left child. */ if (*pred_ptr == (*node_ptr)->l) { block_t *old_right = (*node_ptr)->r; *node_ptr = *pred_ptr; /* Replace target with its left child. */ (*node_ptr)->r = old_right; /* Attach the original right subtree. */ assert(*node_ptr != (*node_ptr)->l); assert(*node_ptr != (*node_ptr)->r); } else { /* The predecessor is deeper in the left subtree. */ block_t *old_left = (*node_ptr)->l; block_t *old_right = (*node_ptr)->r; block_t *pred_node = *pred_ptr; /* Remove the predecessor from its original location. */ remove_free_tree(&old_left, *pred_ptr); /* Replace the target node with the predecessor. */ *node_ptr = pred_node; (*node_ptr)->l = old_left; (*node_ptr)->r = old_right; assert(*node_ptr != (*node_ptr)->l); assert(*node_ptr != (*node_ptr)->r); } } /* If the target node has one child (or none), simply splice it out. */ else if ((*node_ptr)->l || (*node_ptr)->r) { block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r; *node_ptr = child; } else { /* No children: remove the node. */ *node_ptr = NULL; } /* Clear the removed node's child pointers to avoid dangling references. */ target->l = NULL; target->r = NULL; } ``` ### 補完程式碼 https://github.com/Brianpan/kernel-hw2/blob/master/q2.c ### 閱讀筆記 TBD ## Q3 ### QuickSort精神 1. 找一個pivot值,將整個陣列或串列分成兩個部分,左半邊都小於等於pivot,右半邊都大於pivot 2. 向下分成左右兩個子問題,繼續做步驟1的操作,直到只剩下一個元素 3. 合併左右兩邊和pivot成為一個排序的陣列或串列 #### 非遞迴版本實作 ##### 概念 想像一個虛擬的堆疊用迴圈迭代確認是否為空 這個例子是用begin,end陣列指到的值 初始狀態是 ```cpp! begin[0] = *list; end[0] = list_tail(list); ``` 每一輪結束後的更新狀態是 ```cpp! 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); ``` 收斂是當L==R, 像是pivot那一點就是確認已排序 每個回合結束後會i+2,換句話說我們先排右邊的序列 ##### 圖解 初始狀態 ```graphviz digraph g1 { node[shape=plaintext]; "begin[0]" [fontcolor="brown"]; "end[0]" [fontcolor="brown"]; pivot [fontcolor="red"]; L [fontcolor="blue"]; R [fontcolor="blue"]; p [fontcolor="black"]; l0 [label ="NULL", fontcolor="black"]; node[shape=box]; n0 [label= "0"]; n1 [label= "1"]; n2 [label= "2"]; n3 [label= "3"]; n4 [label= "4"]; { rank="same"; n3 -> n1 -> n0 -> n2 -> n4 -> l0 } "begin[0]" -> n3; "end[0]" -> n4; pivot -> n3; L -> n3; R -> n4; p -> n1; } ``` 把pivot節點從串列移除 ```graphviz digraph g2 { node[shape=plaintext]; "left" [fontcolor="purple"]; "right" [fontcolor="purple"]; pivot [fontcolor="red"]; L [fontcolor="blue"]; R [fontcolor="blue"]; p [fontcolor="black"]; l0 [label ="NULL", fontcolor="black"]; l1 [label ="NULL", fontcolor="black"]; l2 [label ="NULL", fontcolor="black"]; l3 [label ="NULL", fontcolor="black"]; node[shape=box]; n0 [label= "0"]; n1 [label= "1"]; n2 [label= "2"]; n3 [label= "3"]; n4 [label= "4"]; { rank="same"; n1 -> n0 -> n2 -> n4 -> l0 } pivot -> n3 -> l1; left -> l2; right -> l3; L -> n3; R -> n4; p -> n1; } ``` p處理完節點1 ```graphviz digraph g3 { node[shape=plaintext]; "left" [fontcolor="purple"]; "right" [fontcolor="purple"]; pivot [fontcolor="red"]; L [fontcolor="blue"]; R [fontcolor="blue"]; p [fontcolor="black"]; l0 [label ="NULL", fontcolor="black"]; l1 [label ="NULL", fontcolor="black"]; l2 [label ="NULL", fontcolor="black"]; l3 [label ="NULL", fontcolor="black"]; node[shape=box]; n0 [label= "0"]; n1 [label= "1"]; n2 [label= "2"]; n3 [label= "3"]; n4 [label= "4"]; { rank="same"; n0 -> n2 -> n4 -> l0 } pivot -> n3 -> l1; left -> n1 -> l2; right -> l3; L -> n3; R -> n4; p -> n0; } ``` p處理完節點0 ```graphviz digraph g4 { node[shape=plaintext]; "left" [fontcolor="purple"]; "right" [fontcolor="purple"]; pivot [fontcolor="red"]; L [fontcolor="blue"]; R [fontcolor="blue"]; p [fontcolor="black"]; l0 [label ="NULL", fontcolor="black"]; l1 [label ="NULL", fontcolor="black"]; l2 [label ="NULL", fontcolor="black"]; l3 [label ="NULL", fontcolor="black"]; node[shape=box]; n0 [label= "0"]; n1 [label= "1"]; n2 [label= "2"]; n3 [label= "3"]; n4 [label= "4"]; { rank="same"; n2 -> n4 -> l0 } pivot -> n3 -> l1; left -> n1 -> n0 -> l2; right -> l3; L -> n3; R -> n4; p -> n2; } ``` p處理完所有節點 ```graphviz digraph g5 { node[shape=plaintext]; "left" [fontcolor="purple"]; "right" [fontcolor="purple"]; pivot [fontcolor="red"]; L [fontcolor="blue"]; R [fontcolor="blue"]; p [fontcolor="black"]; l0 [label ="NULL", fontcolor="black"]; l1 [label ="NULL", fontcolor="black"]; l2 [label ="NULL", fontcolor="black"]; l3 [label ="NULL", fontcolor="black"]; node[shape=box]; n0 [label= "0"]; n1 [label= "1"]; n2 [label= "2"]; n3 [label= "3"]; n4 [label= "4"]; { rank="same"; } left -> n1 -> n0 -> n2 -> l0; pivot -> n3 -> l1; right -> l2; L -> n3; R -> n4; p -> n4; } ``` 更新begin[i], end[i]指標 意義就是下一輪的左右邊界 ```graphviz digraph g6 { node[shape=plaintext]; "begin[0]" [fontcolor="brown"]; "end[0]" [fontcolor="brown"]; "begin[1]" [fontcolor="brown"]; "end[1]" [fontcolor="brown"]; "begin[2]" [fontcolor="brown"]; "end[2]" [fontcolor="brown"]; "left" [fontcolor="purple"]; "right" [fontcolor="purple"]; pivot [fontcolor="red"]; L [fontcolor="blue"]; R [fontcolor="blue"]; l1 [label ="NULL", fontcolor="black"]; l2 [label ="NULL", fontcolor="black"]; l3 [label ="NULL", fontcolor="black"]; node[shape=box]; n0 [label= "0"]; n1 [label= "1"]; n2 [label= "2"]; n3 [label= "3"]; n4 [label= "4"]; { rank="same"; } "begin[0]" -> n1; "end[0]" -> n2; "begin[1]" -> n3; "end[1]" -> n3; "begin[2]" -> n4; "end[2]" -> n4; pivot -> n3 -> l1; left -> n1 -> n0 -> n2 -> l3; right -> n4 -> l2; L -> n3; R -> n4; } ``` ### 實作 #### 單向串列版本 [連結](https://github.com/Brianpan/kernel-hw2/blob/master/q3.c) ```cpp! 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; 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; } ``` #### 雙向串列版本 [連結](https://github.com/Brianpan/kernel-hw2/blob/master/q3_2.c) 此版本和單向串列的主要差別 - 只使用一個begin陣列去模擬堆疊 - 每次分割左右兩個串列前要把環狀串列最後指標給移除 - 這個實作最後要把雙向串列的prev指標更新回正確的地址 # Quiz2 ## Q1 ### 題目精神 - [x] 利用list API實作quicksort - [ ] 實作有效亂數 ### 補完程式碼 [程式碼](https://github.com/brianpan/kernel-hw2/blob/master/q4.c) #### 實作quicksort ```cpp! static void list_quicksort(struct list_head *head) { struct list_head list_less, list_greater; struct listitem *pivot; struct listitem *item = NULL, *is = NULL; if (list_empty(head) || list_is_singular(head)) return; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); // get the pivot and remove it from the circular list pivot = list_first_entry(head, struct listitem, list); list_del(&pivot->list); // move item to either list_less or list_greater list list_for_each_entry_safe (item, is, head, list) { if (cmpint(&item->i, &pivot->i) < 0) list_move_tail(&item->list, &list_less); else list_move_tail(&item->list, &list_greater); } // divide part // recusively sort left, right sublist list_quicksort(&list_less); list_quicksort(&list_greater); // merge phase list_add(&pivot->list, head); // list_less to the beginning of the list_head list_splice(&list_less, head); // list_greater to the end of the list_head list_splice_tail(&list_greater, head); } ``` #### 測試程式碼 更改原本的實作,使用比較前後元素確認排序是否成功 ```cpp! i = 0; list_for_each_entry_safe (item, is, &testlist, list) { if (is) assert(cmpint(&item->i, &is->i) < 0); list_del(&item->list); free(item); i++; } ``` #### 延伸問題 ##### list_for_each_entry_safe 建構的迴圈中,若將 list_move_tail 換為 list_move,為何會無法滿足 stable sorting? **stable sorting** 假設排序中有兩個元素在比較下是相同的 cmp(a, b) == 0, stable sorting會保證排序後相同排序大小的元素有按照原本的出現順序 $[1_1, 2, 1_2]$ -> $[1_1, 1_2, 2]$ 回到原本的問題為什麼無法滿足stable sorting, 那是因為使用list_move就讓原本的順序相反,因為是往前插入 ##### 實作整合至lab0c [commit](https://github.com/Brianpan/lab0-c/commit/e94a5ef0ac32b62766c06587e5b75a688cc801ed) 使用quicksort實作有兩個perf test沒有跑過 ##### 分析 TBD ## Q2 ### 題目精神 - [x] 認識位元操作API: clz, ctz - [x] 位元操作 - [ ] 計算平方根 ### clz實作 #### 演算法精神 從長度32開始每次遞迴都把長度減半 終止條件:剩下兩位元時查表 magic number代表在2bit時候的快速查表 - clz(0) : 2 - clz(1) : 1 - clz(2) : 0 - clz(3) : 0 ```cpp! // divide and conquer width static const int mask[] = {0, 8, 12, 14}; // magic numbers for 2 bits // 0 -> 2 // 1 -> 1 // 2 -> 0 // 3 -> 0 static const int magic[] = {2, 1, 0, 0}; unsigned clz2(uint32_t x, int c) { if (!x && !c) return 32; uint32_t upper = (x >> (16 >> c)); uint32_t lower = (x & (0xFFFF >> mask[c])); // 2 bits left which implies c == 3 // c = 0 => 16 bits // c = 1 => 8 bits // c = 2 => 4 bits // c = 3 => 2 bits if (c == 3) return upper ? magic[upper] : 2 + magic[lower]; return upper ? clz2(upper, c + 1) : (16 >> (c)) + clz2(lower, c + 1); } ``` 64位元是32位元延伸, 先判斷前32位元clz32是否是0, 是則判斷下32位元的clz32值 ```cpp! static inline int clz64(uint64_t x) { return clz32(x >> 32) ? clz32(x >> 32) : clz32(x & 0xFFFFFFFF) + 32; } ``` ### 計算sqrt ```cpp! uint64_t sqrti(uint64_t x) { uint64_t m, y = 0; if (x <= 1) return x; int total_bits = 64; /* clz64(x) returns the count of leading zeros in x. * (total_bits - 1 - clz64(x)) gives the index of the highest set bit. * Rounding that index down to an even number ensures our starting m is a * power of 4. */ int shift = (total_bits - 1 - clz64(x)) & 0xFFFFFFFE; m = 1ULL << shift; while(m) { uint64_t b = y + m; y >>= 1; if (x >= b) { x -= b; y += m; } m >>= 2; } return y; } ``` #### 演算法精神 TBD 理解中, 要用自己的話說一次 [reference](https://hackmd.io/l4--gpsZQBiEI5LKS1lDvg?view) ## Q3 ### 目標 - [x] 理解hash table實作 - [x] 應用在2sum 這裡是使用linked list方式實作map,這個方式好處是不用碰撞後還要挪元素 (probing) ### hlist linux 用於hash table實作的資料結構[include/linux/types.h](https://github.com/torvalds/linux/blob/master/include/linux/types.h) ```cpp! struct hlist_head { struct hlist_node *first; } struct hlist_node { struct hlist_node *next, **pprev; } ``` ### map實作 資料結構和巨集 ```cpp! #define MAP_HASH_SIZE(bits) (1 << (bits)) #define GOLDEN_RATION_32 0x61C88647 // definition of the map structs typedef struct { int bits; struct hlist_head *ht; } map_t; struct hash_key { int key; void *data; struct hlist_node node; }; ``` 初始化map ```cpp! map_t *map_init(int bits) { map_t *map = malloc(sizeof(map_t)); if (!map) return NULL; map->bits = bits; map->ht = malloc(sizeof(struct hlist_head) * MAP_HASH_SIZE(bits)); if (map->ht) { for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) (map->ht)[i].first = NULL; } else { free(map); map = NULL; } return map; } ``` 湊雜函式 ```cpp! static inline unsigned int hash(unsigned int val, unsigned int bits) { return (val * GOLDEN_RATION_32) >> (32 - bits); } ``` 取得函式 這裡的處理比較直觀 - 先找到湊雜值所在的串列 - 使用線性搜尋找到鍵值相同的元素 ```cpp! static struct hash_key *find_key(map_t *map, int key) { struct hlist_head *head = &(map->ht)[hash(key, map->bits)]; // find exact mach of the key from the hash table's hlist for (struct hlist_node *p = head->first; p; p = p->next) { struct hash_key *kn = container_of(p, struct hash_key, node); if (kn->key == key) return kn; } return NULL; } void *map_get(map_t *map, int key) { struct hash_key *kn = find_key(map, key); return kn ? kn->data : NULL; } ``` 新增函式 比較值得注意的是從頭插入 需要更新**pprev, *next指標 ```cpp! void map_add(map_t *map, int key, void *data) { struct hash_key *kn = find_key(map, key); // key exists if (kn) return; kn = malloc(sizeof(struct hash_key)); if (!kn) return; kn->key = key, kn->data = data; struct hlist_head *h = &map->ht[hash(key, map->bits)]; struct hlist_node *n = &kn->node, *first = h->first; n->next = first; // renew pprev of old first node if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } ``` 刪除map 每個map串列逐個刪除 這邊pprev的更新是把它更新成&head, 下一輪再做刪除 ```cpp! void map_deinit(map_t *map) { if (!map) return; for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) { struct hlist_head *head = &map->ht[i]; for (struct hlist_node *p = head->first; p;) { struct hash_key *kn = container_of(p, struct hash_key, node); struct hlist_node *n = p; p = p->next; if (!n->pprev) /* unhashed */ goto bail; // next's pprev to cur's pperv struct hlist_node *next = n->next, **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; n->next = NULL, n->pprev = NULL; bail: free(kn->data); free(kn); } } free(map); } ```