1. 程式人生 > 實用技巧 >堆排序的理解

堆排序的理解

堆是具有下列性質的完全二叉樹

每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆

每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。


堆排序的過程:

1.把陣列複製一份

2.建堆

   建堆的過程就是從最下層最右邊的非終端結點開始,把兩個子結點和父結點中最大的值作為父節點,從下往上遍歷一遍。最終得到如下圖所示的完全二叉樹:

  建堆的程式碼如下:

//傳入陣列及其長度  
  private void heapbuild(int[] c, int len) {
//根據二叉樹的性質知道,i為最後一個父節點,從後往前遍歷
        for(int i=(len/2);i>=0;i--){
//根據二叉樹的性質,如果根節點是0,那麼父節點的左右子樹分別是2*i+1,2*i+2 int left=2*i+1; int right=2*i+2; //假設父節點是最大的值。 int large=i; //求出三個數中最大的,和父節點交換。 if(left<len&&c[left]>c[large]){ swap(c,left,large); } if(right<len&&c[right]>c[large]){ swap(c,right,large); } } }

3.重排

  最終陣列是要按照從小到大的順序儲存。建立的是大頂堆,所以重排的過程就是把堆頂(最大的數)和陣列的末尾交換,然後對前面的數進行重新建堆,然後再把最大的數和末尾的前一個交換,以此類推,最終得到的為從小到大排好序的陣列。

程式碼如下:

        for(int i=len;i>0;i--){
//首位交換
            swap(b,0,len-1);
//建堆的長度減一
            len--;
//建堆
            heapbuild(b,len);
        }


複雜度分析:

堆排序的效率到底有多高呢?我們來分析一下。


它的執行時間主要是消耗在初始構建堆

和在重建堆時的反覆篩選上。

在構建堆的過程中,因為我們是完全二叉樹從最下層最右邊的非終端結點開始構建,將它與其孩子進行比較和若有必要的互換,對於每個非終端結點來說,其實最多進行兩次比較和互換操作,因此整個構建堆的時間複雜度為O(n)

在正式排序時,第i次取堆頂記錄重建堆需要用O(logi)的時間,並且需要取n-1次堆頂記錄,因此,重建堆的時間複雜度為O(nlogn)

所以總體來說,堆排序的時間複雜度為O(nlogn)。由於堆排序對原始記錄的排序狀態並不敏感,因此它無論是最好、最壞和平均時間複雜度均為O(nlogn)。這在效能上顯然要遠遠好過於冒泡、簡單選擇、直接插入的O(n2)的時間複雜度了。空間複雜度上,它只有一個用來交換的暫存單元,也非常的不錯。不過由於記錄的比較與交換是跳躍式進行,因此堆排序也是一種不穩定的排序方法

另外,由於初始構建堆所需的比較次數較多,因此,它並不適合待排序序列個數較少的情況.