n 個元素的序列A[1].key,A[2].key,…,A[n].key,當且僅當滿足下述關係時,稱之爲堆。
大根堆 小根堆
將其看成二叉樹,則大根堆所有的父節點都不小於它的子節點,小根堆所有的父節點都不大於它的子節點,以大根堆爲例。
第一步,建堆:調整無序序列,使其滿足大根堆的定義;
第二步,篩選:輸出堆頂元素,即將堆頂元素和當前無序序列的最後一個元素交換位置,調整剩餘元素使其成爲一個新的堆,新堆元素個數減1;
第三步,繼續執行第二步,直到排序完成。
建堆是一個自下而上的調整過程,由最後一個非終端節點開始進行調整,然後依次向上調整二叉樹,使其最終滿足大根堆;調整是從上往下的過程,使不滿足大根堆定義的二叉樹交換父節點和子節點的位置,使其最終滿足大根堆的定義。
核心代碼如下:
/* 建堆 */
for (int i=n/2; i>0; --i) {
heapAdjust(array, i, n); // 由最後一個非終端節點開始進行自下而上的調整
}
System.out.println("初次建堆完成的序列:");
print(array);
/* 篩選 */
for (int i=1; i<n; ++i) {
array[0] =array[1]; // 輸出堆頂元素
array[1] = array[n-i+1];
array[n-i+1] = array[0];
heapAdjust(array, 1, n-i); // 調整堆
}
System.out.println("堆排序完成的序列:");
print(array);
/* 調整 */
heapAdjust(int[] arr, int s, int length) { // arr[s]不滿足堆的定義,length爲數組arr的長度
int temp;
for (int i=2*s; i<=length; i*=2) { // 循環是爲了使以s節點爲根結點的二叉樹滿足堆的定義
if (i<length && arr[i]<arr[i+1]) {
++i; // 令i指向關鍵字較大記錄
}
if (arr[i/2] >= arr[i])
break; // >=成立,說明調整完畢
temp = arr[i]; // 否則使其和關鍵字較大的記錄交換位置
arr[i] = arr[i/2];
arr[i/2] = temp;
}
}
運行結果:
注:(1)堆排序是不穩定的排序。
(2)時間複雜度爲O(nlog2n),最壞情況下時間複雜度爲O(nlog2n)的算法。
(3)空間複雜度爲O(1)。