1. 程式人生 > >資料結構與演算法---堆排序(Heap sort)

資料結構與演算法---堆排序(Heap sort)

堆排序基本介紹

1、堆排序是利用堆這種資料結構而設計的一種排序演算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均為O(nlogn),它也是不穩定排序。

2、堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆, 注意 : 沒有要求結點的左孩子的值和右孩子的值的大小關係。

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

4、大頂堆舉例說明

5、小頂堆舉例說明

 

小頂堆:arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2] // i 對應第幾個節點,i從0開始編號

6、一般升序採用大頂堆,降序採用小頂堆 

 堆排序的基本思想是:

  1. 將待排序序列構造成一個大頂堆
  2. 此時,整個序列的最大值就是堆頂的根節點。
  3. 將其與末尾元素進行交換,此時末尾就為最大值。
  4. 然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反覆執行,便能得到一個有序序列了。

可以看到在構建大頂堆的過程中,元素的個數逐漸減少,最後就得到一個有序序列了.

堆排序步驟圖解說明

要求:給你一個數組 {4,6,8,5,9} , 要求使用堆排序法,將陣列升序排序。

步驟一 構造初始堆。將給定無序序列構造成一個大頂堆(一般升序採用大頂堆,降序採用小頂堆)。

1) .假設給定無序序列結構如下

2).此時我們從最後一個非葉子結點開始(葉結點自然不用調整,第一個非葉子結點 arr.length/2-1=5/2-1=1,也就是下面的6結點),從左至右,從下至上進行調整。

3) .找到第二個非葉節點4,由於[4,9,8]中9元素最大,4和9交換。

4) 這時,交換導致了子根[4,5,6]結構混亂,繼續調整,[4,5,6]中6最大,交換4和6。

 

此時,我們就將一個無序序列構造成了一個大頂堆。

步驟二 將堆頂元素與末尾元素進行交換,使末尾元素最大。然後繼續調整堆,再將堆頂元素與末尾元素交換,得到第二大元素。如此反覆進行交換、重建、交換。

1) .將堆頂元素9和末尾元素4進行交換

 

2) .重新調整結構,使其繼續滿足堆定義

 

3) .再將堆頂元素8與末尾元素5進行交換,得到第二大元素8.

 

4) 後續過程,繼續進行調整,交換,如此反覆進行,最終使得整個序列有序

 

再簡單總結下堆排序的基本思路:

1).將無序序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;

2).將堆頂元素與末尾元素交換,將最大元素"沉"到陣列末端;

3).重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。

堆排序程式碼實現

要求:給你一個數組 {4,6,8,5,9} , 要求使用堆排序法,將陣列升序排序.

 1 public class HeapSort {
 2 
 3     public static void main(String[] args) {
 4         //要求將陣列進行升序排序
 5         //int arr[] = {4, 6, 8, 5, 9};
 6         // 建立要給80000個的隨機的陣列
 7         int[] arr = new int[8000000];
 8         for (int i = 0; i < 8000000; i++) {
 9             arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
10         }
11 
12         System.out.println("排序前");
13         Date data1 = new Date();
14         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
15         String date1Str = simpleDateFormat.format(data1);
16         System.out.println("排序前的時間是=" + date1Str);
17         
18         heapSort(arr);
19         
20         Date data2 = new Date();
21         String date2Str = simpleDateFormat.format(data2);
22         System.out.println("排序前的時間是=" + date2Str);
23         //System.out.println("排序後=" + Arrays.toString(arr));
24     }
25 
26     //編寫一個堆排序的方法
27     public static void heapSort(int arr[]) {
28         int temp = 0;
29         System.out.println("堆排序!!");
30         
31 //        //分步完成
32 //        adjustHeap(arr, 1, arr.length);
33 //        System.out.println("第一次" + Arrays.toString(arr)); // 4, 9, 8, 5, 6
34 //        
35 //        adjustHeap(arr, 0, arr.length);
36 //        System.out.println("第2次" + Arrays.toString(arr)); // 9,6,8,5,4
37         
38         //完成我們最終程式碼
39         //將無序序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆
40         for(int i = arr.length / 2 -1; i >=0; i--) {
41             adjustHeap(arr, i, arr.length);
42         }
43         
44         /*
45          * 2).將堆頂元素與末尾元素交換,將最大元素"沉"到陣列末端;
46               3).重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。
47          */
48         for(int j = arr.length-1;j >0; j--) {
49             //交換
50             temp = arr[j];
51             arr[j] = arr[0];
52             arr[0] = temp;
53             adjustHeap(arr, 0, j); 
54         }
55         
56         //System.out.println("陣列=" + Arrays.toString(arr)); 
57         
58     }
59     
60     //將一個數組(二叉樹), 調整成一個大頂堆
61     /**
62      * 功能: 完成 將 以 i 對應的非葉子結點的樹調整成大頂堆
63      * 舉例  int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6}
64      * 如果我們再次呼叫  adjustHeap 傳入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4}
65      * @param arr 待調整的陣列
66      * @param i 表示非葉子結點在陣列中索引
67      * @param lenght 表示對多少個元素繼續調整, length 是在逐漸的減少
68      */
69     public  static void adjustHeap(int arr[], int i, int lenght) {
70         
71         int temp = arr[i];//先取出當前元素的值,儲存在臨時變數
72         //開始調整
73         //說明
74         //1. k = i * 2 + 1 k 是 i結點的左子結點
75         for(int k = i * 2 + 1; k < lenght; k = k * 2 + 1) {
76             if(k+1 < lenght && arr[k] < arr[k+1]) { //說明左子結點的值小於右子結點的值
77                 k++; // k 指向右子結點
78             }
79             if(arr[k] > temp) { //如果子結點大於父結點
80                 arr[i] = arr[k]; //把較大的值賦給當前結點
81                 i = k; //!!! i 指向 k,繼續迴圈比較
82             } else {
83                 break;//!
84             }
85         }
86         //當for 迴圈結束後,我們已經將以i 為父結點的樹的最大值,放在了 最頂(區域性)
87         arr[i] = temp;//將temp值放到調整後的位置
88     }
89     
90 }
程式碼

堆排序的速度非常快,在我的機器上 8百萬資料 3 秒左右。O(nl