堆及其相關應用
什麼是堆?
提到堆就不得不說到二叉樹這個結構,堆就是一顆完全二叉樹,什麼叫完全二叉樹,用一句話來概括就是:設二叉樹的深度為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;
}
}
堆排序
- 初始值i=1,n=arr.length
- 對陣列0~n-i範圍內的值構建大根堆
- 將陣列第n-i值和第0值進行交換,++i,然後對0到arr.lenght~i的範圍進行heapify操作
- 重複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);
}
}
總結
左神說了一句話:“堆這個結構很重要,堆這個結構很重要,堆這個結構很重要…”