1. 程式人生 > >排序算法(四)堆排序

排序算法(四)堆排序

htc 有序 結構 info 最後一個元素 www 當前 最好 下標

1,什麽是堆

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

每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆 (例如圖 9-2 左圖所示) ;

或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆(例如圖 9-2 右圖所示)。

技術分享圖片

2,為什麽出現堆排序

前面介紹的(簡單)選擇排序,需要每次從未排序序列中選出最小(最大)的數據。我們當時的實現是每次都將未排序序列從頭開始比較,其實從第二次開始就出現了大量的重復比較,即沒有充分利用前一次的比較結果。

如果可以做到每次在選擇到最小記錄的同時,並根據比較結果對其他數據做出相應的調整 , 那樣排序的總體效率就會非常高了 。所以用堆結構進行實現。

3,算法描述

堆排序 (Heap Sort) 就是利用堆(假設利用大頂堆)進行排序的方法。
它的基本思想是, 將待排序的序列構造成一個大頂堆。此時,整個序列的最大值就是堆頂的根結點。將它移走(其實就是將其與堆數組的末尾元素交換,此時末尾元素就是最大值) .然後將剩余的 n - 1 個序列重新構造成一個堆,這樣就得到 n 個元素中的次小值。如此反復執行, 便能得到一個有序序列了 。

4,實現步驟

  1. 由輸入的無序數組構造一個最大堆,作為初始的無序區
  2. 把堆頂元素(最大值)和堆尾元素互換
  3. 把堆(無序區)的尺寸縮小1,並調用heapify(A, 0)從新的堆頂元素開始進行堆調整
  4. 重復步驟2,直到堆的尺寸為1

  實現

 1 public static void heapSort(int[] array) {  
 2         buildHeap(array);  
 3         int len = array.length;  
 4         int i = 0;    
 5         for (i = len - 1; i >= 1; i--) {  
 6             swap(array, 0, i);  // 將堆頂元素與堆的最後一個元素互換;並從堆中去掉最後一個元素(即i每次減1)
 7             heapify(array, 0, i); //
從新的堆頂元素開始向下進行堆調整,時間復雜度O(logn) 8 } 9 } 10 11 // 構建堆 12 /* 13 * 這是第一個for循環 14 * 假設需要排序的序列有9個元素(數組中位置0-8),我們首先要找到每個非終端結點(非葉結點)當作根結點(圖9-7-5註意該圖位置標註是1-9對應我們的0-8),即第1-4個(位置0-3) 15 * 然後循環就是依次將那4個根節點及其子樹調整為大頂堆 16 */ 17 public static void buildHeap(int[] array) { 18 for (int i = array.length / 2 - 1; i >= 0; i--) { 19 heapify(array, i, array.length); 20 } 21 } 22 23 // 調整堆 24 /** 25 * @param data 堆(無序區) 26 * @param parentNode 根節點(子樹的) 27 * @param heapSize 堆的結點個數 28 */ 29 public static void heapify(int[] data, int parentNode, int heapSize) { 30 int leftChild = 2 * parentNode + 1;// 左孩子下標 (看圖9-7-5註意該圖位置標註是1-9對應我們的0-8) 31 int rightChild = 2 * parentNode + 2;// 右孩子下標(如果存在的話) 32 int largest = parentNode; // 選出當前結點與其左右孩子三者之中的最大值 33 // 尋找3個節點中最大值節點的下標 34 if (leftChild < heapSize && data[leftChild] > data[parentNode]) { 35 largest = leftChild; 36 } 37 if (rightChild < heapSize && data[rightChild] > data[largest]) { 38 largest = rightChild; 39 } 40 // 如果最大值不是父節點,那麽交換,並繼續調整堆 41 if (largest != parentNode) { 42 swap(data, largest, parentNode); // 把當前根結點和它的最大(直接)子節點進行交換 43 heapify(data, largest, heapSize); // 遞歸調用,繼續從當前結點向下進行堆調整 44 } 45 } 46 47 // 交換函數 48 public static void swap(int[] array, int i, int j) { 49 int temp = 0; 50 temp = array[i]; 51 array[i] = array[j]; 52 array[j] = temp; 53 }

圖示:

1)輸入數組

技術分享圖片

2)構建堆,得到堆(無序區)

技術分享圖片

我們程序中數組是按從0下標開始的(即0-8),該圖從1下標開始的。

3)開始排序,

技術分享圖片

4)最終完成直到堆的大小由n個元素降到2個

技術分享圖片

5,算法分析

時間復雜度:

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

在正式排序時,第 i 次取堆頂記錄重建堆需要用 O(logi)的時間(完全二叉樹的某個結點到根結點的距離為 [Iog2i ] + 1 ,並且需要取 n-1 次堆頂記錄,因此,重建堆的時間復雜度為 O(nlogn) 。

所以總體來說,堆排序的時間復雜度為 O(nlogn) 。由於堆排序對原始記錄的排序狀態並不敏感,因此它無論是最好、最壞和平均時間復雜度均為 o(nlogn)。這在性能上顯然要遠遠好過於冒泡、簡單選擇、 直接插入的 O(n2)的時間復雜度了。

空間復雜度:

只需要一個用了交換的暫存空間,所以空間復雜度為O(1)

穩定性:不穩定

參考:

大話數據結構》

http://www.cnblogs.com/eniac12/p/5329396.html

排序算法(四)堆排序