[028]八大排序算法詳解——堆排序

基本思想

  • 堆的定義

n個關鍵字序列kl,k2,…,kn稱爲堆,當且僅當該序列滿足如下性質之一(簡稱堆性質):

  1. ki≤k2i且ki≤k2i+1 或
  2. ki≥k2i且ki≥k2i+1(1≤i≤FLOOR(n/2))

若將此序列所存儲的向量R[1..n]看做是一棵完全二叉樹的存儲結構,則堆實質上是滿足如下性質的完全二叉樹:樹中任一非葉結點的關鍵字均不大於(或不小於)其左右孩子(若 存在)結點的關鍵字。

  • 小根堆:根結點(亦稱爲堆頂)的關鍵字是堆裏所有結點關鍵字中最小的。
  • 大根堆:根結點(亦稱爲堆頂)的關鍵字是堆裏所有結點關鍵字中最大的。

我們可以選擇大根堆或者小根堆中的任意一個來進行排序。

  • 排序思想

用大根堆排序的基本思想:

  1. 先將初始文件R[1..n]建成一個大根堆,此堆爲初始的無序區。
  2. 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄R[n]交換,由此得 到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key。
  3. 由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整爲堆。 然後再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換,由 此得到新的無序區R[1..n-2]和有序區R[n-1..n],且仍滿足關係 R[1..n-2].keys≤R[n-1..n].keys,同樣要將R[1..n-2]調整爲堆。

算法實現

堆排序算法,Java實現,代碼如下所示:

01 public abstract class Sorter {
02      public abstract void sort(int[] array);
03 }
04  
05 public class HeapSorter extends Sorter {
06  
07      public void sort(int[] array) {
08           heapSort(array);
09      }
10  
11      /**
12      * <p>堆排序方法
13      * <p>基於大根堆的堆排序方法
14      */
15      private void heapSort(int[] array) {
16           Integer tmp; // 用於交換的暫存單元
17           buildHeap(array); // 執行初始建堆,並調整
18           for (int i = 0; i < array.length; i++) {
19                // 交換堆頂元素array[0]和堆中最後一個元素array[array.length-1-i]
20                tmp = array[0];
21                array[0] = array[array.length - 1 - i];
22                array[array.length - 1 - i] = tmp;
23                // 每次交換堆頂元素和堆中最後一個元素之後,都要對堆進行調整
24                adjustHeap(array, 0, array.length - 1 - i);
25           }
26      }
27  
28      /**
29      * <p>
30      * 建堆方法
31      * <p>
32      * 調整堆中0~array.length/2個結點,保持堆的性質
33      *
34      */
35      private void buildHeap(int[] array) {
36           // 求出當前堆中最後一個存在孩子結點的索引
37           int pos = (array.length - 1) / 2;
38           // 從該結點結點開始,執行建堆操作
39           for (int i = pos; i >= 0; i--) {
40                adjustHeap(array, i, array.length); // 在建堆過程中,及時調整堆中索引爲i的結點
41           }
42      }
43  
44      /**
45      * <p>
46      * 調整堆的方法
47      *
48      * @param s 待調整結點的索引
49      * @param m 待調整堆的結點的數量(亦即:排除葉子結點)
50      */
51      private void adjustHeap(int[] array, int s, int m) {
52           Integer tmp = array[s]; // 當前待調整的結點
53           int i = 2 * s + 1// 當前待調整結點的左孩子結點的索引(i+1爲當前調整結點的右孩子結點的索引)
54           while (i < m) {
55                if (i + 1 < m && array[i] < array[i + 1]) { // 如果右孩子大於左孩子(找到比當前待調整結點大的孩子結點)
56                     i = i + 1;
57                }
58                if (array[s] < array[i]) {
59                     array[s] = array[i]; // 孩子結點大於當前待調整結點,將孩子結點放到當前待調整結點的位置上
60                     s = i; // 重新設置待調整的下一個結點的索引
61                     i = 2 * s + 1;
62                else // 如果當前待調整結點大於它的左右孩子,則不需要調整,直接退出
63                     break;
64                }
65                array[s] = tmp; // 當前待調整的結點放到比其大的孩子結點位置上
66           }
67      }
68 }

堆排序算法,Python實現,代碼如下所示:

