回顧一下我們學過的選擇排序,在無序區找到一個最小(大)的元素需要比較n-1次,找到第二小的元素需要比較n-2次,直到最后比較1次。而堆排序因為二叉堆的性質,堆頂就是最大的元素,查找次數只有一次,但是將無序轉成有序中間還需要一個預處理過程:構造堆有序。
堆有序並不代表數組有序,堆有序是滿足二叉堆性質的:
1.父節點的鍵值總是優先於任何一個子節點的鍵值;
2.左右子樹都是一個二叉堆。
所以堆排序分為兩個階段,構造堆有序和下沉排序階段。
構造堆有序階段是將原始數據重新組織安排到一個二叉堆中,堆有序就是一棵二叉樹的每個節點都優先於它的兩個子節點,不代表數組有序;
下沉排序階段是將二叉堆中取出優先級高的直至取出所有元素,得到數組有序結果。
構造堆有序的方式有兩種,一種是上浮,另一種是下沉。這里以最大堆為例。
自低向上的堆有序化(上浮)
上浮是某節點與其父節點的比較,如果某節點比其父節點要大,就通過交換的方式進行修復堆。如果這個節點仍然比現在的父節點還大,可以通過循環的方式進行修復堆,直到遇到更大的父節點。
那從哪個節點開始呢?
如果從數組最后一個節點進行開始上浮操作,它的父節點和祖父節點大小關系是不確定的,如果它的祖父節點比它父節點小,並不能保證整個完全二叉樹能夠二叉堆化。
所以從根節點的左孩子開始進行,數組下標為1開始,進行自底向上的堆有序化。在循環進行比較的時候會發現,這個節點已經進行過修復堆了,所以當上浮操作不再交換的時候可以做一個標記,截止進行比較。
如果能夠合理優化的話,在時間上的消耗有可能要比下沉階段的還要少。
動畫
Code
Result
初始狀態 [13, 9, 15, 11, 3, 7, 17, 5, 1]
交換 [15, 9, 13, 11, 3, 7, 17, 5, 1]
交換 [15, 11, 13, 9, 3, 7, 17, 5, 1]
交換 [15, 11, 17, 9, 3, 7, 13, 5, 1]
交換 [17, 11, 15, 9, 3, 7, 13, 5, 1]
[17, 11, 15, 9, 3, 7, 13, 5, 1]
自頂向下的堆有序化(下沉)
下沉是當前節點和左右孩子三者之中的比較,在整個進行自頂向下堆有序化的過程中,它是從非葉子節點開始的,即數組最后一個節點的父節點。
動畫
Code
Result
初始狀態 [13, 9, 15, 11, 3, 7, 17, 5, 1]
交換 [13, 9, 17, 11, 3, 7, 15, 5, 1]
交換 [13, 11, 17, 9, 3, 7, 15, 5, 1]
交換 [17, 11, 13, 9, 3, 7, 15, 5, 1]
交換 [17, 11, 15, 9, 3, 7, 13, 5, 1]
[17, 11, 15, 9, 3, 7, 13, 5, 1]
下沉排序
堆排序的主要工作還是在第二階段完成的,下沉排序。這里我們將堆中最大的元素取出,和數組最后一個元素做交換,數組可操作的長度也相應的縮小一個位置。然后從堆頂開始進行下沉操作,無論循環多次都是從堆頂開始,直到數組可操作的長度最后為1。
動畫
Code
Result
初始狀態 [13, 9, 15, 11, 3, 7, 17, 5, 1]
堆有序
交換 [13, 9, 17, 11, 3, 7, 15, 5, 1]
交換 [13, 11, 17, 9, 3, 7, 15, 5, 1]
交換 [17, 11, 13, 9, 3, 7, 15, 5, 1]
交換 [17, 11, 15, 9, 3, 7, 13, 5, 1]
排序
交換 [15, 11, 1, 9, 3, 7, 13, 5, 17]
交換 [15, 11, 13, 9, 3, 7, 1, 5, 17]
交換 [13, 11, 5, 9, 3, 7, 1, 15, 17]
交換 [13, 11, 7, 9, 3, 5, 1, 15, 17]
交換 [11, 1, 7, 9, 3, 5, 13, 15, 17]
交換 [11, 9, 7, 1, 3, 5, 13, 15, 17]
交換 [9, 5, 7, 1, 3, 11, 13, 15, 17]
交換 [7, 5, 3, 1, 9, 11, 13, 15, 17]
交換 [5, 1, 3, 7, 9, 11, 13, 15, 17]
[1, 3, 5, 7, 9, 11, 13, 15, 17]