堆排序缺點何在?
最近在看coursera上的演算法課,裡面講堆排序那集,最後提到,heap sort 「make poor use of cache memory". 想了一下不太懂。他好像是拿堆排序跟快速排序作對比地說。
鏈接 : Algorithms, Part I最後面他的解釋,不甚明白。 為什麼比快排 make poor use of cache memory?
(謝謝邀請)
平均時間上,堆排序的時間常數比快排要大一些,因此通常會慢一些,但是堆排序最差時間也是O(nlogn)的,這點比快排好。
關於 poor use of cache memory,我的理解是快排在遞歸進行部分的排序的時候,只會訪問局部的數據,因此緩存能夠更大概率的命中;而堆排序的建堆過程是整個數組各個位置都訪問到的,後面則是所有未排序數據各個位置都可能訪問到的,所以不利於緩存發揮作用。簡答的說就是快排的存取模型的局部性(locality)更強,堆排序差一些。我理解,速度和緩存的問題都反映了堆排序讓數據過於大距離的移動,你觀察某個元素在整個排序過程中的移動過程,會發現它是前後大幅度的跑動;而快速排序則是儘快的移動到最終的位置,然後做小範圍的跳動。下面是10個隨機數進行快速排序和堆排序的過程,你可以盯著某個元素往下看,會發現堆排序中,一些元素在左右亂跳,而快速排序中這樣的情況會少很多。(數據產生的 Go 源代碼在這裡: Path of quick-sort and heap sort.)Data {9 4 2 6 8 0 3 1 7 5}
Index [0 1 2 3 4 5 6 7 8 9]
Quick-sort
[[0 1 2 3 4 5 6 7 8 9]
[9 1 2 3 4 5 6 7 8 0]
[9 1 2 7 4 5 6 3 8 0]
[9 1 2 7 6 5 4 3 8 0]
[5 1 2 7 6 9 4 3 8 0]
[5 1 2 7 6 9 3 4 8 0]
[5 1 2 7 6 9 3 8 4 0]
[5 2 1 7 6 9 3 8 4 0]
[5 2 7 1 6 9 3 8 4 0]
[5 7 2 1 6 9 3 8 4 0]
[5 7 2 6 1 9 3 8 4 0]](11x10)
Heap-sort
[[0 1 2 3 4 5 6 7 8 9]
[0 1 2 8 4 5 6 7 3 9]
[0 1 6 8 4 5 2 7 3 9]
[0 4 6 8 1 5 2 7 3 9]
[0 4 6 8 9 5 2 7 3 1]
[1 4 6 8 9 5 2 7 3 0]
[4 1 6 8 9 5 2 7 3 0]
[4 8 6 1 9 5 2 7 3 0]
[4 8 6 3 9 5 2 7 1 0]
[1 8 6 3 9 5 2 7 4 0]
[8 1 6 3 9 5 2 7 4 0]
[8 3 6 1 9 5 2 7 4 0]
[7 3 6 1 9 5 2 8 4 0]
[3 7 6 1 9 5 2 8 4 0]
[3 9 6 1 7 5 2 8 4 0]
[2 9 6 1 7 5 3 8 4 0]
[9 2 6 1 7 5 3 8 4 0]
[9 1 6 2 7 5 3 8 4 0]
[5 1 6 2 7 9 3 8 4 0]
[1 5 6 2 7 9 3 8 4 0]
[1 2 6 5 7 9 3 8 4 0]
[7 2 6 5 1 9 3 8 4 0]
[6 2 7 5 1 9 3 8 4 0]
[5 2 7 6 1 9 3 8 4 0]
[2 5 7 6 1 9 3 8 4 0]
[7 5 2 6 1 9 3 8 4 0]
[5 7 2 6 1 9 3 8 4 0]](27x10)
關於cache locality,如果願意看c++的話可以看這個:
http://m.youtube.com/watch?v=L7zSU9HI-6I簡言之快排是順著掃的,堆排序是跳著走的。快排比堆排序快還有個原因,就是平均情況下堆排序更接近n lg n,而快排要好些。詳見這篇:數學之美番外篇:快排為什麼那樣快這個遠優於是不是有欠考慮,內核裡面的sort就是用堆排序寫的。
貼一段注釋../linux-3.14.13/lib/sort.c:/**
* sort - sort an array of elements
* @base: pointer to data to sort
* @num: number of elements
* @size: size of each element
* @cmp_func: pointer to comparison function
* @swap_func: pointer to swap function or NULL
*
* This function does a heapsort on the given array. You may provide a
* swap_func function optimized to your element type.
*
* Sorting time is O(n log n) both on average and worst-case. While
* qsort is about 20% faster on average, it suffers from exploitable
* O(n*n) worst-case behavior and extra memory requirements that make
* it less suitable for kernel use.
*/
一般來說快排要優於堆排。
要說堆排差在哪裡,我覺得可能是:每次移出堆頂最大元素後,都需要從頂部維護最大堆性質導致了過多的數據交換操作。
空間複雜度
堆排和快排都是原址排序演算法,即除了輸入數組外,僅需要常數個額外的存儲空間。
空間複雜度都是 。時間複雜度
堆排的時間複雜度跟演算法相關,比較穩定。因為不管數據如何,堆排都要先構造最大堆,然後一個一個的把堆頂元素移到堆尾,之後再維持最大堆的性質。具體演算法見:排序演算法-堆排序
而快排的時間複雜度受數據的印象。但一般情況下都不會差到哪裡去。下面我們會說。
不管是以上那種排序演算法,都涉及到交換數組中兩個數據的位置。
我剛才寫了個隨機模擬程序:
分別使用堆排、快排對 20個、100 個、1000個隨機數據進行排序,並記錄平均交換數據的次數。
數組大小 20 100 1000
堆排序 78 588 6017
快排 28 271 3996
隨機試驗結果來看,快排的確比堆排好。
關於快排的不穩定
我們常說快排不穩定,跟數據有關,最壞運行時間是 ,那什麼情況下才會發生?
了解快排的同學應該知道,快排其實是把數組按照某個基準數據 進行分割,分為大於 的數據(放右邊)和小於 的數據(放左邊),接著把 放中間;然後對左右分別遞歸此過程。
那麼只有當每次分割數據時,所有數據都小於 ,或者都大於 時,才會產生 的時間複雜度。
分割操作的時間複雜度是
所以時間複雜度為:
發生這種情況的概率是: ,約等於0.
從統計學上來說,快排產生的劃分 80% 以上都比 9:1 更平衡,另外的 20% 的劃分比 9:1 更不平衡。就算所有的劃分都是 9:1 ,快排的時間複雜度依然是 .
17-08-20 更新:
上述回答沒有怎麼對 heap sort 「make poor use of cache memory" 做解釋,是因為自己也沒有想太明白。
剛才在《演算法導論》上看基數排序是也看到了這麼一句話:快速排序通常比基數排序更有效地舒勇硬體的緩存。
仔細想一想,快排除了數據交換操作少之外,它的循環比價操作是拿 與 ,進行比較,其中 是循環的下標, 是選中的基準點,一般選 首部或尾部。
而堆排序比較的是 、 與 。
在硬體基礎上,快排的基準點 每次循環時都要訪問,更容易緩存命中,但堆排的中的數據相比來說沒那麼容易。所以說 heap sort 「make poor use of cache memory"。
跟快排做對比的話
空間對比快排需要用 n * meta數據的內存堆需要2 * n * meta 數據的內存(因為堆的二分合併操作需要兩個不同的數據空間,why,自己看課本)make poor use of cache memory的意思就是堆排序的內存佔用是快排的兩倍時間對比快排是非穩定時間的,堆排序是穩定時間的,堆排序的排序時間與數據無關,快排與數據有關,(堆排序對相同長度的數組比較次數是固定的,這點估計在教材某個角落沒注意吧?)意味著同樣大小的數據,可能已經排好序的,堆排序仍然需要花上同樣的時間去排序,大部分情況下快排比堆排序要快所以由統計學意義來講,快排遠比堆要優越推薦閱讀:
※從N個數組中找出出現最多的前K個數?
※為什麼Windows系統自帶的記事本(notepad.exe)程序打開大文件這麼慢?
※有沒有什麼演算法可以確定兩圖是否同構?
※N個數最少比較多少次才能保證知道大小順序?
※演算法要怎麼學好?