# 2023q1 Homework1 (quiz1) contributed by < [zeddyuu](https://github.com/zeddyuu) > ## 測驗 1 ```c static inline int cmpint(const void *p1, const void *p2) { const uint16_t *i1 = (const uint16_t *) p1; const uint16_t *i2 = (const uint16_t *) p2; return *i1 - *i2; } ``` 用以對節點內含值比較的函式,這裡比較特別的是參數的 `void *` 型態 ,在 [你所不知道的C語言:指標篇](https://hackmd.io/@sysprog/c-pointer#void--%E4%B9%8B%E8%AC%8E) 內有提到,是為了要讓程式設計者透過顯式轉型避免危險的指標操作。 ```c if (list_empty(head) || list_is_singular(head)) return; struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); ``` 先確認 list 是否為空或是只有一個元素,接著初始化兩個 head 負責連接比 pivot 小或大的 node。 ```c struct item *pivot = list_first_entry(head, AAA, BBB); list_del(&pivot->list); ``` 選出第一個元素做為 pivot ,`list_first_entry` 用來取得第一個元素,參數要求為 list head、嵌入的結構體型態以及 list head 在此結構體內的變數名稱,故答案為 `AAA` : item `BBB` : list ```c CCC (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) DDD (&itm->list, &list_less); else EEE (&itm->list, &list_greater); } ``` 這裡的功能為<s>歷遍</s> 逐一走訪每個節點並且和 pivot 比較值的大小,若比 pivot 小則放入 list_less,比 pivot 大或等於則放入 list_greater,排序要求要 stable,所以值如果相同要照順序放入,也就是從尾巴放入 list,故答案為 `CCC` : `list_for_each_entry_safe` `DDD` : `list_move_tail` `EEE` : `list_move_tail` ```c list_sort(&list_less); list_sort(&list_greater); list_add(&pivot->list, head); list_splice(&list_less, head); FFF(&list_greater, head); ``` :::danger 留意 [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary),不用抄題目。你應該聚焦在理解題目背後的想法。 :notes: jserv > 好的,已改正,謝謝老師。 ::: 遞迴處理兩邊比 pivot 小的 list、比 pivot 大的 list,遞迴結束後,先將 pivot 接到原來的 head ,然後將排序好比 pivot 小的 list 用 `list_splice` 接在 pivot 前,最後將比 pivot 大的 list 用 `list_splice_tail` 接到 pivot 後,完成排序,故答案 `FFF` : `list_splice_tail` ## 測驗 2 ```c if (list_empty(head) || list_is_singular(head)) return; ``` 先確定 list 是否為空或只有一個節點,是的話就不用進行排序。 ```c struct list_head stack[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) INIT_LIST_HEAD(&stack[i]); int top = -1; list_splice_init(head, &stack[++top]); ``` 宣告一個 list head 這個型態的 stack,用迴圈將每個陣列元素用 `INIT_LIST_HEAD` 初始化,接著用 `list_splice_init` 將 head 所有元素移到陣列元素開頭,等同將待排序的 list 放入 stack 中。 ```c struct list_head partition; INIT_LIST_HEAD(&partition); ``` 宣告一個 partition 變數,用來處理等等要排序的 list。 ```c while (top >= 0) { INIT_LIST_HEAD(&partition); list_splice_init(&stack[top--], &partition); ``` 當 stack 內還有待排序的 list 時才執行這個 while 迴圈,迴圈一開始會將放在 stack 最上面的 list 用 `list_splice_init` 接在 partition 做處理。 ```c if (!list_empty(&partition) && !list_is_singular(&partition)) { struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); struct item *pivot = list_first_entry(&partition, struct item, list); list_del(&pivot->list); INIT_LIST_HEAD(&pivot->list); ``` 這段 if 敘述是處理 list 內有超過兩個節點的情況,跟遞迴版本一樣會用 less 跟 greater 分別去串比 pivot 小以及大的節點,pivot 也是選擇用 list 的第一個元素。 ```c struct item *itm = NULL, *is = NULL; GGGG (itm, is, &partition, list) { list_del(&itm->list); if (cmpint(&itm->i, &pivot->i) < 0) list_move(&itm->list, &list_less); else list_move(&itm->list, &list_greater); } ``` 之後就是走訪 list 中每一個節點,看是不是比 pivot 來得小或是大,再分別移到 less 或是 greater,為了要保持 stable 的排序,跟 pivot 相同的話要串在 list_greater 中,這樣後續再接回去 pivot 後方就能確保排序是 stable 的。 所以可以知道這邊的答案就是要有走訪的功能,同時敘述中會有 delete(move) 的情況,所以要用 safe 的版本。 `GGGG` : `list_for_each_entry_safe` ```c HHHH (&pivot->list, &list_less); if (!list_empty(&list_greater)) list_splice_tail(&list_greater, IIII); if (!list_empty(&list_less)) list_splice_tail(&list_less, JJJJ); ``` 走訪完成後就將 pivot 移到 less 的後方,產生出兩個未排序的 list,也就是 less 以及 greater,我們的目標就是跟遞迴處理一樣,要另外排序這兩個 list,因此再把這兩個 list 放入 stack 中成為待排序的 list,要記得先累加 top,空出新的位置之後再去存取。 `HHHH` : `list_move_tail` `IIII` : `&stack[++top]` `JJJJ` : `&stack[++top]` ```c else { top++; list_splice_tail(&partition, &stack[top]); while (top >= 0 && list_is_singular(&stack[top])) { struct item *tmp = list_first_entry(&stack[top], struct item, list); list_del(&tmp->list); INIT_LIST_HEAD(KKKK); list_add_tail(&tmp->list, head); } ``` 再來這邊是待排序的 list 只有一個節點的情況,會直接把 list 再塞回去 stack 裡面,用一個 while 迴圈將 stack 頂端只有一個節點的 list 都拿出來,並且把他們唯一一個元素放到 head 的尾巴,在上面放入 stack 是依照由大到小的順序,先放入 greater 再放入 less,所以取出來根據 stack LIFO 的性質,每次 partition 一定是先處理相較起來較小的 list,最後頂端就只會剩下較小的一個元素,再跟 head 接起來。 因為這邊使用 `list_del` 刪除掉 list 了,但卻沒有對 stack 做相對應的操作(pop 所以要 top = top - 1)。 故答案 `KKKK` : `&stack[top--]` ### 延伸問題 #### 指出設計和實作的缺失 程式碼在一開始就定義了 ```c #define MAX_DEPTH 512 ``` 所以 stack 的最大 size 也只有512,如果要排序的 list 太長會發生 [buffer overflow](https://zh.wikipedia.org/zh-tw/%E7%BC%93%E5%86%B2%E5%8C%BA%E6%BA%A2%E5%87%BA)。 ## 測驗 3