堆排序原理以及實現

堆排序原理以及實現

堆性質的簡介

堆是以二叉樹的形式存儲的一種數據結構,常見的堆的使用方式主要包括:堆排序,優先隊列的構造。堆主要分爲最小堆與最大堆,最小堆的主要性質是根結點小於等於兩個子結點的值,同理可得,最大堆的主要性質是根節點大於等於兩個子節點的值。由於堆是一棵二叉樹,所以根據堆的性質,堆可以看作是一棵完全二叉樹。

堆的創建

從定理中可以看出,要想保持堆的基本性質關鍵點在於根節點與兩個子結點之間的關係,我們從這個性質出發考慮如何來維護堆的基本性質。保證新插入的數據不會影響最大堆的性質。
	public void maxHeap(int[] A,int i,int length){
		
		int left = i*2 + 1; // 根結點的左孩子
		int right = i*2 + 2; // 根結點的右孩子
		int largest = i;
		// 如果存在左孩子,且左孩子大於插入結點的值
		if(left < length && A[i] < A[left]){
			largest = left;
		}
		// 如果存在右孩子,且右孩子大於插入結點的值
		if(right < length && A[largest] < A[right]){
			largest = right;
		}
		// 如果最大值不是當前插入的結點的值,交換兩個結點之間的值
		if(largest != i){
			exchange(A, i, largest);
			maxHeap(A,largest,length);
		}
	}
	private void exchange(int[] A, int i, int largest) {
		A[i] = A[i] ^ A[largest];
		A[largest] = A[i] ^ A[largest];
		A[i] = A[i] ^ A[largest];
	}


從程序中我們可以看出,對於新插入的結點在數組的第一個位置,首先比較其對應的左孩子與右孩子的值,將三個結點中值最大的設置爲根結點,將要插入的結點與其交換位置。利用下標 largest,i 判斷是否發生過交換,如果發生過交換,交換對應兩個結點的值,利用遞歸的形式重複上面的步驟,未發生交換則退出。下圖爲交換的過程。

從上圖我們可以很清楚的看到程序的比較過程。對於給定的數組A={4,1,3,2,16,9,10,14,8,7},通過上面的比較我們可以看出,從第 A.length/2+1個元素到A.length個元素都爲葉子結點。所以我們在建堆時直接可以從A.length/2 到第0個元素一次調用maxHeap()方法。具體程序如下所示。
	public void buildMaxHeap(int[] A){
		
		for(int i = A.length >> 1; i >= 0 ; i--){
			maxHeap(A,i,A.length);
		}
	}
通過上面的幾個程序,我們就可以建立一個堆了。

利用創建好的堆進行堆排序

主要的思路是,交換堆中第一個元素與最後一個元素(按數組中的下標作出選擇),然後重新調用maxHeap()方法維護堆的結構。具體代碼如下:
	// 堆排序
	public void heapSort(){
		
		// 考慮到堆是完全二叉樹,所以可以考慮使用連續的數組存儲二叉樹的結點。
		int a[] = {4,1,3,2,16,9,10,14,8,7};
		// 建立最大堆
		buildMaxHeap(a);
		System.out.println(Arrays.toString(a));
		// 堆排序
		int length = a.length;
		for(int i = 0; i+1 < length;){
			exchange(a,i,--length);
			maxHeap(a,i,length);
		}
		System.out.println(Arrays.toString(a));
	}


利用堆構造優先隊列

我們先來看一下優先隊列的定義,在算法導論中關於優先隊列的定義是這樣的:優先隊列(priority queue) 是一種用來維護由一組元素構成的集合S的數據結構,其中的每一個元素都有相關的值,稱爲關鍵字(key)。一個優先隊列支持一下操作(最大優先隊列):
1、insert(S,x):把元素x插入集合S中。這一操作等價於S=SU{x}。
2、maximum(S) :返回S中具有最大關鍵字的元素。
3、extractMax(S) : 去掉並返回S中具有最大關鍵字的元素。
4、increaseKey(S,x,k) :將元素x的關鍵字值增加到k,這裏假設k的值不小於x的原關鍵字的值。
優先隊列的應用有很多,其中一個就是共享計算機系統的作業調度。

下面讓我們一起實現這幾個方法。
insert(S,x):把元素x插入集合S中。這一操作等價於S=SU{x},因爲涉及到擴充的問題,又因爲數組不具有可擴充的特性,這裏我假設每次申請多餘的數組空間。
	// 把元素x插入到集合A中
	public void insert(int[] A,int x){

		int i;
		// 遍歷一遍數組,查找已存在數組的末尾下標
		for(i = 0; i < A.length; i++){
			if(A[i] == -1){
				break;
			}
		}
		A[i] = Integer.MIN_VALUE; 
		increaseKey(A,i,x);
	}


maximum:返回S中具有最大關鍵字的元素,這個是容易實現的方法。
	public int maximum(int[] A){
		return A[0];
	}
extactMax(S) : 去掉並返回S中具有最大關鍵字的元素,我們結合之前的maxHeap()方法,很容易想到如何實現這個方法。
	// 返回最大優先隊列中的最大元素並刪除
	public int extractMax(int[] A){
		
		int max;
		int length = A.length-1;
		// 判斷A數組中元素的個數
		if(A.length == 0){
			return Integer.MIN_VALUE;
		}
		max = A[0];
		// 交換兩個數的值
		exchange(A,0,length);
		// 重新維護堆的結構
		maxHeap(A,0,length);
		
		return max;
	}
increaseKey(S,x,k) : 將元素x的關鍵字值增加到k,這裏假設k的值不小於x的原關鍵字的值。
	public void increaseKey(int[] A,int i,int key){
		
		if(key < A[i-1]){
			System.out.println("new key is smaller than key");
		}
		A[i-1] = key; 
		while(i > 0 && A[i-1] > A[i>>1]){
			exchange(A,i-1,i/2);
			i = i>>1;
		}
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章