01 class Sorter:
02     '''
03     Abstract sorter class, which provides shared methods being used by
04     subclasses.
05     '''
06     __metaclass__ = ABCMeta
07     
08     @abstractmethod  
09     def sort(self, array):
10         pass
11  
12 class HeapSorter(Sorter):
13     '''
14     Heap sorter
15     '''    
16     def sort(self, array):
17         length = len(array)
18         self.__heapify(array)
19         = 0
20         while i<length:
21             array[0], array[length-1-i] = array[length-1-i], array[0]
22             self.__sift_down(array, 0, length-1-i)         
23             = + 1
24     
25     def __heapify(self, array):
26         length = len(array)
27         pos = (length-1// 2
28         = pos
29         while i>=0:
30             self.__sift_down(array, i, length)
31             = - 1
32     
33     def __sift_down(self, array, s, m):
34         tmp = array[s]
35         = 2 * + 1
36         while i<m:
37             if i+1<m and array[i]<array[i+1]:
38                 = + 1
39             if array[s]<array[i]:
40                 array[s] = array[i]
41                 = i
42                 = 2 * + 1
43             else:
44                 break
45             array[s] = tmp

排序過程

假設待排序數組爲array = {94,12,34,76,26,9,0,37,55,76,37,5,68,83,90,37,12,65,76,49},數組大小爲20。

第一步:初始建堆
首先執行的初始建堆(在建堆的過程中需要調整堆)。過程如下:

  • 求出當前堆中最後一個存在孩子結點的索引

這裏,把數組array看做是一棵完全二叉樹,這樣數組每個索引位置上的元素都對應到二叉樹中的結點,如圖所示:
heapsort
其中需要在這棵樹中找到最後一個有孩子最大的一個結點的索引:
pos = (array.length-1)/2 = (20-1)/2 = 9
也就是索引爲9的array[9] = 76,由後至前層次遍歷,從array[9]一直到array[0],對初始堆進行調整。

  • 對初始堆進行調整
  1. 調整結點array[9] = 76:

    先比較array[9] = 76的左右孩子:s = 9,i = 2*s+1 = 2*9 + 1 = 19,而i+1 = 19 + 1 = 20 > m = array.length-1 = 20 -1 = 19(array[9] = 76沒有右孩子),只需要將array[9] = 76與array[i] = array[19] = 49比較,因爲array[9] = 76>array[i] = array[19] = 49,則不需要交換array[9] = 76與array[i] = array[19] = 49,繼續對下一個結點(也就是array[8] = 55)進行調整;

  2. 調整結點array[8] = 55:

    先比較array[8] = 55的左右孩子:s = 8,i = 2*s+1 = 2*8 + 1 = 17,,而i+1 = 17 + 1 = 18 < m = array.length-1 = 20-1 = 19(array[8] = 55存在右孩子),左孩子array[i] = array[17] = 65小於右孩子array[i+1] = array[18] = 76,只需要將array[8] = 76與右孩子array[i+1] = array[18] = 76比較,因爲array[8] = 55<array[i+1] = array[18] = 76,則需要交換array[8] = 55與array[i+1] = array[18] = 76,交換後如圖所示:
    heapsort-1
    繼續對下一個結點(也就是array[8] = 55)進行調整;

  3. 調整結點array[7] = 37:

    顯然,不需要交換;

  4. 調整結點array[6] = 0:

    調整結果如圖所示:
    heapsort-2

  5. 調整結點array[5] = 9:

    調整結果如圖所示:
    heapsort-3

  6. 調整結點array[4] = 26:

    調整結果如圖所示:
    heapsort-4

  7. 調整結點array[3] = 76:

    顯然,不需要交換。

  8. 調整結點array[2] = 34:

    調整結果如圖所示:
    heapsort-5

  9. 調整結點array[1] = 12:

    調整結果如圖所示:
    heapsort-6

  10. 調整結點array[0] = 94:

    顯然,不需要交換。

至此,對初始堆的調整完成。

第二步:第一次交換
將堆頂元素與最後一個元素交換,即array[0] = 94與最後一個元素array[19] = 49交換,如圖所示:
heapsort-7
此時,數組爲:
array = {49,76,90,12,76,68,34,37,76,26,37,5,9,83,0,37,12,65,55,94}
數組中最大的元素被交換到了數組的末尾,也就是array[19] = 94是最終排好序的固定位置。

第三步:調整堆
過程同前面類似。
……
最後經過堆排序得到有序的數組。

算法分析

  • 時間複雜度

堆排序的時間,主要由建立初始堆和反覆重建堆這兩部分的時間開銷構成。
堆排序的最壞時間複雜度爲O(nlgn)。堆排序的平均性能較接近於最壞性能。由於建初始堆所需的比較次數較多,所以堆排序不適宜於記錄數較少的文件。

  • 空間複雜度

堆排序過程中,需要調整堆,交換待排序記錄需要一個臨時存儲單元,所以空間複雜度爲O(1)。

  • 排序穩定性

堆排序是就地排序,它是不穩定的排序方法。

轉載鏈接:http://shiyanjun.cn/archives/802.html

發佈了33 篇原創文章 · 獲贊 14 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章