堆排序算法详细分析
一、堆相關概念
1.堆
堆是完全二叉樹,即除最后一層外,其它層都是滿的,且最后一層從左到右依次都有元素。如下圖所示。
堆是用數組來實現的,圖中下標就為數組的下標,其對應數組[5, 1, 7, 2, 8, 6, 3, 9, 4],可以看出節點i的左子節點對應2i+1節點,右子節點為2i+2,父節點為(i-1)/2。
2.大頂堆與小頂堆
大頂堆:若堆中每個非葉子節點的值均大于等于其子節點的值,則稱這個堆為大頂堆。
小頂堆:若堆中每個非葉子節點的值均小于等于其子節點的值,則稱這個堆為小頂堆。
大頂堆與小頂堆對左右子節點值的大小沒有要求。
二、堆排序的基本思想
1.根據大頂堆和小頂堆的概念,可知它們的根節點對應整個堆最大值與最小值。
2.將長度為n的待排序數組調節成大頂堆(或小頂堆),將數組頭元素與末尾元素互換,最大值(最小值)就放在了最后。
3.繼續將數組的前n-1位調成大頂堆(或小頂堆),將數組頭元素與第n-1個元素互換,將次大值(次小值)放在對應位置,按此方法,一直循環調節與互換,直到待調節元素為1個。
4.最后將得到升序(降序)的數列。
三、代碼實現分析
1.將堆調節成大頂堆(以大頂堆為例)
(1) 方法概述
一個堆調節成大頂堆的過程是比較復雜的。
函數:這里定義一個函數,該函數可以將以堆中某個非葉子節點為根節點的子樹調節成大頂堆(只是部分為大頂堆)。
思路:從最后一個葉子節點開始調節,接著調節這個節點的前一個節點(即數組中的前一個元素,且最后一個非葉子節點的前面都是非葉子節點),直到調節到整個堆根節點(即數組頭元素)。
優點:這樣可以保證每次調節節點時,該節點的左右子樹都是大頂堆。
要點:調節該非葉子節點i,就是將其與左右子節點中的最大值放在該節點,此處若該節點為最大值,則以該節點為根節點的樹就是大頂堆,不用進行調整。若該節點不為最大值,則會與左右子節點中最大值發生互換,這可能會影響左右子樹是否還為大頂堆,此時需要進一步調整左右子樹,調整方法與前面相同。直到調整到葉子節點,此時以非葉子節點i為根節點的樹,就成為了大頂堆。
堆中最后一個非葉子節點的索引:arr.length/2 - 1。
(2) 圖解
以arr = [5, 1, 7, 2, 8, 6, 3, 9, 4]為例:
arr.length/2 - 1 = 3,第一個飛葉子節點索引為3。交換后:
調整索引為2的:不變
調整索引為1的:
這里左子樹因互換而不滿足大頂堆,需調整:
調節索引為0的:
調節其左子樹:
索引為4的是葉子節點,結束調整。
調節完索引為0的節點,調節結束,大頂堆形成。
(3) 代碼
調節節點函數:
/*** 將以i為根節點對應的樹調節成大頂堆* @param arr 待調整的數組* @param i 非葉子節點在數組中的位置* @param length 表示待調整樹的長度(越來越小)** 第i節點的左子節點為【2*i+1】,右子節點為【2*i+2】*/public static void adjustHeap(int[] arr, int i, int length) {int temp = arr[i];//先取出當前元素的值,保存在臨時變量//開始調整for (int index = i * 2 + 1; index < length; index = index * 2 + 1) {if (index + 1 < length && arr[index] < arr[index + 1]) {//找左子節點和右子節點的最大值index++;}if (arr[index] > temp) {//子節點大于當前節點的值arr[i] = arr[index];arr[index] = temp;i = index;//i指向index,繼續循環比較,調整成大頂堆} else {break;}}}public static void main(String[] args){int[] arr = {5, 1, 7, 2, 8, 6, 3, 9, 4};//將整個堆調成大頂堆for (int i = arr.length / 2 - 1; i >= 0; i--) {adjustHeap(arr, i, arr.length);}}2.堆排序算法
1.直接嵌套循環(不推薦)
public static void heapSort(int[] arr){System.out.println("堆排序");for(int length = arr.length;length > 1;length--){//調節成大頂堆for(int i = length/2-1;i>=0;i--){adjustHeap(arr, i ,length);}int temp = arr[0];arr[0] = arr[length - 1];arr[length - 1] = temp;}}調節完成后,進行交換,再調節的短一個的數組(length–),此種方法容易理解,但耗時很大。
2.從0開始調節
我們注意到從第二次開始調節開始(下圖為第二次調節開始時),根節點的左右子樹都滿足大頂堆的概念,無需進行從下至上的排序,直接從根節點開始調節即可。
代碼:
四、完整代碼
public class HeapSort {public static void main(String[] args) {int[] arr = {5, 1, 7, 2, 8, 6, 3, 9, 4};heapSort(arr);System.out.println(Arrays.toString(arr));}public static void heapSort(int[] arr){int temp;//將樹調成大頂堆for (int i = arr.length / 2 - 1; i >= 0; i--) {adjustHeap(arr, i, arr.length);}for (int k = arr.length - 1; k > 0; k--) {temp = arr[0];arr[0] = arr[k];arr[k] = temp;adjustHeap(arr, 0, k);}}public static void adjustHeap(int[] arr, int i, int length) {int temp = arr[i];//先取出當前元素的值,保存在臨時變量//開始調整for (int index = i * 2 + 1; index < length; index = index * 2 + 1) {if (index + 1 < length && arr[index] < arr[index + 1]) {//找左子節點和右子節點的最大值index++;}if (arr[index] > temp) {//子節點大于當前節點的值arr[i] = arr[index];arr[index] = temp;i = index;//i指向k,繼續循環比較,調整成大頂堆} else {break;}}} }總結
- 上一篇: 远程服务器安装docker和docker
- 下一篇: 霍夫曼树(最优二叉树)的实现