數(shù)據(jù)結構(三)-- 優(yōu)先隊列

什么是優(yōu)先隊列菩混?

我們在前幾篇文章中學習過了“隊列”這種數(shù)據(jù)結構。那么優(yōu)先隊列和普通隊列有什么區(qū)別的呢扁藕?普通隊列的特點是“先進先出”沮峡,優(yōu)先隊列則不一樣,它的入隊順序沒有變化亿柑,但是出隊的順序是根據(jù)優(yōu)先級的高低來決定的邢疙。優(yōu)先級高的優(yōu)先出隊。

本文首發(fā)于心安-XinAnzzZ 的個人博客,轉載請注明出處~

舉個生活中的小栗子秘症。病人去醫(yī)院看病照卦,正常情況下都是需要取號排隊,這是普通隊列乡摹。但是特殊情況下役耕,有一個情況緊急的病人突然來了,這個病人就擁有優(yōu)先權聪廉,他就優(yōu)先出隊瞬痘,這就是優(yōu)先隊列。

再比如說板熊,玩過LOL的同學都知道框全,游戲里面防御塔都有一個自動攻擊功能,小兵排著隊進入防御塔的攻擊范圍干签,這時候大炮車的優(yōu)先級更高(因為系統(tǒng)判定大炮車對于防御塔的威脅更大)津辩,所以防御塔會優(yōu)先攻擊大炮車。而當大炮車陣亡容劳,剩下的全部都是普通小兵喘沿,這時候離得近的優(yōu)先級越高,防御塔優(yōu)先攻擊距離更近的小兵竭贩。

希望通過這兩個小栗子蚜印,各位同學能夠明白優(yōu)先隊列是怎么一回事,從而更好的理解后面的內容留量。

  • 各種不同的底層數(shù)據(jù)結構所對應的時間復雜度
入隊 出隊(取出最大元素)
普通線性結構 O(1) O(n)
順序線性結構 O(n) O(1)
O(logn) O(logn)

我們假設有一個優(yōu)先隊列窄赋,它的優(yōu)先級是元素越大優(yōu)先級越高,也就是每次出隊的是隊列中值最大的元素楼熄。那么如果我們使用普通的線性結構(比如數(shù)組和鏈表)來實現(xiàn)忆绰,那么入隊的操作,直接插入即可孝赫,時間復雜度是O(1)较木,出隊我們需要進行遍歷整個隊列找到最大元素,然后出隊,時間復雜度是O(n)驹吮。而如果使用順序的線性結構(順序線性結構指的是數(shù)據(jù)已經(jīng)有了順序逢并,比如二分搜索樹,中序遍歷就是有序的)弦撩,入隊的時候需要進行遍歷整個隊列來找到合適的位置來插入這個元素,時間復雜度是O(n),出隊的時候由于有序双戳,所以直接取出,時間復雜度是O(1)糜芳。我們知道飒货,O(n)的時間復雜度是效率比較低的魄衅,所以我們接下來介紹一種入隊和出隊都是O(logn)時間復雜度的數(shù)據(jù)結構--堆。

  • 完全二叉樹

有一定基礎的同學對這個概念應該不會陌生塘辅,這里還是貼上完全二叉樹的百度百科晃虫。概念性的東西我不解釋太多,大家自己去體會和理解扣墩。這里我只說自己的理解哲银,也是我認為最簡單的理解。

一棵樹呻惕,元素從左往右荆责、從上到下依次排列,中間不能有空缺亚脆,就是一個完全二叉樹做院。這里要注意區(qū)分完全二叉樹和滿二叉樹的區(qū)別。如下圖:這棵樹擁有10個元素濒持,這10個元素從上到下键耕、從左到右依次排列,這就是一個完全二叉樹弥喉。假如說6郁竟、7、8由境、9這四個位置任意一個位置元素為空棚亩,那么就不是完全二叉樹。
image
  • 二叉堆

二叉堆是特殊的完全二叉樹虏杰。它滿足“堆中任意一個節(jié)點的值都不大于其父親節(jié)點的值”這一特征讥蟆。當然,這個“不大于”是由我們來定義的纺阔,我們也可以定義為“不小于”瘸彤。如果是“不大于”,那么形成的就是“最大堆”笛钝,反之就是“最小堆”质况。下面就是一個最小堆和一個最大堆。注意一個誤區(qū)玻靡,在最大堆當中结榄,堆中任意一個節(jié)點的值都不大于父親節(jié)點的值,是否意味著層次越低的節(jié)點值越大呢囤捻?這是不一定的臼朗。

image
image
  • 二叉堆的數(shù)組實現(xiàn)

