在一次面試當中,面試官問到了自己關於堆排序的一些細節,之前在整理各種高級排序的時候,有看過堆排序,然而在現場要給面試官講解排序的原理的時候,發現自己懵逼了,所以還是需要特地寫一篇隨筆來記錄堆排序的整個原理和過程,這里借鑒了百度知道里頭的堆排序的講解圖。
首先我們要了解什么是堆排序,其排序的時間復雜度為O(nlogn),且不會因為排序的數組的數據惡化,但需要提供額外的排序內存。這里的堆當中,常用的數據結構就是二叉樹,且是完全二叉樹。根據要排序的方式(升序,降序)可以將這個二叉樹的特點定義下來,就是根節點都比左右子節點大(大根堆)或者是根節點都比左右子節點小(小根堆)。而整個堆排的過程,包括了一個建樹,調整樹頂的過程。二話不說,先舉一個例子來說,假定我們要對序列{16,7,3,20,17,8}進行排序,先上我們的流程圖:
這是初步構建出來的樹,之后,我們從葉子節點開始從底向上遍歷,調整樹當中的數據,使其成為大根堆:
通過對圖中畫紅圈的節點進行數據交換,我們可以得到構建出來的大根堆:
之后我們將根節點和最后一個葉子節點互換,並且將互換后的這個葉子節點固定住(表示這個點已經被排序好)之后調整樹的時候不對該再進行調整。
之后的過程依次類推,最后就可以得到一個排序好的數組。
整個流程通過圖可以看的很清晰。那么接下來我們來看看代碼實現:
public class testHeapSort { public static void main(String[] args) { // TODO Auto-generated method stub int[] input={2,3,5,2,3,6,7,4,6}; HeapSort(input); for(int tmp:input) System.out.print(tmp+" "); } //堆排序 public static void HeapSort(int array[]) { buildTheTree(array); //從數組當中最后一個位置開始固定,一次固定到頭,即可得到升序數組 for(int i=array.length-1;i>=1;i--) { //根和當前最后的葉子節點交換 int tmp=array[0]; array[0]=array[i]; array[i]=tmp; //調整堆 maxify(array, i, 0); } } //第一次建樹 public static void buildTheTree(int array[]) { //因為是從葉子節點開始從底向上的調整,所以起點為數組長度的2分之一 int half=array.length/2; for(int i=half;i>=0;i--) { maxify(array, array.length, i); } } //調整樹的方法,大頂堆 public static void maxify(int array[],int size,int i) { //左子節點 int left=2*i+1; //右子節點 int right=2*i+2; //找出當前根節點、其左子節點、右子節點最大的節點作為根節點 int large=i; if(left<size && array[left]>array[i])large=left; if(right<size && array[right]>array[large])large=right; //如果再上邊查找過程當中,根節點就是最大的節點,那么不需要再去調整樹,因為這是一個從下往上調整的過程 //所以當前節點以下的樹已經滿足大根堆的要求,直接返回 if(large==i)return; //如果需要當前根節點和子節點互換,則互換過去的子節點再一次調整 int tmp=array[large]; array[large]=array[i]; array[i]=tmp; //互換后調整。 maxify(array, size, large); } }
通過代碼當中的注解,結合流程圖應該就可以很清晰的理解堆排序的原理和實現過程,這里就不再進行贅述了。