PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

最近在刷力扣題的時候不止一次看到過這個PriorityQueue,他的優良特性可以幫助我們解決大量的題目。這篇文章從用法、原理、源碼以及力扣實際題目的角度進行一個全面的分析。希望對你有幫助。

一、什麼是優先級隊列

1、概念

我們都知道隊列,隊列的核心思想就是先進先出,這個優先級隊列有點不太一樣。優先級隊列中,數據按關鍵詞有序排列,插入新數據的時候,會自動插入到合適的位置保證隊列有序。(順序有兩種形式:升序或者是降序)

來一個標準點的定義:

PriorityQueue類在Java1.5中引入。PriorityQueue是基於優先堆的一個無界隊列,這個優先隊列中的元素可以默認自然排序或者通過提供的Comparator(比較器)在隊列實例化的時排序。

要求使用Java Comparable和Comparator接口給對象排序,並且在排序時會按照優先級處理其中的元素。

比如我們往隊列裡面插入132,插入2的時候,就會在內部調整為123(默認順序是升序)。正是由於這個優良特性可以幫助我們實現一系列問題。我們先看一個例子,體會一下他的優點,然後再看一下為什麼能夠實現這樣的功能。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

我們看到就算是我們隨意插入數據,但是輸出的結果總是有序的,這樣一來優先級隊列就可以有了很多個使用場景。下面給出一道力扣題。體會一下他的優點。

2、案例演示特性

Leetcode215題:在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。

輸入:3,2,3,1,2,4,5,5,6,k = 4。輸出就是5,因此重複的並不考慮。我們一般情況下一般首先想到冒泡排序。每一次都冒出來一個最小的元素,這樣冒K次,就是我們想要的結果。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

其實冒泡排序還可以解決另外一個問題,那就是返回倒數第K大的元素,方法是每次冒出來一個最大的元素即可。

這樣做很簡單,但是需要我們自己手寫冒泡排序,下面我們看使用優先級隊列如何解決的。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

就這幾行代碼就搞定了,是不是超級簡單。為什麼優先級隊列能夠實現呢?下面我們來分析一下他的數據結構:

3、數據結構

優先級隊列底層的數據結構其實是一顆二叉堆,什麼是二叉堆呢?我們來看看

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

在這裡我們會發現以下特徵:

(1)二叉堆是一個完全二叉樹

(2)根節點總是大於左右子節點(大頂堆),或者是小於左右子節點(小頂堆)。

如果我們要插入一個節點怎麼辦呢?

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

自己使用畫圖工具畫的,能看懂就行,不要在意那些細節兄弟。過程如下:

(1)找到待插入位置:滿足完全二叉樹的特點,依次插入

(2)插入之後判斷是否滿足二叉堆的性質,不滿足那就調整

(3)1<==>6交換,發現不滿足,1<==>4交換,滿足即停止。

對於我們的優先級隊列就是這樣的一種數據結構,因此我們在插入的時候保證了數據的有序性。

OK。到這基本上我們能夠體會到優先級隊列的思想了。來一個小結:

優先級隊列使用二叉堆的特點,可以使得插入的數據自動排序(升序或者是降序)。

現在我們知道了這些,還沒講源碼。從源碼的角度來體會一下:

二、源碼分析(基於jdk1.8)

源碼分析一般的順序都是先類屬性、構造方法、普通方法。在編譯器中鼠標定位到這個PriorityQueue上,ctrl+鼠標左鍵就可以進入到這個集合的源碼裡面。

1、屬性

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

2、構造方法

(1)默認構造方法:PriorityQueue()

使用默認的初始容量(11)創建一個 PriorityQueue,並根據其自然順序對元素進行排序。

(2)包含集合元素:PriorityQueue(Collection c)

創建包含指定 collection 中元素的 PriorityQueue。

(3)指定初始容量:PriorityQueue(int initialCapacity)

使用指定的初始容量創建一個 PriorityQueue,並根據其自然順序對元素進行排序。

(4)指定初始容量和比較器:PriorityQueue(int initialCapacity, Comparator comparator)

使用指定的初始容量創建一個 PriorityQueue,並根據指定的比較器對元素進行排序。

(5)包含優先級元素:PriorityQueue(PriorityQueue c)

創建包含指定優先級隊列元素的 PriorityQueue。

(6)包含set元素:PriorityQueue(SortedSet c)

​ 創建包含指定有序 set 元素的 PriorityQueue。

3、普通方法

PriorityQueue中常用的方法很多。來看幾個常用的。

(1)add:插入一個元素,不成功會拋出異常

<code>public boolean add(E e) {
        return offer(e);
}/<code>

我們看到add方法其實是通過調用offer方法實現的。我們直接看offer方法

(2)offer:插入一個元素,不能被立即執行的情況下會返回一個特殊的值(true 或者 false)

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

注意,優先級隊列插入的元素不能為空,這一點在文章一開始提到過。步驟是這樣的:

首先把modCount數量加1,如果容量不夠把當前隊列的尺寸加1,最後在i的位置上使用siftUp方法把e添加進來。此時真正插入的操作又落到了siftUp方法身上,我們接著看。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

尼瑪,這裡也沒有實現真正的插入操作,而是先判斷是否使用了自己的比較器。我們直接來看自己的比較器不為空,如何插入。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

這裡就是真正的插入操作了。一個正常的數據插入過程。沒什麼特別的。

(3)remove:刪除一個元素,如果不成功會返回false。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

這裡會發現真正實現刪除操作的是removeAt方法。我們跟進去看看

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

這個刪除操作主要是兩部分,if裡面判斷刪除的是否是最後一個,否則的話就是用siftDown方法進行“向下沉”刪除。不成功那就使用“向上浮”。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

刪除的時候同樣需要進行判斷比較器。

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

OK。刪除操作就是這麼多。基本思路是向上浮還是向下沉。

(4)poll:刪除一個元素,並返回刪除的元素

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

又回到了siftDown刪除操作,就不贅述了。

(5)peek:查詢隊頂元素

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

(6)indexOf(Object o):查詢對象o的索引

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

(7)contain(Object o):判斷是否容納了元素

PriorityQueue:非常實用的java優先級隊列(jdk1.8源碼分析)

實現原理很簡單和上面的一樣。

OK,源碼就是這些。

三、優先級隊列使用

上面已經介紹了原理和源碼。而且給出了一個實際力扣案例。但是那屬於算法,其實在真正工作領域也有不少的應用場景。

1、股票交易

我們的股票屏幕上總是給出最好或者是表現最差的那些股票。就可以基於優先級隊列。方法其實是和找出前K個最大最小的元素方法類似。可以類比到股票中。

這只是給出了一個案例,你可以把股票交易的這樣的使用場景類比到其他的場景中去。

2、會員項目

會員的優先級總是比普通會員高,因此我們就可以使用優先級隊列保存會員的優先級。

這裡只給出倆。其他的各位同僚自己體會吧,我自己曾經使用優先級隊列來保存無人機的各種狀態信息。所以也可以保存各種狀態信息。


分享到:


相關文章: