參考
Java排序算法(五):堆排序
圖解堆排序
數據結構和算法(Golang實現)(24)排序算法-優先隊列及堆排序
堆排序的相對于選擇排序的進步就是,下一次取最小(最大)值時能使用上一次的對比信息。如果每次都只是找出根節點,那就只是普通的選擇排序了;每次取出最值后,對堆的調整都是為了之后更快實現堆調整而存在的。
堆排序是一種樹形選擇排序,在排序過程中可以把元素看成是一顆完全二叉樹,每個節點都大(小)于它的兩個子節點,當每個節點都大于等于它的兩個子節點時,就稱為大頂堆,也叫堆有序; 當每個節點都小于等于它的兩個子節點時,就稱為小頂堆。
算法思想(以大頂堆為例):
1.將長度為n的待排序的數組進行堆有序化構造成一個大頂堆
2.將根節點與尾節點交換并輸出此時的尾節點
3.將剩余的n -1個節點重新進行堆有序化
4.重復步驟2,步驟3直至構造成一個有序序列
一、構造堆
假設待排序數組為[20,50,10,30,70,20,80]
在構造有序堆時,我們開始只需要掃描一半的元素(n/2-1 ~ 0)即可,為什么?
因為(n/2-1)~0的節點才有子節點,如圖1,n=8,(n/2-1) = 3 即3 2 1 0這個四個節點才有子節點
所以代碼4~6行for循環的作用就是將3 2 1 0這四個節點從下到上,從右到左的與它自己的子節點比較并調整最終形成大頂堆,過程如下:
1.第一次for循環將節點3和它的子節點7 8的元素進行比較,最大者作為父節點(即元素60作為父節點)
【紅色表示交換后的狀態】
2.第二次for循環將節點2和它的子節點5 6的元素進行比較,最大者為父節點(元素80作為父節點)
3.第三次for循環將節點1和它的子節點3 4的元素進行比較,最大者為父節點(元素70作為父節點)
4.第四次for循環將節點0和它的子節點1 2的元素進行比較,最大者為父節點(元素80作為父節點)
(注意這里,元素20和元素80交換后,20所在的節點還有子節點,所以還要再和它的子節點5 6的元素進行比較,這就是28行代碼** i = j **的原因)
至此有序堆已經構造好了!如下圖:
二、調整堆
下面進行while循環
(1)堆頂元素80和尾40交換后-->調整堆
(2)堆頂元素70和尾30交換后-->調整堆
(3)堆頂元素60尾元素20交換后-->調整堆
(4)其他依次類推,最終已排好序的元素如下:
public class HeapSort {
private static void heapSort(int[] arr) {
int len = arr.length -1;
for(int i = len/2 - 1; i >=0; i --){ //堆構造
heapAdjust(arr,i,len);
}
while (len >=0){
swap(arr,0,len--); //將堆頂元素與尾節點交換后,長度減1,尾元素最大
heapAdjust(arr,0,len); //再次對堆進行調整
}
}
public static void heapAdjust(int[] arr,int i,int len){
int left,right,j ;
while((left = 2*i+1) <= len){ //判斷當前父節點有無左節點(即有無孩子節點,left為左節點)
right = left + 1; //右節點
j = left; //j"指針指向左節點"
if(j < len && arr[left] < arr[right]) //右節點大于左節點
j ++; //當前把"指針"指向右節點
if(arr[i] < arr[j]) //將父節點與孩子節點交換(如果上面if為真,則arr[j]為右節點,如果為假arr[j]則為左節點)
swap(arr,i,j);
else //說明比孩子節點都大,直接跳出循環語句
break;
i = j;
}
}
public static void swap(int[] arr,int i,int len){
int temp = arr[i];
arr[i] = arr[len];
arr[len] = temp;
}
public static void main(String[] args) {
int array[] = {20,50,20,40,70,10,80,30,60};
System.out.println("排序之前:");
for(int element : array){
System.out.print(element+" ");
}
heapSort(array);
System.out.println("\n排序之后:");
for(int element : array){
System.out.print(element+" ");
}
}
}
輸出:
排序之前:20 50 20 40 70 10 80 30 60
排序之后:10 20 20 30 40 50 60 70 80
堆排序時間復雜度:O(nlogn)
堆排序對原始記錄的排序狀態并不敏感,其在性能上要遠遠好過于冒泡、簡單選擇、直接插入排序。