執行人: yihsuan1011
專題講解影片
重做 lab0 並彙整其他學員的成果。
q_new
用malloc
來配置記憶體,若配置失敗則會回傳NULL
,配置成功則接著使用INIT_LIST_HEAD
來初始化。
q_free
用list_for_each_entry_safe
來走訪佇列中的每個節點,並透過list_del
與q_release_element
將每個節點的空間釋放。list_for_each_entry_safe
中除了用entry
儲存當前節點外,還會使用另一個指標safe
來儲存下一個節點,因此不必擔心當前節點空間被釋放後會導致循環無法繼續。
q_insert_head
使用malloc
為新節點配置記憶體,並檢查該,element_t
有無配置成功,接著使用strdup
複製字串s
到entry->value
,若檢查到entry->value
配置失敗,則要將整個entry
都釋放,最後再透過list_add
將新節點加入佇列中。
q_insert_tail
q_insert_tail
與q_insert_head
的目標類似,僅在插入節點的位置有所不同,因此直接用head->prev
作為q_insert_head
的第一個引數就可以完成目標,只需要額外考慮空佇列的特例即可。
q_remove_head
使用list_first_entry
找到開頭節點,並使用strncpy
將被刪除的節點字串複製到sp
中,字串長度由bufsize
限制,且需要加上結尾字元,最後再由list_del_init
刪除開頭節點。
q_remove_tail
使用list_last_entry
找到結尾節點,並使用strncpy
將被刪除的節點字串複製到sp
中,字串長度由bufsize
限制,且需要加上結尾字元,最後再由list_del_init
刪除結尾節點。
q_size
使用list_for_each
走訪佇列中的每個節點,宣告一個整數len
來累計list_for_each
的迴圈次數,達到計算佇列長度的效果。
q_delete_mid
首先需要找到佇列的中點,原本使用課程中提到的快慢指標完成,後來參考同學們的作法,善用雙向鏈結佇列的特性,用兩個指標以相反方向走訪,兩個指標指向同個節點時即為中點。此種做法比快慢指標法需要走訪的節點數少三分之一。
q_delete_dup
根據LeetCode 82的題意,該函式的目標是刪除所有重複的節點。使用list_for_each_entry_safe
走訪所有節點,並且使用bool dup
來標示當前節點是否為多個連續重複節點組成的重複節點群。用strcmp
比較當前節點和下一個節點的字串是否相同,如果相同,則刪除當前節點,同時dup
設置為1
表示已進入重複節點群。直到當前節點與下一個點的字串不同時,會進到else if (dup)
的區塊,刪除最後一個重複節點並將dup
重新設置為0
。
q_swap
使用一個for
迴圈走訪所有節點,且當前節點node
和node->next
皆不能為head
,再使用list_move
將當前節點與下一個節點交換位置,完成一個成對節點的交換,而此時的node->next
就會指向下一組成對節點的第一個節點,重複直到佇列結束。
q_reverse
使用list_for_each_entry_safe
走訪所有節點,並將當前節點依序移動至佇列最前端,即完成佇列排列順序反轉。
q_reverseK
使用list_for_each_entry_safe
走訪所有節點,使用count
計次,每k
個節點擷取成一個子佇列sub_q
,並將sub_q
透過q_reverse
進行順序反轉,再將子佇列移動回原佇列的位置。
q_merge_two
參考上課中的merge方法,另外定義了q_merge_two
。引數為兩個佇列,宣告了一個儲存結果的list_head result
,依序比較兩個佇列的最前端節點,將字串較大者移動到result
的末端,完成排序後,原本的兩個佇列會有一個變為空佇列,再將另一個有剩餘節點的佇列移動到result
的末端,最終結果result
會儲存再原先第一個佇列的位址。
後面新增了引數 bool descend
,目前先簡單依descend
不同分成兩種while
迴圈,但兩種迴圈有很多相同處,應可再簡化。
q_sort
此處實作了課程中的Merge Sort方法。首先用快慢指標方法找到中間節點,從中間節點切成左右兩個子佇列,用遞迴的方式重複分割佇列,直到每個子佇列都只有一個節點,最後再用前面定義的q_merge_two
兩兩合併,最終得到排序完成的佇列。
q_ascend
使用雙向鏈結佇列的特性,反向走訪所有節點,比較當前節點curr
和前一個節點curr->list.prev
位置的字串大小,若前一個節點大於當前節點則將前一個節點刪除。
q_descend
與q_ascend
相同作法,反向走訪所有節點,比較當前節點curr
和前一個節點curr->list.prev
位置的字串大小,若前一個節點小於當前節點則將前一個節點刪除。
q_merge
首先先檢查是否只有一個佇列,如果有大於一個佇列,會先將第一個佇列儲存到first
,接著再從第二個佇列開始依序透過q_merge_two
合併至first
中,最後返回first的大小。
此處參考同學 chiangkd 筆記中的步驟。
將 list_sort.c 和 list_sort.h 兩者加入lab0-c
資料夾中,直接加入會出現很多錯誤,大多數都是因為include
許多沒有使用到的header,確認那些header不會使用到則直接刪除即可。
修改Makefile,加入list_sort.o
qtest
參考qtest
當中do_sort
的內容,在qtset
中新增do_lsort
,以執行list_sort
,作法同chiangkd同學之實作。
最後再加入新命令 lsort
。
lib/list_sort.c
並量化分析解讀 Linux 核心原始程式碼的 lib/list_sort.c
,設計實驗來量化分析,探討其實作手法,需要善用 perf 一類的工具。解析程式碼要有完整的圖文、數學證明,和如何達到 cache 友善。
list_sort
內容list_sort
的排序步驟如下:
list
和 pending
分別指向佇列的當前節點和暫存節點head->prev->next = NULL
將佇列以 NULL
為結尾for
迴圈走訪每個節點,找到最小的子佇列,就進行 merge
,並更新指標和計數。merge_final
完成最後合併。上述為最簡略的步驟解釋,中間的一些條件判斷方式我還不完全理解,查閱的資料顯示這樣的佇列排序方法能達到高效率及高穩定性。
此方法為 Tim Sort 的排序演算法的變體,結合了上課提到的 merge sort 和 insertion sort。主要概念是將輸入資料分割成許多小佇列,對每一小佇列使用 insertion sort 進行排序,接著再使用 merge 將這些已經排序好的小佇列合併成排序好的大佇列,最終完成排序。
在 qtest 新增 option
,允許使用者切換不同的排序實作 (例如 Linux 核心的 list_sort.c
和自行實作的 merge sort)。
上述步驟透過新增了do_lsort
功能,以及新增了lsort
指令,即可讓使用者透過cmd>
選擇排序方法。
搭配研讀必要的統計原理,探討如何改進原有常數時間複雜度判斷程式