堆樹介紹:
之前在二叉樹的時候說到過一種特殊的二叉樹---完全二叉樹(除了最后一層,其他層的每個結點都是滿的,且最后一層結點全部靠左排列,這樣就可以很方便的用數組來表示,下標從0開始如果父結點索引是i那么它兩個子結點的索引就是2i+1和2i+2,具體的圖解見二叉樹)。而堆樹又是一種特殊的完全二叉樹。它的每一個結點值都大於等於或者小於等於其左右結點的值。這里和二叉搜索樹不一樣,搜索樹是左節點小於根,右結點大於根。為什么是大於等於或者小於等於呢,如果大於等於,那么根就是最大的數,這樣的就是大頂堆;如果是小於等於,那么根就是最小的數,這樣就是小頂堆。
堆樹的操作:
插入:
堆樹插入之后要進行一個堆化的操作,也就是讓這棵樹滿足堆樹的性質。
一種是從下往上另一種是從上往下的堆化。
從下往上:
因為完全二叉樹用數據構造之后,那么新插入的數據都在最后,但是插入之后,可能就不滿足堆樹的要求了,所以需要進行變動。以上圖的大頂堆為例,我們在最后插入9之后是不滿足堆樹的性質的。所以我們需要與其父結點進行交換,直到不能再交換位置。不用擔心數據量大了交換的次數問題,完全二叉樹近似一個滿二叉樹,最多交換logn-1次,想想2的32的次方是多少,這么大的數據最多才31次。
刪除:
假設我們要刪除掉根結點10,並且刪除之后,還要滿足堆樹的性質。
如何才能刪除之后還能保證性質呢。其實就是將要刪除的元素和最后一個元素交換之后,刪除最后一個元素之后,再從上往下進行堆化的操作。
修改數據之后,同樣要進行堆化操作,根據修改之后的數據和他父結點和子結點比較來決定是向上還是向下進行堆化操作。
堆排序:
排序算法一種,就是先將數組構造成堆樹,再進行排序。那么怎么將一個數組構造成堆樹。其實一個數組我們都可以看成是一棵完全二叉樹,但是需要將這棵樹進行改造,才能得到堆樹。如何改造?
比如這個數組:[8 4 20 7 3 1 25 14 17]
樹結構:
我們從最后一個非葉子結點逆向依次進行向下堆化操作,如上圖也就是先從7(最后一個非葉子結點)開始向下堆化,7會和17進行對換,然后逆向一次執行,接下來就是20向下執行堆化,會和25對換;然后是4,這個時候會將剛才換上來17換上去,然后4繼續和下面比較,和14進行交換.......依次這樣操作之后就可以得到一棵堆樹了。圖解如下:
那么得到堆樹之后,看起來也是無序的啊,怎么就能實現排序呢?
可以看到堆樹的根要么是最大的數要么是最小的數,那我們依次將根取出來,之后再進行堆化操作,又會得到剩余數中最大或者最小的。所以,我們將根與最后一個數交換之后,再進行堆化操作(這里的堆化操作就要除開最后一個點了),然后再新的根和倒數第二個交換再堆化,依次這樣操作,后面數慢慢的都是有序的了。
圖解如下:
代碼實現數組轉堆樹和堆排序(不穩定):
/** * 堆排序 * @param arr */ private static void heapSort(int[] arr) { int len = arr.length; /** * 數組構造堆樹,從倒數第一個非葉子結點開始逆向一次進行堆化操作。最后一個葉子結點的父結點就是最后一個非葉子結點。 * 索引從0開始。兩個子結點索引是2i+1和2i+2,所以最后一個非葉子結點的索引就是 len/2 - 1 */ for (int i = len / 2 - 1; i >=0 ; i--) { //時間復雜度nlogn createMaxHeap(arr, i, len); } for (int i = len - 1; i > 0 ; i--) { //時間復雜度nlogn int maxData = arr[0]; //第一個數最大 arr[0] = arr[i]; arr[i] = maxData; /** * 交換第一個最后一個位置,然后重新構造大頂堆。每循環一次就構造好了一個數的位置, * 最后到i為止都是排好序的,堆化的時候不需要再操作了 */ createMaxHeap(arr, 0, i); } } /** * 大頂堆構造及堆化過程 * @param arr * @param start * @param end end之后是已經排好序的,所以需要end下標來判斷截止 */ private static void createMaxHeap(int[] arr, int start, int end) { int parentIndex = start; int leftChildIndex = 2 * parentIndex + 1; while (leftChildIndex < end) { int tempIndex = leftChildIndex; //比較左右結點誰大,記錄誰的下標 if (leftChildIndex + 1 < end && arr[leftChildIndex] < arr[leftChildIndex + 1]) { tempIndex = leftChildIndex + 1; } //父結點比孩子大,不交換 if (arr[parentIndex] > arr[tempIndex]) { return; } else { //交換數據,刷新父結點繼續執行堆化操作 int tempData = arr[parentIndex]; arr[parentIndex] = arr[tempIndex]; arr[tempIndex] = tempData; parentIndex = tempIndex; leftChildIndex = 2 * parentIndex + 1; } } }
堆樹和堆排序在JDK中的應用,可以參見優先隊列:java.util.PriorityQueue