從零開始認識堆排序
一、什麼是堆?
維基百科的解釋是:堆是一種特別的樹狀資料結構,它需要滿足任意的子節點必須都大於等於(最大堆)或者小於等於(最小堆)其父節點。
二、堆排序
堆排序是通過二叉堆資料結構實現,二叉堆滿足一下兩個特性:
1、滿足對的基本特性
2、完全二叉樹,除了最底層外,其它層都已填充滿,且是從左到右填充。
二叉堆的高度即為根節點到葉子節點的最長簡單路徑長度,即為θ(lgn)。
二叉堆上的操作時間複雜度為O(lgn)。
1、二叉堆中的元素個數
根據二叉堆的特性2,我們知道高度為h的二叉堆重元素個數如下:
根節點為1
第一層為2=21
第二層為4=22
...
第h-1層為2h-1
第h層元素個數範圍為[1,2h
最底層之外的元素個數和為1+2+22+...+2h-1=(1-2h-1)/(1-2)=2h-1
高度為h的二叉堆元素個數範圍:[2h-1 + 1,2h-1+2h]=[2h,2h+1-1]
以高度為3的最大堆為例:
圖1
圖2
2、二叉堆的高度
由二.1推導,我們知道高度為h的二叉堆的元素個數n滿足:
2h ≦ n ≦ 2h+1-1
=>
2h ≦ 2lgn ≦ 2h+1-1
=>
h ≦ lgn < h+1
由此可得,含有n個元素的二叉堆的高度為θ(lgn)
3、使用陣列表示堆儲存
節點下標 i,則父節點下標為 i/2,左子節點下標為 2i,右子節點下標 2i + 1。
以圖1最大堆為例:
從根節點開始,根節點下標 1。
第一層節點下標:2、3
第二層節點下標:4、5、6、7
第三層節點下標:8
圖3
陣列形式:
圖4
具體到特定的程式語言,陣列以0開始下標的,推導:
對於節點 i,則其父節點為 (i - 1)/2,左子節點下標為 2i + 1,右子節點下標 2i + 2。
4、堆的葉子節點
對於有n個元素的二叉堆,最後一個元素的下標為為n,根據二叉堆的性質,其父節點下標為n/2,因為每一層是由左向右進行構建,所以其父節點也是倒數第二層的最後一個節點,所以,其後的節點都為最底層節點,為葉子節點,下標為n/2 + 1、n/2 + 2... n。
具體到特定的程式語言,陣列以0開始下標的,推到:
葉子節點下標為(n-1)/2 + 1、(n-1)/2 + 2... n。
5、堆維護
所謂堆維護,即保持堆的基本特性,以最大堆為例:給定某個節點,維護使得以其為根節點的子堆為滿足子節點都小於等於父節點。
如下,給定堆構建陣列,及特定元素下標i:
public static void maxHeapify(int[] arr, int i) { int size = arr.length; //堆大小 int maxIndex = i; //記錄當前節點及其子節點的最大值節點索引 int left = 2 * i + 1; //左子節點索引 int right = 2 * i + 2; //右子節點索引 //對比節點及其左子節點 if (left < size && arr[left] > arr[maxIndex]) { maxIndex = left; } //對比節點及其右子節點 if (right < size && arr[right] > arr[maxIndex]) { maxIndex = right; } //不滿足最大堆性質,則進行下沉節點i,遞迴處理 if (maxIndex != i) { int tmp = arr[i]; arr[i] = arr[maxIndex]; arr[maxIndex] = tmp; maxHeapify(arr, maxIndex); } }
如下圖,堆中元素9的維護過程:
圖5
堆維護過程的時間複雜度:O(lgn)。
6、構建堆
根據二.4我們可以得到所有葉子節點的下標。我們可以使用二.5中的堆維護過程,對所堆中所有的非葉子節點執行堆維護操作進行堆的構建。
public static void buildHeap(int[] arr) { for (int i = (arr.length - 1) / 2; i >= 0; i--) { maxHeapify(arr, i); } }
以陣列 {27,17,3,16,13,10,1,5,7,12,4,8,9,0} 為例進行堆構建,結果為:{27,17,10,16,13,9,1,5,7,12,4,8,3,0}
圖6
構建最大堆的時間複雜度為O(n)。
7、堆排序
首先執行最大堆構建,當前堆中最大值會上升到根節點,也就是堆陣列的首節點。
我們可以通過交換首尾節點,使得最大值轉移至尾部,然後對除尾部元素外的堆陣列執行根元素堆維護,上浮堆最大值。
然後,將最大值交換至陣列尾部倒數第二個元素位置,重新執行剩餘堆陣列的根元素堆維護,依次類推,直至剩餘堆陣列大小變為2為止。
以二.6中陣列為例:{27,17,3,16,13,10,1,5,7,12,4,8,9,0}
第一次執行:
{27,17,10,16,13,9,1,5,7,12,4,8,3,0},max:27
第二次執行:
{17,16,10,7,13,9,1,5,0,12,4,8,3},max:17
第三詞執行:
{16,13,10,7,12,9,1,5,0,3,4,8},max:16
第四次執行:
{13,12,10,7,8,9,1,5,0,3,4},max:13
第五次執行:
{12,8,10,7,4,9,1,5,0,3},max:12
第六次執行:
{10,8,9,7,4,3,1,5,0},max:10
第七次執行:
{9,8,3,7,4,0,1,5},max:9
第八次執行:
{8,7,3,5,4,0,1},max:8
第九次執行:
{7,5,3,1,4,0},max:7
第十次執行:
{5,4,3,1,0},max:5
第十一次執行:
{4,1,3,0},max:4
第十二次執行:
{3,1,0},max:3
第十三次執行:
{1,0},max:1
堆排序時間複雜度:O(nlgn)
&n