資料結構與演算法---堆排序(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、一般升序採用大頂堆,降序採用小頂堆
堆排序的基本思想是:
- 將待排序序列構造成一個大頂堆
- 此時,整個序列的最大值就是堆頂的根節點。
- 將其與末尾元素進行交換,此時末尾就為最大值。
- 然後將剩餘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