# 2025q1 Homework2 (quiz1+2) contributed by < JeffBla > ## Q1 間接指標、鏈結串列與記憶體配置 ### 測驗 1 ### 解釋程式碼運作的原理 ```c /** * Inserts a new item into the list before a specified item. * * This function traverses the list to locate the position immediately before * the item pointed to by @before and inserts @item in that position. * The time complexity is O(n), where n is the number of steps needed to * reach @before from the head of the list. * * Parameters: * @l : Pointer to the list. * @before : Pointer to the item before which the new item should be inserted. * - If @before is the head of the list, the new item is inserted * at the front. * - If @before is NULL, the new item is appended to the end of * the list. * - In all other cases, behavior is undefined if @before does not * belong to @l. * @item : The new list item to be inserted. */ static inline void list_insert_before(list_t *l, list_item_t *before, list_item_t *item){ list_item_t **p; for (p = AAAA; *p != BBBB; p = CCCC) ; *p = item; DDDD = before; } ``` AAAA = &l->head BBBB = before CCCC = &(*p)->next DDDD = item->next 此函式是將節點 `item` 插入到 `before` 之前,在插入之前,需要知道指向 `before` 的節點,以修改節點順序,因此 for 的流程: 1. 初始化: 鏈結串列開頭。 2. 條件判斷: 當 pointer to pointer 所指向的 `next` 或 `head` 指向 `before` 則代表所操控的指標為 `before` 的前一個節點的 `next` ,即我們希望插入的位置,當到達此情況,則停止迴圈。 3. 移動至下一個:當離開迴圈條件不滿足,代表此 pointer to pointer 的數值並非所求,檢查下一個節點。 最後,因為找到目標指標所以將此指標指向欲新增的節點,並維護後續節點。其函式流程圖如下: 假設一開始的鏈結串列如下 ```graphviz digraph { rankdir=LR; node [shape=record]; list -> head [tailport=s, headport=n]; head -> node1; node1 -> node2; node2 -> node3; } ``` 初始化,將 pointer to pointer 指向 `&list->head` ,利用 pointer to pointer 控制指標以去除特例。 ```graphviz digraph { rankdir=LR; node [shape=record]; edge_p [shape=point, width=0, height=0, label=""]; list -> edge_p [dir=none]; edge_p -> head; head -> before; before -> node2; node2 -> node3; p -> edge_p; } ``` ```graphviz digraph { rankdir=LR; node [shape=record]; edge_p [shape=point, width=0, height=0, label=""]; list -> head; head -> edge_p [dir=none]; edge_p -> before before -> node2; node2 -> node3; p -> edge_p; } ``` 當找到目標,則另用 pointer to pointer 控制 `next` 或 `head` 指標,插入新節點。 ```graphviz digraph { rankdir=LR; node [shape=record]; edge_p [shape=point, width=0, height=0, label=""]; list -> head; head -> edge_p [dir=none]; edge_p -> before [color=red, style=dashed, label="old", fontcolor=red]; before -> node2; node2 -> node3; p -> edge_p; // New connection (being added) edge_p -> new_node [color=green, style=bold, label="new", fontcolor=green]; // Add a note note [shape=note, label="Changing pointer\nfrom before to new_node", color=blue]; note -> edge_p [style=dotted, arrowhead=none, color=blue]; } ``` 最後將新節點指向 `before` 完整插入。 ```graphviz digraph { rankdir=LR; node [shape=record]; edge_p [shape=point, width=0, height=0, label=""]; list -> head; head -> edge_p [dir=none]; before -> node2; node2 -> node3; p -> edge_p; edge_p -> new_node; new_node->before; } ``` ### 解釋測試程式碼運作原理 主要測試函式為 `test_list` ,主要測試三個項目 1. 頭部插入 2. 尾部插入 3. 尾部插入(無數值檢查) 每項目由 1. 重設鏈結串列與待插入數值 2. 確認鏈結串列長度為零 3. 插入 `N` 項 4. 確認插入後鏈結串列長度為 `N` 5. 檢查插入數值是否符合預期 ### 在現有的程式碼基礎上,撰寫合併排序操作 #### 實作 buttom-up mergesort 想法:使用 list_insert_before 進行將鏈結串列 2 依據遞增插入到 鏈結串列 1 的合併演算法。 [詳細實作](https://gist.github.com/JeffBla/921deb6a18373320151af0904541215e) ```c list_item_t *merge(list_item_t *left, list_item_t *right) { list_t result = {NULL}; list_item_t *next; // Merge the two lists while (left && right) { if (left->value <= right->value) { next = left->next; list_insert_before(&result, NULL, left); // Insert at end left = next; } else { next = right->next; list_insert_before(&result, NULL, right); // Insert at end right = next; } } // Handle remaining elements while (left != NULL) { next = left->next; list_insert_before(&result, NULL, left); left = next; } while (right != NULL) { next = right->next; list_insert_before(&result, NULL, right); right = next; } return result.head; } ``` ```c void merge_sort(list_t *l) { if (!l || !l->head || !l->head->next) return; int len = 0; for (list_item_t *curr = l->head; curr; curr = curr->next) len++; for (int size = 1; size < len; size *= 2) { list_item_t dummy; dummy.next = NULL; list_item_t *tail = &dummy; list_item_t *curr = l->head, *left, *right; while (curr) { left = curr; right = split(left, size); curr = split(right, size); tail->next = merge(left, right); while (tail->next) { tail = tail->next; } } l->head = dummy.next; } } ``` ### 測驗 2 EEEE = (*pred_ptr)->r FFFF = &(*pred_ptr)->r ### 解釋程式碼運作的原理 1. `find_free_tree` 從 `root` 找到 `target` 並回傳指向 `target` 的指標的指標,也就是指標的指標指向 `l` 或 `r` 。 2. `find_predecessor_free_tree` 找到能夠替換的 `target` 的節點。 此函式刪除二元搜尋樹中的特定節點。首先找到指向 `target` 的指標的指標。判斷欲刪除節點之子嗣的存在與否,如果沒有則直接刪除,若存在其一且唯一,則將後代上提,替換目標節點。若左右兩邊都存在節點且將替換的節點為 `target` 的兒子,則直接替換,因為其替換的演算法為找尋左子樹中最大值,如果將替換節點為其後代,則代表欲替換節點左子樹不須處理且能夠直接承接 `target` 之右子樹。若左右兩邊都存在節點但將替換節點不為 `target` 的兒子,則紀錄 `target` 左右子樹,遞迴修剪欲替換的節點,待欲替換的節點整理完成,執行替換。 第一種:欲刪除節點沒有子嗣 ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; root:l -> node2; root:r -> node3; node2:l -> node4; node2:r -> node5; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node5 [label="<l> left|<s> target|<r> right"]; node_ptr [label="node_ptr"]; edge_p [shape=point, width=0, height=0, label=""]; null [label="NULL"]; root:l -> node2; root:r -> node3; node2:l -> node4; node2:r -> edge_p [dir=none]; edge_p -> node5 [color=red, style=dashed, label="old", fontcolor=red]; node_ptr ->edge_p; // New connection (being added) edge_p -> null [color=green, style=bold, label="new", fontcolor=green]; } ``` 第二種:存在左或右子代其一 ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; root:l -> node2; root:r -> node3; node2:l -> node4; node4:l -> node8; node4:r -> node9; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; node_ptr [label="node_ptr"]; edge_p [shape=point, width=0, height=0, label=""]; root:l -> edge_p [dir=none]; edge_p -> node2; root:r -> node3; node2:l -> node4; node4:l -> node8; node4:r -> node9; node_ptr -> edge_p; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; edge_p [shape=point, width=0, height=0, label=""]; node_ptr [label="node_ptr"]; root:l -> edge_p [dir=none]; edge_p -> node2 [color=red, style=dashed, label="old", fontcolor=red]; root:r -> node3; node2:l -> node4; node4:l -> node8; node4:r -> node9; node_ptr -> edge_p; // New connection (being added) edge_p -> node4 [color=green, style=bold, label="new", fontcolor=green]; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; root:l -> node4; root:r -> node3; node4:l -> node8; node4:r -> node9; } ``` 第三種:存在左與右子代 ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; root:l -> node2; root:r -> node3; node2:l -> node4; node2:r -> node5; node4:l -> node8; node4:r -> node9; node9:l -> node18; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; edge_p [shape=point, width=0, height=0, label=""]; edge_pred [shape=point, width=0, height=0, label=""]; node_ptr [label="node_ptr"]; pred_ptr [label="pred_ptr"]; root:l -> edge_p [dir=none]; edge_p -> node2; root:r -> node3; node2:l -> edge_pred; edge_pred -> node4; node2:r -> node5; node4:l -> node8; node4:r -> node9; node9:l -> node18; node_ptr -> edge_p; pred_ptr -> edge_pred; } ``` 紀錄欲移除節點的左右子樹,以利後續維護。 ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; edge_p [shape=point, width=0, height=0, label=""]; edge_pred [shape=point, width=0, height=0, label=""]; node_ptr [label="node_ptr"]; pred_ptr [label="pred_ptr"]; pred_node [label="pred_node"]; node9 [label="<l> left|<s> node9|<r> right"]; node18 [label="<l> left|<s> node18|<r> right"]; root:l -> edge_p [dir=none]; edge_p -> node2; root:r -> node3; node2:l -> node4 [color=red label="recorded"]; node2:r -> node5 [color=red label="recorded"]; node4:l -> node8; node4:r -> edge_pred [dir=none]; edge_pred -> node9; node9:l -> node18; node_ptr -> edge_p; pred_ptr -> edge_pred; pred_node -> node9; } ``` `pred_ptr` 找到將替換節點後,利用遞迴,將欲替換節點當作欲移除節點,確保替換後,二元搜索樹結構仍正確。因為替換節點需要做移除並插入的動作,所以利用遞迴將此種操作傳遞到子代,一直到末端。下面是進入迴圈,處理子代的操作: ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; edge_p [shape=point, width=0, height=0, label=""]; edge_pred [shape=point, width=0, height=0, label=""]; node_ptr [label="node_ptr"]; pred_ptr [label="pred_ptr"]; pred_node [label="pred_node"]; node9 [label="<l> left|<s> node9|<r> right"]; node18 [label="<l> left|<s> node18|<r> right"]; root:l -> edge_p [dir=none]; edge_p -> node2; root:r -> node3; node2:l -> node4 [color=red label="recorded"]; node2:r -> node5 [color=red label="recorded"]; node4:l -> node8; node4:r -> edge_pred [dir=none]; edge_pred -> node9 [color=red, style=dashed, label="old", fontcolor=red]; node9:l -> node18; node_ptr -> edge_p; pred_ptr -> edge_pred; pred_node -> node9; // New connection (being added) edge_pred -> node18 [color=green, style=bold, label="new", fontcolor=green]; } ``` 當子代處理完成,回到 `target` 的處理。 ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; edge_p [shape=point, width=0, height=0, label=""]; node_ptr [label="node_ptr"]; pred_node [label="pred_node"]; node9 [label="<l> left|<s> node9|<r> right"]; node18 [label="<l> left|<s> node18|<r> right"]; root:l -> edge_p [dir=none]; edge_p -> node2; root:r -> node3; node2:l -> node4 [color=red label="recorded"]; node2:r -> node5 [color=red label="recorded"]; node4:l -> node8; node4:r -> node18; node_ptr -> edge_p; pred_node -> node9; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; edge_p [shape=point, width=0, height=0, label=""]; node_ptr [label="node_ptr"]; pred_node [label="pred_node"]; node9 [label="<l> left|<s> node9|<r> right"]; node18 [label="<l> left|<s> node18|<r> right"]; root:l -> edge_p [dir=none]; edge_p -> node2 [color=red, style=dashed, label="old", fontcolor=red]; root:r -> node3; node2:l -> node4 [color=red label="recorded"]; node2:r -> node5 [color=red label="recorded"]; node4:l -> node8; node4:r -> node18; node_ptr -> edge_p; pred_node -> node9; // New connection (being added) edge_p -> node9 [color=green, style=bold, label="new", fontcolor=green]; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; node9 [label="<l> left|<s> node9|<r> right"]; node18 [label="<l> left|<s> node18|<r> right"]; root:l -> node9; root:r -> node3; node2:l -> node4; node2:r -> node5; node4:l -> node8; node4:r -> node18; node9:l -> node4 [color=red]; node9:r -> node5 [color=red]; } ``` ```graphviz digraph { rankdir=TD; node [shape=record]; node [label="<l> left|<s> node|<r> right"]; root [label="<l> left|<s> root|<r> right"]; node2 [label="<l> left|<s> target|<r> right"]; node9 [label="<l> left|<s> node9|<r> right"]; node18 [label="<l> left|<s> node18|<r> right"]; root:l -> node9; root:r -> node3; node4:l -> node8; node4:r -> node18; node9:l -> node4; node9:r -> node5; } ``` ### 嘗試補完程式碼 [詳細程式碼](https://gist.github.com/JeffBla/353b44dd7bf834b76a21309b1472fcf8) 測試程式碼移除的三種情況。實作找到指向 `target` 的指標、初始化並插入建二元搜索樹。 ```c block_t **find_free_tree(block_t **root, block_t *target) { if (!*root) { return root; } while ((*root)->size != target->size) { if ((*root)->size < target->size) { root = &(*root)->r; } else { root = &(*root)->l; } } return root; } ``` ```c void insert_free_tree(block_t **root, size_t sz) { if (!*root) return; while (*root) { if ((*root)->size < sz) { root = &(*root)->r; } else { root = &(*root)->l; } } *root = malloc(sizeof(block_t)); (*root)->l = NULL; (*root)->r = NULL; (*root)->size = sz; } ``` ```c static void tree_reset(void) { root.l = NULL; root.r = NULL; for (size_t i = 0; i < N; i++) { temp[i] = i + 1; } size_t index = 0; fill_balanced(0, N - 1, &index); } ``` 以 N 為 1000 節點測試,建造 complete BST ,並依序刪除 1. First quarter: N / 4 2. Middle: N / 2 3. Third quarter: 3 * N / 4 4. First item: 0 5. Last item: N - 之後測試隨機刪除直到節點全被移除。 ### 效能評比 ### 測驗 3 ### 解釋程式碼運作原理 GGGG = head->prev=prev HHHH = list_entry(pivot,node_t,list)->value IIII = list_entry(n,node_t,list)->value JJJJ = pivot KKKK = right 此程式碼和運用 [Optimized QuickSort — C Implementation (Non-Recursive)](https://alienryderflex.com/quicksort/) 實作非遞迴 (non-recursive; iterative) 的快速排序法程式碼原理相同。 利用 `begein` 與 `end` (在此實作利用 list_tail 取代) 代替分割所需要的遞迴空間及紀錄數值。採用和 list_sort.h 類似風格實作,首先,將環狀改成非環狀並在後續復原 `prev` 指標,復原環狀與雙向指標的特性。利用 `L` 代表串列最左, `R` 代表串列最右進行範圍內的運算,其中 `pivot` 為最左邊的節點,並將此節點獨立,利用 `p` 指標遍歷`L` 到 `R` 中的節點,將大於 `pivot` 內數值的節點歸到 `right` 其他歸到 `left` 。 最後將 `right` 、 `pivot` 、 `left` 開頭記錄到 `begin` ,其中 `right` 所記錄的位置最後面, 其次是 `pivot` 和 `left` ,由於 `begin` 採用堆疊的概念,在排序時,也是 `right` 完成後才是 `pivot` 與 `left` 。 當排序完成, `L` 與 `R` 指向相同節點,將此節點記錄到 `result` ,並使用 `begin` 執行前面一項鏈結串列排序。 ## Q2 鏈結串列、雜湊表,及位元操作 ### 測驗 1 AAAA = list_first_entry BBBB = list_del CCCC = list_move_tail DDDD = list_add EEEE = list_splice FFFF = list_splice_tail ### 解釋上方程式碼運作原理 ### 改進 random_shuffle_array 利用 ASLR 實作隨機,在 `unbiased_random` 中乘法可有效地將隨機分佈擴展到目標範圍,透過採用高位而不是使用模數,避免偏差。 ```c #include <stdlib.h> #include <time.h> #include <stdint.h> static uint16_t get_unsigned16(void) { // Get addresses of different functions that will be randomized by ASLR void* func_ptr1 = (void*)&malloc; void* func_ptr2 = (void*)&free; // Create some stack variables to get their addresses int stack_var1; int stack_var2; // Mix function and stack addresses together uintptr_t addr_mix = (uintptr_t)func_ptr1 ^ (uintptr_t)func_ptr2 ^ (uintptr_t)&stack_var1 ^ (uintptr_t)&stack_var2; // Mix in some dynamic memory address void* heap_ptr = malloc(sizeof(int)); addr_mix ^= (uintptr_t)heap_ptr; free(heap_ptr); // Add time component for additional entropy addr_mix ^= (uintptr_t)time(NULL); // Extract 16 bits with better mixing uint16_t random_value = (uint16_t)((addr_mix >> 4) ^ (addr_mix >> 8) ^ addr_mix); return random_value; } static uint16_t unbiased_random(uint16_t max_exclusive) { // For small ranges relative to UINT16_MAX, this approximation works well uint32_t random = get_unsigned16(); uint32_t scaled = (random * (uint32_t)max_exclusive) >> 16; return (uint16_t)scaled; } static inline void random_shuffle_array(uint16_t *operations, uint16_t len) { for (uint16_t i = 0; i < len; i++) { // Will generate a number between 0 and i inclusive uint16_t j = unbiased_random(i + 1); uint16_t temp = operations[i]; operations[i] = operations[j]; operations[j] = temp; } } ``` ### 若將 list_move_tail 換為 list_move,為何會無法滿足 stable sorting `list_for_each_entry_safe` 是由前到後遍歷, `list_move_tail` 將每次遍歷的數值加入到特定鏈結串列的末端,形成類似佇列的排列,先插入的節點會在前面,而後插入的在後面,確保了 stable 的性質。若換成 `list_move` 將每次遍歷的數值加入到特定鏈結串列的前端,則形成如堆疊的排列,先插入的節點在後面,後插入的在前面。 ### 測驗 2 GGGG = 14 HHHH = 2 IIII = 0 JJJJ = 3 KKKK = 2 LLLL = 1 MMMM = ~1 NNNN = PPPP =