優先順序佇列PriorityQueue
優先順序佇列PriorityQueue,這名字聽起來吊炸天,其實就是一個堆。
PriorityQueue 是一個基於優先順序堆的無界佇列, 它的元素是按照自然順序(natural order)排序的。 在建立的時候, 我們可以給它提供一個負責給元素排序的比較器。 PriorityQueue 不允許 null 值, 因為他們沒有自然順序, 或者說他們沒有任何的相關聯的比較器。 最後, PriorityQueue 不是執行緒安全的, 入隊和出隊的時間複雜度是 O(log(n))。
堆樹的定義如下:
(1) 堆樹是一顆完全二叉樹;
(2) 堆樹中某個節點的值總是不大於或不小於其孩子節點的值;
(3) 堆樹中每個節點的子樹都是堆樹。
Java中PriorityQueue通過二叉小頂堆實現,可以用一棵完全二叉樹表示。
leftNo = parentNo2+1
rightNo = parentNo2+2
parentNo = (nodeNo-1)/2
PriorityQueue的peek()和element操作是常數時間,add(), offer(), 無引數的remove()以及poll()方法的時間複雜度都是log(N)。
有關堆排序的知識,那麼給一個數組,如何進行堆排序呢,同時這也是面試中會考察的:
package com.fwc; public class heapsort { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //定義整型陣列 int[] arr = {1,5,6,8,7,2,3,4,9}; //呼叫堆排序陣列 HeapSort(arr); //輸出排序後的陣列 for(int i=0;i<arr.length;i++) { System.out.print(arr[i]+" "); } } //堆排序函式 public static void HeapSort(int[] arr) { int n = arr.length-1; for(int i=(n-1)/2;i>=0;i--) { //構造大頂堆,從下往上構造 //i為最後一個根節點,n為陣列最後一個元素的下標 HeapAdjust(arr,i,n); } for(int i=n;i>0;i--) { //把最大的數,也就是頂放到最後 //i每次減一,因為要放的位置每次都不是固定的 swap(arr,i); //再構造大頂堆 HeapAdjust(arr,0,i-1); } } //構造大頂堆函式,parent為父節點,length為陣列最後一個元素的下標 public static void HeapAdjust(int[] arr,int parent,int length) { //定義臨時變數儲存父節點中的資料,防止被覆蓋 int temp = arr[parent]; //2*parent+1是其左孩子節點 for(int i=parent*2+1;i<=length;i=i*2+1) { //如果左孩子大於右孩子,就讓i指向右孩子 if(i<length && arr[i]<arr[i+1]) { i++; } //如果父節點大於或者等於較大的孩子,那就退出迴圈 if(temp>=arr[i]) { break; } //如果父節點小於孩子節點,那就把孩子節點放到父節點上 arr[parent] = arr[i]; //把孩子節點的下標賦值給parent //讓其繼續迴圈以保證大根堆構造正確 parent = i; } //將剛剛的父節點中的資料賦值給新位置 arr[parent] = temp; } //定義swap函式 //功能:將跟元素與最後位置的元素交換 //注意這裡的最後是相對最後,是在變化的 public static void swap(int[] arr,int i) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; } }
add(E e)和offer(E e)的語義相同,都是向優先佇列中插入元素,只是Queue介面規定二者對插入失敗時的處理不同,前者在插入失敗時丟擲異常,後則則會返回false。對於PriorityQueue這兩個方法其實沒什麼差別
element()和peek()的語義完全相同,都是獲取但不刪除隊首元素,也就是佇列中權值最小的那個元素,二者唯一的區別是當方法失敗時前者丟擲異常,後者返回null。根據小頂堆的性質,堆頂那個元素就是全域性最小的那個;由於堆用陣列表示,根據下標關係,0下標處的那個元素既是堆頂元素。所以直接返回陣列0下標處的那個元素即可
remove()和poll()方法的語義也完全相同,都是獲取並刪除隊首元素,區別是當方法失敗時前者丟擲異常,後者返回null。由於刪除操作會改變佇列的結構,為維護小頂堆的性質,需要進行必要的調整
remove(Object o)方法用於刪除佇列中跟o相等的某一個元素(如果有多個相等,只刪除一個),該方法不是Queue介面內的方法,而是Collection介面的方法。由於刪除操作會改變佇列結構,所以要進行調整;又由於刪除元素的位置可能是任意的,所以調整過程比其它函式稍加繁瑣。具體來說,remove(Object o)可以分為2種情況:
- 刪除的是最後一個元素。直接刪除即可,不需要調整。
- 刪除的不是最後一個元素,從刪除點開始以最後一個元素為參照呼叫一次siftDown()即可。此處不再贅述2. 刪除的不是最後一個元素,從刪除點開始以最後一個元素為參照呼叫一次siftDown()即可。此處不再贅述