排序|優先隊列不知道,先看看堆排序吧

前言

在我的的專欄中,其餘排序陸陸續續都已經寫了,而堆排序遲遲沒有寫,趁着國慶假期的尾聲,把堆排序也寫一寫。
插入類排序—(折半)插入排序、希爾排序java

交換類排序—冒泡排序、快速排序手撕圖解算法

歸併類排序—歸併排序(逆序數問題)數組

計數排序引起的圍觀風波——一種O(n)的排序微信

兩分鐘搞懂桶排序數據結構

對於常見的快排、歸併這些O(nlogn)的排序算法,我想大部分人可能很容易搞懂,可是堆排序大部分人可能比較陌生,或許Java的comparator接口中可能瞭解一點。堆排序在應用中好比優先隊列此類維護動態數據效率比較高,有着很是普遍的應用。函數

而堆排序能夠拆分紅排序,其中你可能對堆比較陌生,對排序比較熟悉,下面就帶你完全瞭解相關內容。
學習


什麼是堆?spa

談起堆,不少人第一聯想到的是土堆,而在數據結構中這種土堆與徹底二叉樹更像,而堆就是一類特殊的數據結構的統稱。堆一般是一個能夠被看作一棵樹(徹底)的數組對象。且老是知足如下規則:.net

  • 堆是一棵徹底二叉樹設計

  • 每一個節點老是大於(或小於)它的孩子節點。

徹底二叉樹
我想什麼是徹底二叉樹大部分人也是知道:最後一層以上都是滿的,最後一層節點從左到右能夠排列(有任何空缺即不知足徹底二叉樹)。



看做樹的數組對象
咱們都知道咱們排序的對象通常都是對數組之類的序列進行排序,若是轉成抽象數據結構再實現可能成本比較大。

咱們正常在構造一棵二叉樹的時候一般採用鏈式left,right節點,但其實二叉樹的表示方式用數組也能夠實現,只不過普通的二叉樹若是用數組儲存可能空間利用 效率會很低而不多采用,但咱們的堆是一顆徹底二叉樹。使用數組儲存空間使用效率也比較高,因此在形式上咱們把這個數組當作對應的徹底二叉樹,而操做上能夠直接操做數組也比較方便。

在這裏插入圖片描述

大根堆 VS 小根堆
上面還有一點就是在這個徹底二叉樹中全部節點均大於(或小於)它的孩子節點,因此這裏就分爲兩種狀況

  • 若是全部節點大於孩子節點值,那麼這個堆叫作大根堆,堆的最大值在根節點。

  • 若是全部節點小於孩子節點值,那麼這個堆叫作小根堆,堆的最小值在根節點。


堆排序

經過上面的介紹,我想你對堆應該有了必定的認識,堆排序確定是藉助堆實現的某種排序,其實堆排序的總體思路也很簡單,就是

  • 構建堆,取堆頂爲最小(最大)。

  • 將剩下的元素從新構建一個堆,取堆頂,一直到元素取完爲止。

建堆

若是給一個無序的序列,首先要給它建成一個堆,咱們如何實現這個操做呢?如下拿一個小根堆爲例進行分析。

對於二叉樹(數組表示),咱們從下往上進行調整,從第一個非葉子節點開始向前調整,對於調整的規則以下:

①對於小根堆,當前節點與左右孩子比較,若是均小於左右孩子節點,那麼它自己就是一個小根堆,它不須要作任何改變,若是左右有孩子節點比它還小,那麼就要和最小的那個進行替換。



②可是普通節點替換可能沒問題,對於某些和子節點替換有可能改變子樹成堆,因此須要繼續往下判斷交換(最差判斷到葉子節點)。




分析構造堆的這個過程,每一個非葉子節點都須要判斷比較是否交換,這樣一層就是O(n),而每一個節點可能替換以後影響子節點成堆須要再往下判斷遍歷,你可能會認爲它是一個O(nlogn),但其實你看看二叉樹性值,大部分都是在底部的,上面的只有不多個數,若是你用數學方法去求得最終的複雜度它仍是一個O(n)級別,這裏就不做詳細介紹了。

一個大根堆創建過程也是同樣的:


堆排序

上面的一個堆建造完畢以後,咱們怎麼去利用這個堆實現排序呢?答案也是很簡單的,咱們知道堆有一個特性就是堆頂是最小(或最大),而咱們建造這個若是去除第一個元素,剩餘左右孩子依然知足堆的性質



最後一個元素放置堆頂,因爲第一個元素的存在使得整個不知足堆性質。分析這個結構,和咱們前面構造堆的過程當中構造到第一個元素的操做相同:


  • 判斷左右孩子,若是須要交換則交換,交換後再次考慮交換子節點是否須要交換。一直到不須要考慮。



