【算法】三、堆排序

堆排序是一種選擇排序,其時間複雜度爲O(nlogn)。html

堆的定義java

  n個元素的序列{k1,k2,…,kn}當且僅當知足下列關係之一時,稱之爲堆。windows

  情形1:k<= k2i 且k<= k2i+1 最小化堆小頂堆數組

  情形2:k>= k2i 且k>= k2i+1 (化堆大頂堆數據結構

  其中i=1,2,…,n/2向下取整;測試

     

                 

 

  若將和此序列對應的一維數組(即以一維數組做此序列的存儲結構)當作是一個徹底二叉樹,則堆的含義代表,徹底二叉樹中全部非終端結點的值均不大於(或不小於)其左、右孩子結點的值。spa

  由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或徹底二叉樹的根)必爲序列中n個元素的最小值(或最大值)。.net

  例如,下列兩個序列爲堆,對應的徹底二叉樹如圖:code

  

 

  若在輸出堆頂的最小值以後,使得剩餘n-1個元素的序列重又建成一個堆,則獲得n個元素的次小值。如此反覆執行,便能獲得一個有序序列,這個過程稱之爲堆排序htm

  堆排序(Heap Sort)只須要一個記錄元素大小的輔助空間(供交換用),每一個待排序的記錄僅佔有一個存儲空間。

 

堆的存儲

  通常用數組來表示堆,若根結點存在序號0處, i結點的父結點下標就爲(i-1)/2。i結點的左右子結點下標分別爲2*i+1和2*i+2。

  (注:若是根結點是從1開始,則左右孩子結點分別是2i和2i+1。)

  如第0個結點左右子結點下標分別爲1和2。

  如最大化堆以下:

   

 

  左圖爲其存儲結構,右圖爲其邏輯結構。

堆排序的實現

  實現堆排序須要解決兩個問題:

    1.如何由一個無序序列建成一個堆?

    2.如何在輸出堆頂元素以後,調整剩餘元素成爲一個新的堆?

 

  先考慮第二個問題,通常在輸出堆頂元素以後,視爲將這個元素排除,而後用表中最後一個元素填補它的位置,自上向下進行調整:首先將堆頂元素和它的左右子樹的根結點進行比較,把最小的元素交換到堆頂;而後順着被破壞的路徑一路調整下去,直至葉子結點,就獲得新的堆。

  咱們稱這個自堆頂至葉子的調整過程爲「篩選」。

  從無序序列創建堆的過程就是一個反覆「篩選」的過程。

構造初始堆

  初始化堆的時候是對全部的非葉子結點進行篩選。

  最後一個非終端元素的下標是[n/2]向下取整,因此篩選只須要從第[n/2]向下取整個元素開始,從後往前進行調整。

  好比,給定一個數組,首先根據該數組元素構造一個徹底二叉樹。

  而後從最後一個非葉子結點開始,每次都是從父結點、左孩子、右孩子中進行比較交換,交換可能會引發孩子結點不知足堆的性質,因此每次交換以後須要從新對被交換的孩子結點進行調整。

進行堆排序

  有了初始堆以後就能夠進行排序了。

  堆排序是一種選擇排序。創建的初始堆爲初始的無序區。

  排序開始,首先輸出堆頂元素(由於它是最值),將堆頂元素和最後一個元素交換,這樣,第n個位置(即最後一個位置)做爲有序區,前n-1個位置還是無序區,對無序區進行調整,獲得堆以後,再交換堆頂和最後一個元素,這樣有序區長度變爲2。。。

  不斷進行此操做,將剩下的元素從新調整爲堆,而後輸出堆頂元素到有序區。每次交換都致使無序區-1,有序區+1。不斷重複此過程直到有序區長度增加爲n-1,排序完成。

堆排序實例

   首先,創建初始的堆結構如圖:

  

  而後,交換堆頂的元素和最後一個元素,此時最後一個位置做爲有序區(有序區顯示爲黃色),而後進行其餘無序區的堆調整,從新獲得大頂堆後,交換堆頂和倒數第二個元素的位置……

  

  重複此過程:

  

 

  最後,有序區擴展完成即排序完成:

  

 

  由排序過程可見,若想獲得升序,則創建大頂堆,若想獲得降序,則創建小頂堆

代碼

  假設排列的元素爲整型,且元素的關鍵字爲其自己。

  由於要進行升序排列,因此用大頂堆。

  根結點從0開始,因此i結點的左右孩子結點的下標爲2i+1和2i+2。

 

void HeapAdjust(int H[], int start, int end) {

        int temp = H[start];
        for (int i = 2 * start + 1; i <= end; i = 2 * i + 1) {
            //由於假設根結點的序號爲0而不是1,因此i結點左孩子和右孩子分別爲2i+1和2i+2
            if (i < end && H[i] < H[i + 1])//左右孩子的比較
            {
                i++;//i爲較大的記錄的下標
            }

            if (temp > H[i])//左右孩子中獲勝者與父親的比較
            {
                break;
            }

            //將孩子結點上位,則以孩子結點的位置進行下一輪的篩選
            H[start] = H[i];
            start = i;

        }
        H[start] = temp; //插入最開始不和諧的元素
    }

    void HeapSort(int A[], int n) {
        //先創建大頂堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            HeapAdjust(A, i, n - 1);
        }
        //進行排序
        for (int i = n; i > 0; i--) {
            //最後一個元素和第一元素進行交換
            int temp = A[i];
            A[i] = A[0];
            A[0] = temp;

            //而後將剩下的無序元素繼續調整爲大頂堆
            HeapAdjust(A, 0, i - 1);
        }
    }

 

堆排序分析

  堆排序方法對記錄數較少的文件並不值得提倡,但對n較大的文件仍是頗有效的。由於其運行時間主要耗費在建初始堆和調整建新堆時進行的反覆「篩選」上。

  堆排序在最壞的狀況下,其時間複雜度也爲O(nlogn)。相對於快速排序來講,這是堆排序的最大優勢。此外,堆排序僅需一個記錄大小的供交換用的輔助存儲空間。

參考資料:

  嚴蔚敏《數據結構》

  http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html

  http://blog.csdn.net/morewindows/article/details/6709644

出處:http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html  (對原文代碼有改動。本文代碼爲java代碼。通過了博主測試,運行沒有問題。)

相關文章
相關標籤/搜索