contribute by <Hualing-Chiu
>
延伸問題:
- 解釋上述程式碼的運作原理,提出改進方案並予以實作。
- 使用 Linux 核心風格的 List API 改寫上述程式碼,並針對鏈結串列,提出可避免最差狀況的快速排序實作,應設計效能評比的測試程式。
list_add
此函式的目的為在鏈結串列前端加入一個新節點,node_t
是要被加入的新節點,當節點加入完成後,將 *list
指回串列的開頭。
list_tail
此函式目的在於找到串列尾端,這裡使用間接指標來操作,不斷向後更新,因此 AAAA
應為 (*left)->next
。
list_length
此函式是在計算整個串列的長度,當判斷 *left
不為 null
時,不斷將指標向後更新,因此 BBBB
應為 (*left)->next
。
quick_sort
假設原本的鏈結串列為下:
每次都挑選最左邊的節點為 pivot,在進入 while
迴圈之前,將 begin[0]
及 end[0]
分別指向串列左端節點及右端節點,進入迴圈後,L
指向 begin[0]
的節點,R
指向 end[0]
的節點,p
則是會向後更新去走訪每個節點。
當n->value > value
條件成立時,會將當前指向的節點加到 right
的開頭,反之則加到 left
的開頭。待 p
走訪完整個串列後,會將串列分為 left
、right
、pivot
三個部分,且 begin[0]
與 end[0]
分別指向 left
串列的頭跟尾,begin[1]
與 end[1]
指向 pivot
,begin[2]
與 begin[2]
分別指向 right
串列的頭跟尾。
當 L
與 R
從堆疊中取出相同值的情況下,代表子串列只剩下單一元素,即可加入到 result
的開頭,就可得到排序好的串列。
延伸問題
- 解釋上述程式碼的運作原理,提出改進方案並予以實作。
- 將改進過的 Timsort 實作整合進 sysprog21/lab0-c,作為另一個有效的排序命令,應設計效能評比的測試程式,確保測試案例符合 Timsort 設想的資料分布樣式。
Timsort 是結合合併排序和插入排序的特點,對排序進行優化,尤其是在已經部分排序的序列,先識別出資料中已排序的子序列( run ),透過不斷合併這些 run 來達成全資料範圍的排序。
接著對 Timsort 程式碼進行探討,以圖示為例:
原始的鏈結串列為雙向鏈結串列
在 timsort
函式中,首先先將鏈結串列斷開改成單向串列
接著是 find_run
函式,尋找串列中已經排序好的 run,使用 len
計算 run 的長度,以下用圖示說明 find_run
函式的作法:
head
指向開頭,next
指向 list
的下一個節點next
不為空且 cmp(priv, list, next) <= 0
成立,則指標持續向後更新,直到找到非升序的串列為止。result.head
與 result.next
。prev
指標實作反轉序列prev
next
不為空且 cmp(priv, list, next) <= 0
則繼續走訪,否則離開迴圈。result.head
與 result.next
,可以發現此段子序列已排序完成。再來執行 merge_collapse
函式,此函式目的為確保堆疊上的 run 長度保持平衡,此判斷條件就是測驗二題目所描述的 \(A<=B+C\)
此外,還多了一個判斷條件為假設尚未合併的 run 有四個以上,若 \(A<=B+C\) 或 \(B<=C+D\),則 \(C\) 和 \(B\) 或 \(D\) 選長度小的兩者合併。
最後,確保堆疊內的 run 長度都達到平衡後,執行 merge_force_collapse
函式將剩餘的 run 全部合併,接著把雙向鏈結接上變為雙向串列,即完成 Timsort 排序。
首先需先了解 preorder 與 postorder 的定義