堆與優先隊列
我們經常提到的數據結構大頂堆指的是二叉堆,它是一顆堆有序的完全二叉樹(非葉子結點層都是滿的,最後一層從右向左只能空缺右結點)。其中根節點是所有結點中最大,並且每個父節點都大於其兩個子節點(堆有序)。完全二叉樹底層是用數組實現的,所以它只是邏輯上的一個概念。下圖是一個大頂堆的例子:
那麼給定一個數組怎麼建立大頂堆呢?我們用下面程式碼來說明,建立堆的時間複雜度為O(n)。
1 //建立大頂堆 2 public void creatHeap(int[] arr) { 3 for (int i=0; i<arr.length; i++) { 4 heapInsert(arr, i); 5 } 6 } 7 /** 8 * 某一個元素進堆,元素上浮的過程 9 * @param arr 10 * @param index 11 */ 12 public void heapInsert(int[] arr, int index) { 13 //當前元素大於父節點時,交換 14 while (arr[index] > arr[index / 2 - 1]) { 15 swap(arr, index, index / 2 - 1); 16 // 來到父節點位置繼續比較 17 index = index / 2 - 1; 18 } 19 } 20 21 public void swap(int[] arr, int m, int n) { 22 int temp = arr[m]; 23 arr[m] = arr[n]; 24 arr[n] = temp; 25 }
如果說堆中某個元素變小了,我們可以使用heapify來調整元素的位置,使得堆仍然是大頂堆,heapify實際上是當前元素下沉的過程,具體程式碼如下:
1 /** 2 * 元素下沉過程 3 * @param arr 4 * @param index 當前變小的元素 5 * @param heapSize 堆的大小,小於等於數組的大小 6 */ 7 public void heapify(int[] arr, int index, int heapSize) { 8 //左右孩子結點的索引,數組的索引從0開始 9 int left = 2 * index + 1; 10 int right = left + 1; 11 12 while (left < heapSize) { 13 //從左右孩子中選出大的 14 int largest = right < heapSize && arr[right] > arr[left] ? right : left; 15 //和index比較 16 largest = arr[largest] > arr[index] ? largest : index; 17 //當前變小的元素任然比左右孩子結點大,結束循環 18 if (largest == index) break; 19 //交換 20 swap(arr, largest, index); 21 //更新index位置,來到largest的位置 22 index = largest; 23 //重置left位置,繼續向下比較 24 left = 2 * index + 1; 25 } 26 }
優先隊列本質還是一個隊列,只不過隊列里的元素有優先順序,優先順序高的元素先出隊列。在Java中優先隊列有兩種實現,一種是大頂堆,另外一種是小頂堆。以大頂堆為例:當隊首元素彈出時,優先隊列會自動調整剩下的元素,使其仍是一個大頂堆,時間複雜度為O(nlogn)。具體實現過程就利用了我們上面的heapify方法,同樣給出程式碼說明:
1 public void heapSort(int[] arr) { 2 int len = arr.length; 3 //交換首尾元素,使得彈出的元素在堆中失效,但仍在數組中 4 swap(arr, 0, --len); 5 //不斷地進行heapify操作 6 while (len > 0) { 7 heapify(arr, 0, len); 8 swap(arr, 0, --len); 9 } 10 }
優先隊列可以應用於系統的任務調度,優先順序高的任務先執行,低的在隊列中等候;圖的搜索演算法中也會用到優先隊列,感興趣的童鞋可以閱讀《演算法》第四版具體學習。
參考資料:左程雲演算法初級班
《演算法》第四版