堆排序

一、預備知識-堆

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

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

  • 每個結點的值都大於或等於其左右孩子結點的值,稱爲大根堆;
  • 或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小根堆。如下圖:


通過圖可以比較直觀的看出大根堆和小根堆的特點,需要注意的是:這種結構是對父節點-左/右孩子節點之間做的約束,而對左-右孩子節點之間並沒有什麼要求。

另外,因爲堆的結構是完全二叉樹,所以可以用數組來存儲,並通過節點下標的規律快速進行索引。

下面是上圖大根堆與小根堆對應的數組:


二、堆排序基本思想圖解(大根堆爲例)

假設現在待排序數據存在array[count]中,其初始狀態如下:


對應的完全二叉樹爲:


堆排序的過程如下:

(1)初始化堆;

因爲堆是對父節點-左/右孩子節點之間的約束,所以從最後一個非葉子節點開始調整。


注意每次交換後,都要對下一層的子堆進行遞歸調整,因爲交換後有可能破壞已調整子堆的結構。


(2)進行調整後,堆頂元素(array[0])爲最大值,將最大值與堆尾部元素(array[count-1])交換,並將count值減去1,則此時得到新的無序數組array[count],此時的堆被破壞;


對應到數組元素爲:

(黃色標記爲已排序部分)


(3)調整堆:與建堆過程類似,堆頂元素被一個比較小的值代替,所以從堆頂元素開始調整,在堆頂、堆頂的左孩子、堆頂的右孩子中找出最大的與堆頂進行交換,被交換的元素再次與他下一層的左右孩子進行比較(遞歸)調整。



(4)重複(2)。

下面把整個過程畫完:


此時,大概的一個手工過程就懂了,注意的是:初始化堆是基礎,時從下向上調整。交換後調整堆時因爲有了建堆的基礎,每次調整的都是二叉樹的一支子樹,是從上往下。

三、代碼實現

package Alog;

public class HeapSort {

	public static void main(String[] args) {
		int[] array = new int[]{12, 5, 9 , 36, 8, 21, 7};
		System.out.println("初始狀態:");
		showArray(array);
		
		initHeap(array); //這個應該也是堆排序的一部分,此處只是爲了顯示下結果
		System.out.println("建堆之後:");
		showArray(array); 
		
		heapSort(array);
		System.out.println("排序之後:");
		showArray(array); 
	}
	
	
	public static void heapSort(int[] array){
		initHeap(array); //建堆
		
		int count = array.length;
		while(count > 1) {
			int tmp = array[count - 1];
			array[count - 1] = array[0];
			array[0] = tmp;
			
			count--; //未排序部分又少一個
			adjustHeap(array, count, 0);//調整過程自上而下,參數root=0
		}
	}	
	
	public static void initHeap(int[] array){
		//建堆,從最後一個非葉子節點開始,而最後一個非葉子節點的下標爲array.length/2-1
		for(int root = array.length/2 - 1; root >= 0; root--){
			adjustHeap(array, array.length, root);
		}
		
	}
	

	public static void adjustHeap(int[] array, int count, int root){
		int maxChildIndex;
		
		while(root <= count/2-1) { //待調整子堆的根節點必須是非葉子節點
			//需要在root、letfchild、rightchild中找到最大的值,
			//因爲最後一個非葉子節點有可能沒有右孩子,所以要做出判斷。
			if(root == count/2 - 1 && count % 2 == 0){
				//節點數量爲偶數時,最後一個非葉子節點只有左孩子
				maxChildIndex = 2 * root + 1;				
			}else{
				int leftChildIndex = 2 * root + 1;
				int rightChildIndex = 2 * root + 2;
				
				maxChildIndex = array[leftChildIndex] > array[rightChildIndex] ?
						leftChildIndex : rightChildIndex;
			}
			
			if(array[root] < array[maxChildIndex]){
				int tmp = array[root];
				array[root] = array[maxChildIndex];
				array[maxChildIndex] = tmp;
				
				//*****************這裏很重要,繼續調整因交換結構改變的子堆
				root = maxChildIndex; 
			}else{
				return;
			}
		}
	}
	
	public static void showArray(int[] array){
		for(int i = 0; i < array.length; i++){
			System.out.print(array[i] + ((i == array.length - 1) ? "\n" : " "));
		}
	}
}

輸出結果:

初始狀態:
12 5 9 36 8 21 7
建堆之後:
36 12 21 5 8 9 7
排序之後:
5 7 8 9 12 21 36
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章