二叉堆與Java中的優(yōu)先隊(duì)列


之前在A*算法演示程序的編碼過程中齿拂,發(fā)現(xiàn)javaScript并沒有原生的優(yōu)先隊(duì)列大脉,于是去Java中找到了PriorityQueue類蕾盯,研究了一下源碼。Java中的優(yōu)先隊(duì)列基于最小二叉堆實(shí)現(xiàn)敬飒。最小二叉堆具有兩個(gè)性質(zhì):

  • 結(jié)構(gòu)性:必須是一顆完全二叉樹,樹的插入從左到右芬位,一棵完全二叉樹的高度為小于log(N)的最大整數(shù)无拗。

  • 堆序性:二叉樹的父節(jié)點(diǎn)必須小于兩個(gè)子節(jié)點(diǎn)。父節(jié)點(diǎn)下標(biāo)為n昧碉,兩個(gè)子節(jié)點(diǎn)下標(biāo)為2n+1和2(n+1)英染。父節(jié)點(diǎn)的值總是小于子節(jié)點(diǎn)揽惹,兩個(gè)子節(jié)點(diǎn)間大小關(guān)系不確定。

Java PriorityQueue特點(diǎn):

  • 不接受null值

  • 不接受不可排序的值

  • 線程不安全

  • 容量沒有上限

  • 插入和刪除元素的時(shí)間復(fù)雜度為O(log(n))

首先來看下最關(guān)鍵的兩個(gè)方法四康,siftDown(int k, E x)和siftUp(int k, E x)搪搏。這兩個(gè)方法是插入、刪除闪金、排序和初始化操作的基礎(chǔ)疯溺。兩個(gè)方法的目的都是讓節(jié)點(diǎn)k所在的子樹符合二叉堆的性質(zhì),堆序性哎垦。區(qū)別在于囱嫩,siftDown將節(jié)點(diǎn)k作為子樹的父節(jié)點(diǎn)處理,siftUp則將節(jié)點(diǎn)k作為子樹的子節(jié)點(diǎn)處理漏设。

siftDown


private void siftDownComparable(int k, E x) {        Comparablekey = (Comparable)x;        int half = size >>> 1;        //k) c).compareTo((E) queue[right]) > 0)                c = queue[child = right];            //如果父節(jié)點(diǎn)小于子節(jié)點(diǎn)墨闲,當(dāng)前子樹已經(jīng)滿足條件            //不需要繼續(xù)            if (key.compareTo((E) c) <= 0)                break;            //否則,父節(jié)點(diǎn)與子節(jié)點(diǎn)對(duì)調(diào)郑口,繼續(xù)上述步驟            queue[k] = c;            k = child;        }        queue[k] = key;    }~~~關(guān)于這部分代碼损俭,比較有意思的是迭代條件`k>>1`,我們分size為奇數(shù)和偶數(shù)兩種情況來看:* 奇數(shù):size為奇數(shù),`half=(size-1)/2`潘酗,最后一個(gè)節(jié)點(diǎn)下標(biāo)為`size-1`杆兵,父節(jié)點(diǎn)下標(biāo)為`(size-1)/2-1`,所以`kkey = (Comparable) x;

while (k > 0) {

int parent = (k - 1) >>> 1;

Object e = queue[parent];

if (key.compareTo((E) e) >= 0)

break;

queue[k] = e;

k = parent;

}

queue[k] = key;

}

siftUp的代碼很容易理解仔夺,遞歸的與父節(jié)點(diǎn)比較大小琐脏,然后根據(jù)結(jié)果決定是否需要對(duì)調(diào)位置。

heapify

接下來是PriorityQueue的初始化方法中最復(fù)雜的一個(gè)缸兔,從一個(gè)無需集合(Collection)生成一個(gè)優(yōu)先隊(duì)列日裙。


private void initFromCollection(Collection c) {

initElementsFromCollection(c);

heapify();

}

initElementsFromCollection(c)方法很簡(jiǎn)單,將集合c中的數(shù)據(jù)放入隊(duì)列惰蜜,將集合c的大小賦予size屬性昂拂。而heapify()方法就是將集合c轉(zhuǎn)換為二叉堆的關(guān)鍵。


private void heapify() {

for (int i = (size >>> 1) - 1; i >= 0; i--)

siftDown(i, (E) queue[i]);

}

size>>>1的特殊性我們剛才已經(jīng)討論過了抛猖,這里看下heapify()是怎么做的格侯,從(size>>>1)-1開始向上遍歷,對(duì)每一個(gè)父節(jié)點(diǎn)财著,都要保證它所在的子樹符合條件联四,那么整棵樹都將符合條件。

add offer

向隊(duì)列中添加元素撑教,兩者代碼相同朝墩,之所以并存是因?yàn)镻riorityQueue繼承自Queue。


public boolean offer(E e) {

if (e == null)

throw new NullPointerException();

modCount++;

int i = size;

if (i >= queue.length)

//容量不夠時(shí)伟姐,擴(kuò)容

grow(i + 1);

size = i + 1;

if (i == 0)

queue[0] = e;

else

siftUp(i, e);

return true;

}

這里將元素放于隊(duì)列尾部收苏,調(diào)用siftUp方法進(jìn)行調(diào)整亿卤。最壞的情況,新插入的元素比根節(jié)點(diǎn)的元素還要小鹿霸,那么siftUp要執(zhí)行到根節(jié)點(diǎn)所在的子樹排吴。

removeAt

刪除隊(duì)列中下標(biāo)為i的元素。由于要保持完整二叉樹的結(jié)構(gòu)杜跷,刪除元素后仍需要進(jìn)行重新排序傍念。


private E removeAt(int i) {

modCount++;

int s = --size;

//如果i就是最后一個(gè)節(jié)點(diǎn)的下標(biāo),直接刪除

if (s == i)

queue[i] = null;

else {

E moved = (E) queue[s];

queue[s] = null;

siftDown(i, moved);

if (queue[i] == moved) {

siftUp(i, moved);

if (queue[i] != moved)

return moved;

}

}

return null;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末葛闷,一起剝皮案震驚了整個(gè)濱河市憋槐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淑趾,老刑警劉巖阳仔,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扣泊,居然都是意外死亡近范,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門延蟹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來评矩,“玉大人,你說我怎么就攤上這事阱飘〕舛牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵沥匈,是天一觀的道長(zhǎng)蔗喂。 經(jīng)常有香客問我,道長(zhǎng)高帖,這世上最難降的妖魔是什么缰儿? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮散址,結(jié)果婚禮上乖阵,老公的妹妹穿的比我還像新娘。我一直安慰自己爪飘,他們只是感情好义起,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著师崎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪椅棺。 梳的紋絲不亂的頭發(fā)上犁罩,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天齐蔽,我揣著相機(jī)與錄音,去河邊找鬼床估。 笑死含滴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丐巫。 我是一名探鬼主播谈况,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼递胧!你這毒婦竟也來了碑韵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后声滥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吧趣,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年陕截,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豁遭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贺拣,到底是詐尸還是另有隱情蓖谢,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布纵柿,位于F島的核電站蜈抓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昂儒。R本人自食惡果不足惜沟使,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渊跋。 院中可真熱鬧腊嗡,春花似錦、人聲如沸拾酝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒿囤。三九已至客们,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背底挫。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工恒傻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人建邓。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓盈厘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親官边。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沸手,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容