# 2025q1 Homework1 (lab0) ## 開發環境 ``` lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 16 On-line CPU(s) list: 0-15 Vendor ID: GenuineIntel Model name: 12th Gen Intel(R) Core(TM) i5-12500H CPU family: 6 Model: 154 Thread(s) per core: 2 Core(s) per socket: 12 Socket(s): 1 Stepping: 3 CPU(s) scaling MHz: 18% CPU max MHz: 4500.0000 CPU min MHz: 400.0000 BogoMIPS: 6220.80 Caches (sum of all): L1d: 448 KiB (12 instances) L1i: 640 KiB (12 instances) L2: 9 MiB (6 instances) L3: 18 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-15 ``` ## 針對佇列操作的程式碼實作 ### q_new 函式目的:建立一個空的佇列,並且回傳指標 實作方式:為了避免 `malloc` 配置失敗,需要在 `malloc` 後檢查配置是否成功,因此需要在 `malloc` 後做檢查,以免發生 Undefined Behavior 如果配置成功,則將 `prev` 及 `next` 指向自己並回傳 ```c struct list_head *q_new() { struct list_head *node = (struct list_head *) malloc(sizeof(struct list_head)); if (!node) return NULL; node->next = node; node->prev = node; return node; } ``` ### q_free 目的:釋放整個佇列所佔據的記憶體 實作:先建立一個 `element` 接著夠過這個 `element` 尋訪整個 `queue` 在逐一進行釋放。此外,為了避免當前節點釋放後找不到下一個節點得問題,需要在額外新增一個 node 在釋放當前節點之前儲存下一個節點的位置,我們可以透過 list_for_each_safe() 這個 macro 達成 ```c void q_free(struct list_head *head) { if (!head) return; element_t *cur = NULL, *node = NULL; list_for_each_entry_safe (cur, node, head, list) { free(cur->value); free(cur); } free(head); } ``` ### q_insert_head & q_insert_tail ### q_remove_head & q_remove_tail 目的:將 queue 中第一個元素移除,並放置於暫存區 實作:使用 list_entry 這個 macro 來取得第一個元素,接著透過 `list_def` 將其從 queue 中移除,並置於暫存區 `sp` 首先是 `q_remove_head`,根據 queue.h 中的註解,`sp` 是一個暫存區,裡面放的是被 remove 的元素,`bufsize` 是這個暫存區的大小 ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head) return NULL; element_t *node = list_entry(head->next, element_t, list); list_del(&node->list); if (sp) { strncpy(sp, node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return node; } ``` 以上程式碼在 queue 為 empty 的時候會進行 remove 會導致 Segmentation fault ,因此需要在額外檢查 list_head 是否為 empty ```diff - if (!head) + if (!head || list_empty(head)) ``` ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *node = list_entry(head->prev, element_t, list); list_del(&node->list); if (sp) { strncpy(sp, node->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return node; } ``` ### q_delete_mid ### q_delete_dup ### q_swap 目的:將兩元素的順序交換 實作:可以透過 `list.h` 中的 `list_del` 與 `list_add`,來實作 ```c void q_swap(struct list_head *head) { // https://leetcode.com/problems/swap-nodes-in-pairs/ if (!head || list_empty(head)) return; struct list_head *node = head->next; while (node && node->next != head) { list_del(node); list_add(node, node->next); node = node->next; } } ``` 實際測試會有問題,只要佇列的數量大於 3 就會失敗,因此需修改 ```diff! - struct list_head *node = head->next; - while (node && node->next != head) { + struct list_head *cur = head->next, *node = cur->next; + while (cur != head && cur->next != head) { list_del(node); - list_add(node, node->next); - node = node->next; + list_add(node, cur->prev); + cur = cur->next; + node = cur->next; ``` ### q_reverse 目的:反轉 queue 中元素的順序 實作:與 `q_swap` 類似,可以透過 `list_for_each_safe` 以及 `list_move` 等 function 來達成 ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *node = NULL, *safe = NULL; list_for_each_safe (node, safe, head) { list_del(node); list_move(node, head); } } ``` ### q_reverseK 目的:對於整個 queue 以 k 個為一組進行反轉 實作:先將要反轉的部分從 queue 中切離,接著使用 q_reverse 進行反轉,再組合回去 ```c void q_reverseK(struct list_head *head, int k) { // https://leetcode.com/problems/reverse-nodes-in-k-group/ if(!head || list_empty(head)) return; struct list_head *node = NULL, *safe = NULL, *start = head; int count = 0; list_for_each_safe(node, safe, head){ if (count < k - 1 ){ count++; continue; } LIST_HEAD(tmp); list_cut_position(&tmp, start, node); q_reverse(&tmp); list_splice(&tmp, start); start = safe->prev; count = 0; } } ``` ### q_ascend & q_descend 目的:對於每個節點,如果該節點右側有比當前節點還大 / 小的值,及刪除當前節點 實作:從 queue 的尾端往前找會比較簡單,如果條件成立就刪除,反之則繼續往前找 ```clike! int q_ascend(struct list_head *head) { ... while (tail != head && tail->prev != head) { struct list_head *node = tail->prev; element_t *node_elem = list_entry(node, element_t, list); if (strcmp(node_elem->value, tail_elem->value) > 0) { ... } int q_descend(struct list_head *head) { ... while (tail != head && tail->prev != head) { struct list_head *node = tail->prev; element_t *node_elem = list_entry(node, element_t, list); if (strcmp(node_elem->value, tail_elem->value) < 0) { ... } ``` ### q_sort 目的:針對 queue 中的元素進行排序 實作:將排序拆成三個 fucntion ,分別是 `merge` 、 `merge_sort` 以及 `q_sort` q_sort 中,將整個 list 的環狀部分拆掉,接著呼叫 `merge_sort` 將 list 拆分成單一元素,然後透過 `merge` 將 list 組合回來,最後在重建 list 的環狀結構 `merge_sort` 實作過程中發生 Segmentation fault ,原始版本如下 ```c struct list_head *merge_sort(struct list_head *head, bool descend) { if (!head || list_is_singular(head)) return head; struct list_head *slow = head, *fast = head; ... } ``` 重新檢視實作的流程,當呼叫到 `merge_sort` 的時候,此時傳入的 `head` 並非 dummy node ,因此不該使用 `list_is_singular` 這個 function,應該判斷這個 `head` 後面是否還有節點存在,如果是單一節點則無須進行排序 另外,考量到傳入的 list 只有兩個節點的情況,如果 `fast` 的起始點設在 `head`,當進入 while 後,`fast` 會因為 `fast = fast->next->next` 存取到不存在的位址,造成 Segmentation fault 因此,程式碼應修成以下: ```diff struct list_head *merge_sort(struct list_head *head, bool descend) { - if (!head || list_is_singular(head)) + if (!head || !head->next) return head; - struct list_head *slow = head, *fast = head; + struct list_head *slow = head, *fast = head->next; ... } ``` ### q_merge 目的:將所有 head 指向的 queue 合併成一個,接著進行排序 實作:建立一個新的 list_head ,接著將 head 指向的所有 queue 走過一遍,並切把節點搬到新的 list_head ,最後在進行排序 註解中有提到,這個函式有使用到 `queue_contex_t` 這個結構 ```cpp typedef struct { struct list_head *q; struct list_head chain; int size; int id; } queue_contex_t; ``` 可以先將 head->next 作為起始點,接著逐一將所有節點搬到這個 queue,如下 ```cpp int q_merge(struct list_head *head, bool descend) { // https://leetcode.com/problems/merge-k-sorted-lists/ if (!head || list_empty(head)) return 0; queue_contex_t *new_head = list_entry(head->next, queue_contex_t, chain); struct list_head *node = head->next, *safe = NULL; while (node != head) { safe = node->next; queue_contex_t *tmp = list_entry(node, queue_contex_t, chain); list_splice(tmp->q, new_head->q); node = safe; } q_sort(new_head->q, descend); return q_size(new_head->q); } ``` 但這個版本由於 `new_head` 及 `tmp` 在最開始的時候同時指向同一個地址,接著在進行 `list_splice` 操作,導致 segmentation fault,因此修改後如下: ```diff int q_merge(struct list_head *head, bool descend) { ... - struct list_head *node = head->next, *safe = NULL; + struct list_head *node = head->next->next, *safe = node->next; - while (node != head) { + while (node && node != head) { queue_contex_t *tmp = list_entry(node, queue_contex_t, chain); - list_splice(tmp->q, new_head->q); + list_splice_tail_init(tmp->q, new_head->q); ... } ```