這樣到最後,堆排序便可完成,最終獲得的序列即爲堆排序序列。

一個大根堆的排序過程以下:


具體實現

有了上述的思想以後,如何具體的實現這個堆排序的代碼呢?
從細緻的流程來看,大概流程是以下的:

給定數組建堆(creatHeap)

  • 從第一個非葉子節點開始判斷交換下移(shiftDown),使得當前節點和子孩子可以保持堆的性值

  • 若是交換打破子孩子堆結構性質,那麼就要從新下移(shiftDown)被交換的節點一直到中止。

堆構造完成,取第一個堆頂元素爲最小(最大),剩下左右孩子依然知足堆的性值,可是缺個堆頂元素,若是給孩子調上來,可能會調動太多而且可能破壞堆結構。

  • 因此索性把最後一個元素放到第一位。這樣只須要判斷交換下移(shiftDown),不過須要注意此時整個堆的大小已經發生了變化,咱們在邏輯上不會使用被拋棄的位置,因此在設計函數的時候須要附帶一個堆大小的參數。

  • 重複以上操做,一直堆中全部元素都被取得中止。

而堆算法複雜度的分析上,以前建堆時間複雜度是O(n)。而每次刪除堆頂而後須要向下交換,每一個個數最壞爲logn個。這樣複雜度就爲O(nlogn).總的時間複雜度爲O(n)+O(nlogn)=O(nlogn).

具體實現的代碼以下:

import java.util.Arrays;

public class 堆排序 {

    static void swap(int arr[],int m,int n)
    
{
        int team=arr[m];
        arr[m]=arr[n];
        arr[n]=team;
    }
    //下移交換 把當前節點有效變換成一個堆(小根)
    static void shiftDown(int arr[],int index,int len)//0 號位置不用
    
{
        int leftchild=index*2+1;//左孩子
        int rightchild=index*2+2;//右孩子
        if(leftchild>=len)
            return;
        else if(rightchild<len&&arr[rightchild]<arr[index]&&arr[rightchild]<arr[leftchild])//右孩子在範圍內而且應該交換
        {
            swap(arr, index, rightchild);//交換節點值
            shiftDown(arr, rightchild, len);//可能會對孩子節點的堆有影響,向下重構
        }
        else if(arr[leftchild]<arr[index])//交換左孩子
        {
            swap(arr, index, leftchild);
            shiftDown(arr, leftchild, len);
        }
    }
    //將數組建立成堆
    static void creatHeap(int arr[])
    
{
        for(int i=arr.length/2;i>=0;i--)
        {
            shiftDown(arr, i,arr.length);
        }
    }
    static void heapSort(int arr[])
    
{
        System.out.println("原始數組爲         :"+Arrays.toString(arr));
        int val[]=new int[arr.length]; //臨時儲存結果
        //step1建堆
        creatHeap(arr);
        System.out.println("建堆後的序列爲  :"+Arrays.toString(arr));
        //step2 進行n次取值建堆,每次取堆頂元素放到val數組中,最終結果即爲一個遞增排序的序列
        for(int i=0;i<arr.length;i++)
        {
            val[i]=arr[0];//將堆頂放入結果中
            arr[0]=arr[arr.length-1-i];//刪除堆頂元素,將末尾元素放到堆頂
            shiftDown(arr, 0, arr.length-i);//將這個堆調整爲合法的小根堆,注意(邏輯上的)長度有變化
        }
        //數值克隆複製
        for(int i=0;i<arr.length;i++)
        {
            arr[i]=val[i];
        }
        System.out.println("堆排序後的序列爲:"+Arrays.toString(arr));

    }
    public static void main(String[] args) {
        int arr[]= {14,12,16,8,9,1,14,9,6 };
        heapSort(arr);  
    }

}

執行結果:

在這裏插入圖片描述

固然,代碼爲了成章節我把它命名爲中文,還有些不規範的地方請注意甄別。

結語

對於堆排序就先介紹到這裏了,固然堆的強大之處不止這麼一點,優先隊列一樣也是用到堆可是這裏就不詳細介紹了,我相信優秀的你確定又掌握了一門O(nlogn)級別的排序算法啦。若是寫的有啥不確切地方還請指正。

最後,若是感受不錯一鍵三聯哦!,歡迎關注公衆號:bigsai,更多精彩等你來看。期待你的關注,而且筆者也準備了一波pdf學習資源分享給你。


另外歡迎添加筆者微信交流:

點贊、在看二連



本文分享自微信公衆號 - bigsai(bigsai)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索