# 2025q1 Homework2 (quiz1+2) contributed by < `TING0419` > ## 第一週 ### 測驗一 ### 測驗二 ### 測驗三 用8個數字模擬 `非遞迴Quicksort` ```graphviz digraph QuickSort_Step1 { node [shape=record]; label="Step 1: Initial List"; list [label="4 | 19 | 6 | 23 | 11 | 30 | 3 | 8", shape=box]; } ``` ```graphviz digraph QuickSort_Step2 { node [shape=record]; label="Step 2: Select Pivot = 4 and Partition"; stack1 [label="begin[] = [4, 19, 6, 23, 11, 30, 3, 8]", shape=box]; pivot1 [label="Pivot: 4", shape=ellipse]; left1 [label="Left: 3", shape=box]; right1 [label="Right: 19 | 6 | 23 | 11 | 30 | 8", shape=box]; stack1 -> pivot1; pivot1 -> left1; pivot1 -> right1; } ``` ```graphviz digraph QuickSort_Step3 { node [shape=record]; label="Step 3: Select Pivot = 19 and Partition"; stack2 [label="begin[] = [6, 11, 8] + [Pivot: 19] + [23, 30]", shape=box]; pivot2 [label="Pivot: 19", shape=ellipse]; left2 [label="Left: 6 | 11 | 8", shape=box]; right2 [label="Right: 23 | 30", shape=box]; stack2 -> pivot2; pivot2 -> left2; pivot2 -> right2; } ``` ```graphviz digraph QuickSort_Step4 { node [shape=record]; label="Step 4: Select Pivot = 6 and Partition"; stack3 [label="begin[] = [6] + [8 -> 11]", shape=box]; pivot3 [label="Pivot: 6", shape=ellipse]; left3 [label="Left: None", shape=box]; right3 [label="Right: 8 | 11", shape=box]; stack3 -> pivot3; pivot3 -> left3; pivot3 -> right3; } ``` ```graphviz digraph QuickSort_Step5 { node [shape=record]; label="Step 5: Select Pivot = 8 and Partition"; stack4 [label="begin[] = [8] + [11]", shape=box]; pivot4 [label="Pivot: 8", shape=ellipse]; left4 [label="Left: None", shape=box]; right4 [label="Right: 11", shape=box]; stack4 -> pivot4; pivot4 -> left4; pivot4 -> right4; } ``` ```graphviz digraph QuickSort_Step6 { node [shape=record]; label="Step 6: Select Pivot = 23 and Partition"; stack5 [label="begin[] = [23] + [30]", shape=box]; pivot5 [label="Pivot: 23", shape=ellipse]; left5 [label="Left: None", shape=box]; right5 [label="Right: 30", shape=box]; stack5 -> pivot5; pivot5 -> left5; pivot5 -> right5; } ``` ```graphviz digraph QuickSort_Step7 { node [shape=record]; label="Step 7: Final Sorted List"; sorted [label="Sorted: 3 | 4 | 6 | 8 | 11 | 19 | 23 | 30", shape=box]; } ``` 該程式實作了一個基於 `struct list_head `的 **快速排序**,並進行了鏈表構造、`shuffle`、排序與驗證。 --- #### 1. 定義鏈表節點 (node_t) ```c typedef struct __node { long value; struct list_head list; } node_t; ``` - `value`:儲存數值。 - `list`:使用 Linux Kernel 的 `list_head` 來形成雙向鏈表。 --- #### 2. 相關工具函式 ##### (1) 獲取鏈表最後一個節點 ```c struct list_head *list_tail(struct list_head *head) { while (head && head->next) head = head->next; return head; } ``` - 作用:尋找並回傳鏈表的最後一個節點。 ##### (2) 計算鏈表長度 ```c int list_length(struct list_head *left) { int n = 0; struct list_head *node; list_for_each(node, left) n++; return n; } ``` - 作用:遍歷鏈表並計算節點數量。 ##### (3) 重新構建雙向鏈表 ```c static void rebuild_list_link(struct list_head *head) { if (!head) return; struct list_head *node, *prev; prev = head; node = head->next; while (node) { node->prev = prev; prev = node; node = node->next; } prev->next = head; head->prev = prev; } ``` - **作用**:重新構建雙向鏈表,確保 `prev` 指標正確。 --- #### 3. Quick Sort 實作 ##### (1) 初始化變數 ```c int n = list_length(list); int max_level = 2 * n; struct list_head *begin[max_level]; struct list_head *result = NULL, *left = NULL, *right = NULL; ``` - `max_level = 2 * n`:模擬遞迴的最大深度。 - `begin[]`:模擬遞迴函式的堆疊,儲存子鏈表的開頭。 - `result`:存放最終排序好的鏈表。 - `left` 和 `right`:用來存放比 Pivot 小或大的節點。 ##### (2) 執行 Quick Sort ```c begin[0] = list->next; list->prev->next = NULL; // Break the link while (i >= 0) { struct list_head *L = begin[i], *R = list_tail(begin[i]); ``` - `begin[0] = list->next` 設定初始未排序鏈表的開頭。 - `list->prev->next = NULL` 斷開鏈表,確保不會影響後續操作。 ##### (3) 選擇 Pivot 並進行分割 ```c if (L != R) { struct list_head *pivot = L; value = list_entry(pivot,node_t,list)->value; struct list_head *p = pivot->next; pivot->next = NULL; /* Break the link next to pivot */ ``` - 選擇 Pivot(第一個節點) - 遍歷剩餘節點,依據值大小分到 `left` 或 `right` ```c while (p) { struct list_head *n = p; p = p->next; int n_value = list_entry(n,node_t,list)->value; if (n_value > value) { n->next = right; right = n; } else { n->next = left; left = n; } } ``` - 比 Pivot 大的值進入 `right` - 比 Pivot 小或相等的值進入 `left` ##### (4) 記錄排序區塊並繼續排序 ```c begin[i] = left; begin[i + 1] = pivot; begin[i + 2] = right; left = right = NULL; i += 2; ``` ##### (5) 處理單獨節點(回溯) ```c else { if (L) { L->next = result; result = L; } i--; } ``` --- #### 4. 其他功能 ##### (1) 構造鏈表 ```c void list_construct(struct list_head *list, int n) { node_t *node = malloc(sizeof(node_t)); node->value = n; list_add(&node->list, list); } ``` ##### (2) 釋放鏈表記憶體 ```c void list_free(const struct list_head *head) { node_t *entry, *safe; list_for_each_entry_safe (entry, safe, head, list) { free(entry); } } ``` ##### (3) 驗證鏈表是否排序 ```c static bool list_is_ordered(const struct list_head *head) { int value = list_entry(head->next, node_t, list)->value; node_t *entry; list_for_each_entry (entry, head, list) { if (entry->value < value) return false; value = entry->value; } return true; } ``` --- #### 5. 主程式執行流程 ```c int main(int argc, char **argv) { struct list_head *list = malloc(sizeof(struct list_head)); INIT_LIST_HEAD(list); size_t count = 100000; int *test_arr = malloc(sizeof(int) * count); for (int i = 0; i < count; ++i){ test_arr[i] = i; } shuffle(test_arr, count); ``` 1. **初始化鏈表** 2. **建立長度 `100000` 的數列** 3. **隨機打亂(shuffle)測試不同輸入情況** ```c while (count--) list_construct(list, test_arr[count]); quick_sort(list); assert(list_is_ordered(list)); list_free(list); free(test_arr); printf("Test passed \n"); return 0; ``` --- #### 結論 - 使用 `Quick Sort` 演算法來對 **雙向鏈表** 進行排序。 - 透過 `shuffle` 測試不同輸入情況。 - 最後驗證排序結果是否正確,確保排序邏輯沒有錯誤。 ## 第二週 ### 測驗一 #### 1. 簡介 這段程式碼的主要功能是: 1. **隨機打亂陣列** `values[256]`。 2. **將 `values[]` 中的數據插入 `testlist` 雙向鏈表**。 3. **使用 `qsort()` 排序 `values[]`**。 4. **使用 `list_quicksort()` 進行鏈表排序**。 5. **比對 `testlist` 和 `values[]` 確保排序正確**。 --- #### 2. List QuickSort 實作 1. 基礎情況處理 ```c if (list_empty(head) || list_is_singular(head)) return; ``` - 若 `head` 為空或只有一個元素,直接返回。 2. 初始化 `list_less` 和 `list_greater` ```c INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); ``` - 用來存放小於和大於 `pivot` 的節點。 3. 選擇 `pivot` 並將其從鏈表移除 ```c pivot = list_first_entry(head, struct listitem, list); list_del(&pivot->list); ``` - 取出 `pivot` 並從鏈表刪除,以便進行分割。 4. 遍歷鏈表,將元素分配到 `list_less` 和 `list_greater` ```c 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); } ``` - 小於 pivot 的節點放入 `list_less`。 - 大於 pivot 的節點放入 `list_greater`。 - `list_move_tail()` 確保 QuickSort Stable。 - 若改成`list_move` 則會unstable,交換後原排序在前的會變成在後。 5. **遞迴處理 `list_less` 和 `list_greater`** ```c list_quicksort(&list_less); list_quicksort(&list_greater); ``` - 對 `list_less` 和 `list_greater` 分別進行 QuickSort。 6. **重新合併鏈表** ```c list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); ``` - **先加入 pivot**。 - **接著拼接 `list_less`**。 - **最後拼接 `list_greater`**。 --- #### 3. `random_shuffle_array()` - Fisher-Yates 洗牌演算法 ##### 原始版 ```c static inline void random_shuffle_array(uint16_t *operations, uint16_t len) { for (uint16_t i = 0; i < len; i++) { uint16_t j = get_unsigned16() % (i + 1); operations[i] = operations[j]; operations[j] = i; } } ``` 錯誤點: - 這段程式碼會 **覆蓋原始數據**,導致某些數字遺失。 ##### 改進版(使用 Fisher-Yates Shuffle) ```c static inline void random_shuffle_array(uint16_t *operations, uint16_t len) { for (uint16_t i = len - 1; i > 0; i--) { uint16_t j = get_unsigned16() % (i + 1); operations[i] = operations[j]; operations[j] = i; } printf("\n "); } ``` 改進點: - 交換 `operations[i]` 和 `operations[j]`,確保數據不丟失。 - 避免`Modulo Bias`取模偏差,確保隨機分布均勻。 --- #### 4. `main()` - 驗證排序是否正確 1. 初始化 `values[]` 並洗牌 ```c random_shuffle_array(values, (uint16_t) ARRAY_SIZE(values)); ``` - 這會打亂 `values[]`。 2. 將 `values[]` 的數據插入 `testlist` ```c for (i = 0; i < ARRAY_SIZE(values); i++) { item = (struct listitem *) malloc(sizeof(*item)); item->i = values[i]; list_add_tail(&item->list, &testlist); } ``` - 用 `malloc()` 分配節點,並加入鏈表尾部。 3. 排序 `values[]`(作為基準) ```c qsort(values, ARRAY_SIZE(values), sizeof(values[0]), cmpint); ``` - 使用標準 `qsort()` 來排序陣列。 4. 對 `testlist` 執行 `list_quicksort()` ```c list_quicksort(&testlist); ``` - 對雙向鏈表進行 QuickSort。 5. 驗證排序結果 ```c list_for_each_entry_safe (item, is, &testlist, list) { assert(item->i == values[i]); list_del(&item->list); free(item); i++; } ``` - 確保鏈表排序結果與 `values[]` 一致。 - 釋放鏈表的記憶體。 --- ### 測驗二 ### 測驗三