1、基本思想
堆是一種特殊的樹形數據結構,其每個節點都有一個值,通常提到的堆都是指一顆完全二叉樹,根結點的值小於(或大於)兩個子節點的值,同時,根節點的兩個子樹也分別是一個堆。
堆排序就是利用堆(假設利用大頂堆)進行排序的方法。它的基本思想是,將待排序的序列構造成一個大頂堆。此時,整個序列的最大值就是堆頂的根節點。將它移走(其實就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的 n-1 個序列重新構造成一個堆,這樣就會得到 n 個元素中次大的值。如此反覆執行,便能得到一個有序序列了。
堆排序的實現需要解決的兩個關鍵問題:
(1)將一個無序序列構成一個堆。
(2)輸出堆頂元素後,調整剩餘元素成爲一個新堆。
2、複雜度分析
堆排序的運行時間主要耗費在初始構建堆和在重建堆時反覆篩選上。在構建對的過程中,因爲我們是完全二叉樹從最下層最右邊的非終端節點開始構建,將它與其孩子進行比較和若有必要的互換,對每個非終端節點來說,其實最多進行兩次比較和互換操作,因此整個構建堆的時間複雜度爲O(n)。
在正式排序時,第i次取堆頂記錄重建堆需要用O(logi)的時間(完全二叉樹的某個節點到根節點的距離爲),並且需要取n-1次堆頂記錄,因此,重建堆的時間複雜度爲O(nlogn)。
所以總體來說,堆排序的時間複雜度爲O(nlogn),由於堆排序對原始記錄的狀態並不敏感,因此它無論是最好、最壞和平均時間複雜度均爲O(nlogn)。這在性能上顯然要遠遠好過於冒泡、簡單選擇、直接插入的時間複雜度了。
空間複雜度上,它只有一個用來交換的暫存單元,也非常的不錯。不過由於記錄的比較與交換是跳躍式進行的,因此堆排序也是一種不穩定的排序方法。
另外,由於出事構建堆所需要的比較次數比較多,因此,他並不適合待排序序列個數較少的情況。
3.大根堆排序算法的基本操作
① 初始化操作:將R[1..n]構造爲初始堆;
②每一趟排序的基本操作:將當前無序區的堆頂記錄R[1]和該區間的最後一個記錄交換,然後將新的無序區調整爲堆(亦稱重建堆)。
注意:
①只需做n-1趟排序,選出較大的n-1個關鍵字即可以使得文件遞增有序。
②用小根堆排序與利用大根堆類似,只不過其排序結果是遞減有序的。堆排序和直接選擇排序相反:在任何時刻堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由後往前逐步擴大至整個向量爲止。
4、Java實現如下
public class HeapSort {
/**
* 構建大頂堆
*/
public static void adjustHeap(int[] a, int i, int len) {
int temp, j;
temp = a[i];
for (j = 2 * i; j < len; j *= 2) {// 沿關鍵字較大的孩子結點向下篩選
if (j < len && a[j] < a[j + 1])
++j; // j爲關鍵字中較大記錄的下標
if (temp >= a[j])
break;
a[i] = a[j];
i = j;
}
a[i] = temp;
}
public static void heapSort(int[] a) {
int i;
for (i = a.length / 2 - 1; i >= 0; i--) {// 構建一個大頂堆
adjustHeap(a, i, a.length - 1);
}
for (i = a.length - 1; i >= 0; i--) {// 將堆頂記錄和當前未經排序子序列的最後一個記錄交換
int temp = a[0];
a[0] = a[i];
a[i] = temp;
adjustHeap(a, 0, i - 1);// 將a中前i-1個記錄重新調整爲大頂堆
}
}
public static void main(String[] args) {
int a[] = { 51, 46, 20, 18, 65, 97, 82, 30, 77, 50 };
heapSort(a);
System.out.println(Arrays.toString(a));
}
}