排序算法(五) 堆排序(選擇排序的進化)

參考
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作為父節點)
【紅色表示交換后的狀態】

Paste_Image.png

2.第二次for循環將節點2和它的子節點5 6的元素進行比較,最大者為父節點(元素80作為父節點)

Paste_Image.png

3.第三次for循環將節點1和它的子節點3 4的元素進行比較,最大者為父節點(元素70作為父節點)

Paste_Image.png

4.第四次for循環將節點0和它的子節點1 2的元素進行比較,最大者為父節點(元素80作為父節點)

Paste_Image.png

(注意這里,元素20和元素80交換后,20所在的節點還有子節點,所以還要再和它的子節點5 6的元素進行比較,這就是28行代碼** i = j **的原因)

至此有序堆已經構造好了!如下圖:

Paste_Image.png
二、調整堆

下面進行while循環
(1)堆頂元素80和尾40交換后-->調整堆

Paste_Image.png

(2)堆頂元素70和尾30交換后-->調整堆

Paste_Image.png

(3)堆頂元素60尾元素20交換后-->調整堆

Paste_Image.png

(4)其他依次類推,最終已排好序的元素如下:

Paste_Image.png
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)
堆排序對原始記錄的排序狀態并不敏感,其在性能上要遠遠好過于冒泡、簡單選擇、直接插入排序。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容