一、預備知識-堆
堆排序是利用堆這種數據結構而設計的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均爲O(nlogn),它也是不穩定排序。
堆是具有以下性質的完全二叉樹:
- 每個結點的值都大於或等於其左右孩子結點的值,稱爲大根堆;
- 或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小根堆。如下圖:
通過圖可以比較直觀的看出大根堆和小根堆的特點,需要注意的是:這種結構是對父節點-左/右孩子節點之間做的約束,而對左-右孩子節點之間並沒有什麼要求。
另外,因爲堆的結構是完全二叉樹,所以可以用數組來存儲,並通過節點下標的規律快速進行索引。
下面是上圖大根堆與小根堆對應的數組:
二、堆排序基本思想圖解(大根堆爲例)
假設現在待排序數據存在array[count]中,其初始狀態如下:
對應的完全二叉樹爲:
堆排序的過程如下:
(1)初始化堆;
因爲堆是對父節點-左/右孩子節點之間的約束,所以從最後一個非葉子節點開始調整。
注意每次交換後,都要對下一層的子堆進行遞歸調整,因爲交換後有可能破壞已調整子堆的結構。
(2)進行調整後,堆頂元素(array[0])爲最大值,將最大值與堆尾部元素(array[count-1])交換,並將count值減去1,則此時得到新的無序數組array[count],此時的堆被破壞;
對應到數組元素爲:
(黃色標記爲已排序部分)
(3)調整堆:與建堆過程類似,堆頂元素被一個比較小的值代替,所以從堆頂元素開始調整,在堆頂、堆頂的左孩子、堆頂的右孩子中找出最大的與堆頂進行交換,被交換的元素再次與他下一層的左右孩子進行比較(遞歸)調整。
(4)重複(2)。
下面把整個過程畫完:
此時,大概的一個手工過程就懂了,注意的是:初始化堆是基礎,時從下向上調整。交換後調整堆時因爲有了建堆的基礎,每次調整的都是二叉樹的一支子樹,是從上往下。
三、代碼實現
package Alog;
public class HeapSort {
public static void main(String[] args) {
int[] array = new int[]{12, 5, 9 , 36, 8, 21, 7};
System.out.println("初始狀態:");
showArray(array);
initHeap(array); //這個應該也是堆排序的一部分,此處只是爲了顯示下結果
System.out.println("建堆之後:");
showArray(array);
heapSort(array);
System.out.println("排序之後:");
showArray(array);
}
public static void heapSort(int[] array){
initHeap(array); //建堆
int count = array.length;
while(count > 1) {
int tmp = array[count - 1];
array[count - 1] = array[0];
array[0] = tmp;
count--; //未排序部分又少一個
adjustHeap(array, count, 0);//調整過程自上而下,參數root=0
}
}
public static void initHeap(int[] array){
//建堆,從最後一個非葉子節點開始,而最後一個非葉子節點的下標爲array.length/2-1
for(int root = array.length/2 - 1; root >= 0; root--){
adjustHeap(array, array.length, root);
}
}
public static void adjustHeap(int[] array, int count, int root){
int maxChildIndex;
while(root <= count/2-1) { //待調整子堆的根節點必須是非葉子節點
//需要在root、letfchild、rightchild中找到最大的值,
//因爲最後一個非葉子節點有可能沒有右孩子,所以要做出判斷。
if(root == count/2 - 1 && count % 2 == 0){
//節點數量爲偶數時,最後一個非葉子節點只有左孩子
maxChildIndex = 2 * root + 1;
}else{
int leftChildIndex = 2 * root + 1;
int rightChildIndex = 2 * root + 2;
maxChildIndex = array[leftChildIndex] > array[rightChildIndex] ?
leftChildIndex : rightChildIndex;
}
if(array[root] < array[maxChildIndex]){
int tmp = array[root];
array[root] = array[maxChildIndex];
array[maxChildIndex] = tmp;
//*****************這裏很重要,繼續調整因交換結構改變的子堆
root = maxChildIndex;
}else{
return;
}
}
}
public static void showArray(int[] array){
for(int i = 0; i < array.length; i++){
System.out.print(array[i] + ((i == array.length - 1) ? "\n" : " "));
}
}
}
輸出結果:
初始狀態:
12 5 9 36 8 21 7
建堆之後:
36 12 21 5 8 9 7
排序之後:
5 7 8 9 12 21 36