上面我們說了,二叉堆是一個完全二叉樹,就是一個一個元素從左到右视哑、從上到下依次排列出來的绣否,那也就可以使用數(shù)組的方式來表示二叉堆。用數(shù)組來表示一個二叉堆要解決的問題就是挡毅,我們如何找到一個節(jié)點的左右孩子蒜撮。如果我們使用的是樹的形式來表示的話,我們是可以通過一個“指針”來表示它的左右孩子慷嗜,但是數(shù)組是沒有“指針”的淀弹,應該如何實現(xiàn)呢?仔細觀察一下下圖庆械,我對這個二叉堆從上到下薇溃、從左到右進行了編號,編號就代表了元素在數(shù)組中的索引缭乘,我們可以發(fā)現(xiàn)沐序,節(jié)點編號滿足以下規(guī)律:

parent(i) = (i - 1) / 2 left child(i) = 2 * i + 1 right child(i) = (i + 1) * 2

image
  1. 下面我們新建一個工程,創(chuàng)建名為MaxHeap(最大堆)的類在里面加入以下代碼堕绩,這里我們底層依賴ArrayList策幼。

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n49" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 30px 10px 0px; border: 1px solid; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> public class MaxHeap<E extends Comparable<E>> {
?
private ArrayList<E> data;
?
/*** 根據(jù)孩子節(jié)點索引獲取父節(jié)點的索引 /
private int getParentIndex(int childIndex) {
if (childIndex == 0) {
throw new IllegalArgumentException("the root node doesn't have parent node !");
}
return (childIndex - 1) / 2;
}
?
/
** 根據(jù)父節(jié)點索引獲取左孩子索引 /
private int getLeftChildIndex(int parentIndex) {
if (parentIndex * 2 + 1 > size() - 1) {
throw new IllegalArgumentException("the parent node doesn't have left child node !");
}
return parentIndex * 2 + 1;
}
?
/
** 根據(jù)父節(jié)點索引獲取右孩子索引 */
private int getRightChildIndex(int parentIndex) {
if ((parentIndex + 1) * 2 > size() - 1) {
throw new IllegalArgumentException("the parent node doesn't have right child node !");
}
return (parentIndex + 1) * 2;
}
}</pre>

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n59" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 30px 10px 0px; border: 1px solid; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> public void add(E e) {
data.add(e);
int index = data.size() - 1;
// 如果父節(jié)點的值小于新節(jié)點的值,進行位置互換
while (index > 0 && data.get(getParentIndex(index)).compareTo(e) < 0) {
data.set(index, data.get(getParentIndex(index)));
data.set(getParentIndex(index), e);
index = getParentIndex(index);
}
}</pre>

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n66" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 30px 10px 0px; border: 1px solid; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> /*** 提取最大值 */
public E extractMax() {
if (data.isEmpty()) {
throw new IllegalArgumentException("The heap is empty !");
}
E result = data.get(0);
E remove = data.remove(size() - 1);
?
if (size() == 0) {
return result;
}
data.set(0, remove);
?
int index = 0;
// 如果說左孩子的索引值小于 size 說明左孩子存在奴紧,當左孩子不存在的時候 循環(huán)終止
while (getLeftChildIndex(index) < size()) {
// 假設左右孩子中左孩子的值較大
int maxIndex = getLeftChildIndex(index);
?
// 如果右孩子存在且有孩子的值大于左孩子特姐,則最大值的索引等于右孩子
if (getRightChildIndex(index) < size()
&& data.get(maxIndex).compareTo(data.get(getRightChildIndex(index))) < 0) {
maxIndex = getRightChildIndex(index);
}
// 如果當前節(jié)點值小于左右孩子節(jié)點中較大的那個值,則進行位置互換黍氮,否則跳出循環(huán)
if (data.get(index).compareTo(data.get(maxIndex)) < 0) {
// 互換位置
E e = data.get(index);
data.set(index, data.get(maxIndex));
data.set(maxIndex, e);
?
index = maxIndex;
} else {
break;
}
}
return result;
}</pre>

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n72" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 30px 10px 0px; border: 1px solid; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> public interface Queue<E> {
?
/**

  • inserts the element into the queue
  • @param e the element to add
    /
    void enqueue(E e);
    ?
    /
    *
  • remove the head of this queue
  • @return the has been removed element
    /
    E dequeue();
    ?
    /
    *
  • get the head of this queue
  • @return the element at front of the queue
    /
    E getFront();
    ?
    /
    *
  • get the queue's size
  • @return the queue's size
    /
    int size();
    ?
    /
    *
  • whether is empty or not
  • @return {@code true} if the queue is empty
    */
    boolean isEmpty();
    }</pre>

<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="java" cid="n76" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; position: relative !important; padding: 10px 30px 10px 0px; border: 1px solid; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;"> public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
?
private MaxHeap<E> maxHeap;
?
public PriorityQueue() {
this.maxHeap = new MaxHeap<>();
}
?
@Override
public void enqueue(E e) {
maxHeap.add(e);
}
?
@Override
public E dequeue() {
return maxHeap.extractMax();
}
?
@Override
public E getFront() {
return maxHeap.getMax();
}
?
@Override
public int size() {
return maxHeap.size();
}
?
@Override
public boolean isEmpty() {
return maxHeap.isEmpty();
}
}</pre>

