Java中PriorityQueue相關

優先隊列:依靠堆數據結構來實現,因爲堆又是完全二叉樹,採用數組來存儲,父子關係依靠下標來維繫。

Java中,如果調用無參構造函數,默認生成一個能夠容納11個元素的數組,如果不指定比較器,按照小根堆來排列。

擴容操作:PriorityQueue的擴容選擇和ArrayList(擴大到原來容量的1.5倍),Vector(默認擴容到原容量的2倍,如果指定了擴容大小,就擴容指定大小)的策略都不同,PriorityQueue的擴容策略:如果當前數組容量小於64,就擴容到原來容量的2倍+2, 如果數組容量大於64, 就擴容到原來的1.5倍。

因爲涉及到比較操作,所以在構造的時候允許傳入一個比較器,如果傳入比較器,就用傳入的比較器來比較兩個元素的大小,如果沒有傳入比較器,就用數組元素自己的比較器來比較。

堆的基本調整操作:siftUp(int k, E x), siftDown(int k, E x)

向上調整函數siftUp(int k, E x): 從當前位置開始,一直和父節點比較,如果小於父節點,就將父節點移到當前位置,然後將當前位置上移到父節點位置,接着和父節點進行比較,直到大於父節點或者當前位置移動到根節點,結束。

向下調整函數siftDown(int k, E x):只有待調整節點不是葉子節點的時候,纔有調整的必要,如果是葉子節點,不需要調整。

類似於向上調整,向下調整的過程中,當前位置和孩子節點中小的一個進行比較,如果孩子節點更小,則將孩子節點上提作爲父節點,當前位置下移到孩子節點的位置,接着和孩子節點中小的一個進行比較,直到當前節點小於兩個孩子節點或者已經到達了葉子節點,結束。

調整過程中, half = size >>>1; (邏輯右移操作符:高位補0),因爲數組下標是從0開始的,half代表的就是第一個葉子節點的下標,調整過程都是在非葉子節點上進行的,所以結束條件就是while(k < half)。

調整操作heapify():數組中元素初始狀態可能並不滿足堆的要求,就需要進行調整,調整的策略非常簡單,從最後一個非葉子節點開始調用siftDown,一直到根節點,調整操作完成。所以說調整操作還是在siftDown()基礎上。源碼:

一個需要特別注意的地方:刪除特定對象的remove()函數,因爲要維持堆的特性,在刪除元素以後,將數組的最後一個元素放到刪除位置,然後進行調整。在調整過程中就有兩種情況:1.刪除位置和最後一個元素都在根節點的同一棵子樹上,則進行向下調整即可(siftDown); 2.如果不在根節點的同一棵子樹上,則大小關係不確定,不一定是向上調整(siftUp)還是向下調整(siftDown), 先向下調整,調整完成以後如果還是原地,嘗試進行向上調整,如果需要向上調整,就會出現最後一個元素被調整到刪除位置的前面。爲了區分這兩種情況,remove()採取的策略是根據返回值來判斷,如果返回null,則說明最後一個元素經過調整以後位於刪除位置的後面,如果返回的是一個元素值,則說明最後一個元素被調整到了刪除位置的前面。

經過上面的描述,就會發現一個問題,如果執行刪除操作以後,最後一個元素被調整到了刪除位置前面,那麼迭代器的遍歷操作就有可能漏掉這個元素,Java中解決的辦法就是提供了一個額外的數據結構:雙端隊列forgetMeNot,將所有這種情況的元素都放入到forgetMeNot中,遍歷過程中,遍歷到數組末尾,還需要檢查forgetMeNot是否爲空,不爲空的話還需要對forgetMeNot進行遍歷。


 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章