1. 程式人生 > >堆及其相關應用

堆及其相關應用

什麼是堆?

提到堆就不得不說到二叉樹這個結構,堆就是一顆完全二叉樹,什麼叫完全二叉樹,用一句話來概括就是:設二叉樹的深度為h,除第h層外,其它各層的結點數都達到最大個數,第h層所有的結點都連續集中在最左邊,這就是完全二叉樹,舉幾個例子:

堆分兩種,一種叫大根堆,一種叫小根堆。大根堆就是在堆結構中,任意一棵子樹的根節點一定是最大值,舉個例子:

看上圖最左邊的那顆樹,他的子樹有64323、643、423,而每一棵子樹的根節點都是其子樹的最大值,小根堆概念差不多,就是在堆結構中,任意一顆子樹的根節點一定是最小值,就不舉例了

構建大根堆

瞭解了堆的相應結構,接下來我們就要構建一個堆,我們用陣列來實現堆結構,假如陣列中的元素是012345,那麼對應的樹就是


直接給出關係:在陣列不越界的前提下,任意一個下標i對應的根節點是(i-1)/2,左孩子是2×i+1,右孩子是2×i+2,當然,也包含特殊情況,比方說0本身就是根節點了,那麼0的根節點就是(0-1)/2,在計算機中等於0,所以整棵樹的根節點的根節點就是它本身

下面我們就要講,給你一個數組,如何構建一個大根堆,假設這個陣列的值是213604,我們定義一個下標i,表示0到i是一個大根堆

首先,i的初始值為0,那麼陣列中的2這個值就是一個大根堆,然後i++

到了1,因為1是小於1的根節點2的,所以1可以直接放到2的後面作為左孩子,此時大根堆為21,然後i++

到了3,因為3是大於根節點2的,所以將2和3交換位置,此時大根堆為312,然後i++

到了6,本來6應該放在1的後面作為1的左孩子,但是因為6比1大,所以6要和1進行交換,此時狀態為3621,還沒完,6還要繼續和其根節點也就是3比較,6比3大,所以6還要和3交換,此時大根堆為6321,然後i++

到了0,0的根節點在陣列中的下標是(4-1)/2=1,0的根節點是2,0比2小,所以直接放上取就行,此時大根堆為63210,然後i++

到了4,4的根節點在陣列中的下標是(5-1)/2=2,4的根節點是2,4比2大,所以4要和2交換,此時狀態為634102,然後4到了陣列下標為2的位置,此時4的根節點是6,4比6小,所以不用交換,因為i已經等於了arr.length-1,所以整個陣列已經變成大根堆了,如圖


生成大根堆的時間複雜度是O(logN),因為耗時的部分在於不斷往上判斷根節點與當前節點的關係,所以時間複雜度與高度成正比,而一個N個節點的完全二叉樹高度是logN的,所以時間複雜度是O(logN),程式碼如下:

程式碼
public class Heap {
	private static void heapSort(int[] arr) {
		if(arr == null || arr.length < 2) 
			return;
		for(int i = 0;i < arr.length;i++) {
			heapInsert(arr,i);//構建0-i的大根堆
		}
	}
	private static void heapInsert(int[] arr,int idx) {
		while(arr[idx] > arr[(idx - 1) / 2]) {
			swap(arr,idx,(idx - 1) / 2);
			idx = (idx - 1) / 2;
		}
	}
	private static void swap(int[] arr,int i,int j) {
		int t = arr[i];
		arr[i] = arr[j];
		arr[j] = t;
	}
}

改變大根堆

現在考慮這樣一個問題,假設我已經構建好了一個大根堆,現在我將陣列中的某一個值改變了,那麼他就有可能不是一個大根堆了,我需要將改變後的陣列重新變為一個大根堆,怎麼做

假設原始大根堆是654352,現在我將6變為1,於是陣列就變為154352,我現在要將其重新變為大根堆

先看該節點的左右兩個孩子,找出兩個孩子中較大的那個,然後和該節點進行比較,如果比當前節點大,就交換,對應樣例就變成514352。然後重複該操作,就會變成554312,沒有孩子了停止,大根堆也重新構建好了。程式碼如下:

程式碼
private static void heapify(int[] arr,int idx,int heapsize) {
	int left = 2 * idx + 1;
	while(left < heapsize) {
		int right = left + 1;
		int largest = right < heapsize && arr[right] > arr[left] ? right : left;
		largest = arr[largest] > arr[idx] ? largest : idx;
		if(largest == idx) 
			break;
		swap(arr,largest,idx);
		idx = largest;
		left = idx * 2 + 1;
	}
}

堆排序

  1. 初始值i=1,n=arr.length
  2. 對陣列0~n-i範圍內的值構建大根堆
  3. 將陣列第n-i值和第0值進行交換,++i,然後對0到arr.lenght~i的範圍進行heapify操作
  4. 重複2,3操作,直到i=n
程式碼
private static void heapSort(int[] arr) {
	if(arr == null || arr.length < 2) 
		return;
	for(int i = 0;i < arr.length;i++) 
		heapInsert(arr,i);//構建0-i的大根堆
	int heapsize = arr.length;
	swap(arr,0,--heapsize);
	while(heapsize > 0) {
		heapify(arr,0,heapsize);
		swap(arr,0,--heapsize);
	}
}

總結

左神說了一句話:“堆這個結構很重要,堆這個結構很重要,堆這個結構很重要…”