示例代碼Github

好了唐含,本文就到此為止了。后續(xù)的博文會為大家?guī)鞮eetCode上面的一道題目沫浆,就是使用本文的最大堆來解決的捷枯,到時候可以看一下最大堆的實際應用。 謝謝觀看~~

  1. 具體實現(xiàn)

  2. 定義隊列接口

以最大堆為底層實現(xiàn)優(yōu)先隊列

以上就是使用數(shù)組來實現(xiàn)一個最大堆的核心代碼专执,下面我們以這個最大堆為基礎實現(xiàn)一個優(yōu)先隊列淮捆。

移除最大元素代碼如下:

image

由于堆的特殊性,所以我們取出元素只會取出堆中的最大元素本股,畢竟人家名字就叫“最大堆”攀痊。由于堆的性質,最大元素其實就是根元素拄显,如果移除根元素蚕苇,那么就不再是一個樹了,解決辦法是移除根元素之后將堆中最后一個元素放到根元素的位置凿叠,但是這樣就不再滿足堆中元素不大于父節(jié)點元素這一性質了。 所以之后一步的操作是用新的根元素和左右孩子中較大的元素進行比較,然后和較大的元素進行互換盒件,直到新的根元素不小于左右孩子為止蹬碧,這樣刪除最大節(jié)點之后的樹就還能滿足堆的性質了。這個過程我們稱為“Sift down”炒刁,即下沉恩沽。 下面使用圖解和代碼來演示如何從取出堆中最大元素。

  1. 從堆中移除最大元素

然后我們就可以編寫添加元素的代碼了:

image

一般來講翔始,如果新添加的元素大于其父親節(jié)點罗心,那么將這個元素和其父親節(jié)點進行位置互換,如果仍然大于其父親節(jié)點城瞎,繼續(xù)互換渤闷,直到不大于父節(jié)點為止。 在數(shù)據(jù)結構中脖镀,我們將這個過程稱之為“Sift up”飒箭,翻譯過來就是上浮。下圖展示了“上浮”的過程蜒灰。

通過上面的的概念可以知道弦蹂,向堆中添加一個元素只需要在最后一個元素后面添加一個元素,這個在數(shù)組的操作中是非常簡單的强窖。但是如果添加的元素比父親節(jié)點大凸椿,那么就不再滿足二叉堆的性質,這個問題應該如何解決呢翅溺?

  1. 向堆中添加一個元素脑漫。

首先,我們的類中的元素需要具有可比性未巫,所以我們的元素需要是Comparable接口的子類窿撬。成員變量data用來裝載堆中的數(shù)據(jù),它使用的是ArrayList叙凡。然后根據(jù)上面我們得到的公式劈伴,寫出了“根據(jù)子節(jié)點得到父節(jié)點索引”以及“根據(jù)父節(jié)點得到子節(jié)點”的方法,這三個方法在后面的增刪等操作中尤為重要握爷。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末跛璧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子新啼,更是在濱河造成了極大的恐慌追城,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燥撞,死亡現(xiàn)場離奇詭異座柱,居然都是意外死亡迷帜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門色洞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戏锹,“玉大人,你說我怎么就攤上這事火诸〗跽耄” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵置蜀,是天一觀的道長奈搜。 經(jīng)常有香客問我,道長盯荤,這世上最難降的妖魔是什么馋吗? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮廷雅,結果婚禮上耗美,老公的妹妹穿的比我還像新娘。我一直安慰自己航缀,他們只是感情好商架,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芥玉,像睡著了一般蛇摸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灿巧,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天赶袄,我揣著相機與錄音,去河邊找鬼抠藕。 笑死饿肺,一個胖子當著我的面吹牛,可吹牛的內容都是我干的盾似。 我是一名探鬼主播敬辣,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼零院!你這毒婦竟也來了溉跃?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤告抄,失蹤者是張志新(化名)和其女友劉穎撰茎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體打洼,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡龄糊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年逆粹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绎签。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡枯饿,死狀恐怖,靈堂內的尸體忽然破棺而出诡必,到底是詐尸還是另有隱情,我是刑警寧澤搔扁,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布爸舒,位于F島的核電站,受9級特大地震影響稿蹲,放射性物質發(fā)生泄漏扭勉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一苛聘、第九天 我趴在偏房一處隱蔽的房頂上張望涂炎。 院中可真熱鬧,春花似錦设哗、人聲如沸唱捣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽震缭。三九已至,卻和暖如春战虏,著一層夾襖步出監(jiān)牢的瞬間拣宰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工烦感, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巡社,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓手趣,卻偏偏與公主長得像晌该,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子回懦,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容