二叉堆

二叉堆定義

二叉堆是一種特殊的堆, 二叉堆是完全二叉樹或者近似完全二叉樹. 二叉堆滿足堆特性: 父節(jié)點的鍵值總是保持固定的序關(guān)系于任何一個子節(jié)點的鍵值(就是父節(jié)點大/小于子節(jié)點), 且每個節(jié)點的左子樹和右子樹都是一個二叉堆.
當(dāng)父節(jié)點的鍵值總是大于或等于任何一個子節(jié)點的鍵值時為最大二叉堆. 當(dāng)父節(jié)點的鍵值總是小于或等于任何一個子節(jié)點的鍵值時為最小堆

二叉堆的表現(xiàn)形式

二叉堆一般用數(shù)組來表示. 如果節(jié)點在數(shù)組中的位置是n(n是節(jié)點在數(shù)組中的下標(biāo)), 則n節(jié)點對應(yīng)的子節(jié)點在數(shù)組中的位置分別是 2n + 1 和 2n + 2. 因此, 第1個位置的子節(jié)點在2和3, 第2個位置的子節(jié)點在4和5, 以此類推.

如下圖, 左圖是最小堆, 右圖是最大堆

        1                         11
      /   \                      /  \
     2     3                   9     10
    /  \  /  \                / \   /  \
   4   5  6  7               5  6  7   8
  /\   /\                   /\  /\
 8  9 10 11                1 2 3  4 

而將這兩個堆保存在數(shù)組中:

左圖:  1  2  3  4  5  6  7  8  9 10 11
右圖: 11  9 10  5  6  7  8  1  2  3  4

明白了堆的特性以及存儲方式, 那我們就能推斷出:

1. parent(t) = (t - 1) >>>1 (t指數(shù)組中的下標(biāo), >>> 或 <<< 表示數(shù)據(jù)在進(jìn)行無符號的左右移動, 每移動一位就等于整除2)
2. left(t) = t << 1 + 1
3. right(t) = t << 1 + 2

注意:
parent(t): 指t節(jié)點的父節(jié)點在數(shù)組中的下標(biāo)
left(t): 指t節(jié)點的左子節(jié)點在數(shù)組中的下標(biāo)
right(t): 指t節(jié)點的右子節(jié)點在數(shù)組中的下標(biāo)

基本操作

二叉堆的基本操作: 插入元素, 刪除元素, 堆排序, 堆向上調(diào)整(siftUp), 堆向下調(diào)整(siftDown)

插入元素
/**
 * 插入元素到堆中
 * @param e
 * @return
 * @throws InterruptedException
 */
public boolean put(E e) throws InterruptedException{
    lock.lockInterruptibly();
    try {
        if(e == null) throw new NullPointerException();
        // 1. 判斷堆是否滿了, 滿了的話就等待, 直到有其他線程拿走元素
        if(size > queues.length){ // queue已經(jīng)滿了, 等待清楚
            notFull.await(); // 這個 await 是響應(yīng)線程中斷的
        }
        // 2. 若堆中沒有元素, 則直接在堆的頭節(jié)點放入元素
        if(size == 0){ // heap中沒有數(shù)據(jù)
            queues[0] = e;
        }else{
            queues[size] = e;
            size++;
            siftUp(size - 1, e);
        }
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
    return true;
}

插入:

  1. 若heap沒數(shù)據(jù)則直接將元素放置頭節(jié)點, ok
  2. 若heap有元素, 則將元素放置到為節(jié)點, 在調(diào)用 siftUp 進(jìn)行heap調(diào)整(那什么是 siftUp 調(diào)整呢, 我們接下來看)
向上調(diào)整

/**在數(shù)組中插入元素x到下標(biāo)為k的位置, 為保持堆的性質(zhì),
 * 通過siftUp來進(jìn)行調(diào)整, 直到x大于或等于x的 parent的值或到root節(jié)點
 *
 * @param k
 * @param x
 */
private void siftUp(int k, E x){
    Comparable<? super E> key = (Comparable<? super E>)x;
    while(k > 0){
        int parent = parent(k);  // 獲取對應(yīng)的父節(jié)點的下標(biāo)
        Object e = queues[parent]; // 獲取對應(yīng)的父節(jié)點對應(yīng)的值
        // 將當(dāng)前節(jié)點與父節(jié)點的值進(jìn)行比較
        // 若當(dāng)前節(jié)點比其父節(jié)點大, 則說明不在需要在向上 sift 比較了
        //
        if(key.compareTo((E) e) >= 0){
            break;
        }
        queues[k] = e; // 將父節(jié)點下沉
        k = parent; // 將這次比較的父節(jié)點賦值給k, 為下次 k 與其父節(jié)點作比較而準(zhǔn)備
    }
    // 這里的k 有可能是最初節(jié)點 x的父節(jié)點, 也有可能就是x節(jié)點父節(jié)點的下標(biāo)
    queues[k] = key;
}

siftUp:
整個邏輯比較簡單: 將當(dāng)前節(jié)點與父節(jié)點進(jìn)行比較, 不滿足堆特性的進(jìn)行調(diào)整, 直到滿足為止

刪除元素
/**
 * 獲取隊列中的頭節(jié)點
 * @return
 * @throws InterruptedException
 */
public E take() throws InterruptedException{

    /**
     * 1. 直接將頭節(jié)點獲取
     * 2. 將隊列中的尾節(jié)點拿出來, 從節(jié)點0開始siftdown -> 調(diào)整heap,使得,這個堆的最小值還在heap的頂上
     */
    E result = null;
    lock.lockInterruptibly();
    try {
        // 1. 若heap為空, 則等待, 直到有數(shù)據(jù)
        if(size == 0){ // heap中沒數(shù)據(jù)
            notEmpty.await(); // 等待放入數(shù)據(jù)
            return null;
        }
        int s = size - 1;
        // 2. 將heap的頭節(jié)點拿出來
        result = (E)queues[0];
        // 3. 將heap的尾節(jié)點拿出來, 若堆中還有元素, 在從(index=0)從開始siftdown調(diào)整堆
        E x = (E)queues[s];
        queues[s] = null;
        if(s != 0){
            siftDown(0, x);
        }
        size--;
        notFull.signal(); // 進(jìn)行喚醒
    } finally {
        lock.unlock();
    }
    return result;
}

刪除元素:

  1. 直接將頭節(jié)點獲取
  2. 將隊列中的尾節(jié)點拿出來, 從節(jié)點0開始siftdown -> 調(diào)整heap,使得,這個堆的最小值還在heap的頂上,那什么是siftDown, 我們接著看(其實和siftUp差不多)
堆向下調(diào)整

/**
 * 插入元素x到位置k, 為保持二叉堆的特性, 對x進(jìn)行siftDown, 直到x<=子節(jié)點
 * @param k
 * @param x
 */
private void siftDown(int k, E x){
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = parent(size -1); // 最后一個節(jié)點的父節(jié)點下標(biāo)
    while(k < half){
        // 1. 獲取子節(jié)點的坐標(biāo), 并取出兩者中的最小值
        int child = leftChildIndex(k);
        Object c = queues[child];
        int right = child + 1;

        // 2. 選中子節(jié)點中最小的那個節(jié)點進(jìn)行比較
        if(right < size -1 ){
            Object r = queues[right];
            if(((E)c).compareTo((E)r) >= 1) {
                c = queues[child = right];
            }
        }
        // 3. 若節(jié)點小于子節(jié)點, 則比較結(jié)束
        if(key.compareTo((E) c) <= 0){
            break;
        }
        queues[k] = c; // 將子節(jié)點上行
        k = child; // 父節(jié)點的光標(biāo)下移, 為下次比較準(zhǔn)備
    }
    queues[k] = key;
}

siftDown:整個邏輯比較簡單: 將當(dāng)前節(jié)點子節(jié)點進(jìn)行比較, 不滿足堆特性的進(jìn)行調(diào)整, 直到滿足為止

最后貼上上面代碼的完整版本 Heap.java

總結(jié):
二叉堆在java的運用比較廣, 如PriorityQueue, DelayQueue內(nèi)部實現(xiàn)都是基于二叉堆; 而難的地方個人覺得在主要在于堆的調(diào)整

  1. 元素進(jìn)行插入時, 直接插入到尾節(jié)點, 再將尾節(jié)點進(jìn)行向上調(diào)整, 直到根節(jié)點(整個調(diào)整其實就是swap值, 將最大/小放到根節(jié)點)
  2. 刪除元素時, 將尾元素放置到頭節(jié)點, 開始向下調(diào)整

參考資料:
vickyqi寫的Heap.java
skywang12345寫的二叉堆

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末一喘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凸克,老刑警劉巖议蟆,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萎战,居然都是意外死亡咐容,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蚂维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戳粒,“玉大人,你說我怎么就攤上這事虫啥∥翟迹” “怎么了?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵涂籽,是天一觀的道長苹祟。 經(jīng)常有香客問我,道長评雌,這世上最難降的妖魔是什么树枫? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮景东,結(jié)果婚禮上砂轻,老公的妹妹穿的比我還像新娘。我一直安慰自己斤吐,他們只是感情好舔清,可當(dāng)我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曲初,像睡著了一般体谒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臼婆,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天抒痒,我揣著相機(jī)與錄音,去河邊找鬼颁褂。 笑死故响,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颁独。 我是一名探鬼主播彩届,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼誓酒!你這毒婦竟也來了樟蠕?” 一聲冷哼從身側(cè)響起贮聂,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寨辩,沒想到半個月后吓懈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡靡狞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年耻警,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甸怕。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡甘穿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梢杭,到底是詐尸還是另有隱情扒磁,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布式曲,位于F島的核電站妨托,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吝羞。R本人自食惡果不足惜兰伤,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钧排。 院中可真熱鬧敦腔,春花似錦、人聲如沸恨溜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糟袁。三九已至判族,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間项戴,已是汗流浹背形帮。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留周叮,地道東北人辩撑。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像仿耽,于是被迫代替她去往敵國和親合冀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,500評論 2 348

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

  • 我們有意調(diào)整了排序的順序项贺,最后講這個堆排序君躺。不是因為它很難峭判,而是它涉及到了基本的數(shù)據(jù)結(jié)構(gòu)知識。 堆晰洒,又名“優(yōu)先隊列...
    吃個小燒餅閱讀 389評論 0 3
  • 介紹 在以往工作或者面試的時候常會碰到一個問題,如何實現(xiàn)海量TopN啥箭,就是在一個非常大的結(jié)果集里面快速找到最大的前...
    簡單方式閱讀 4,322評論 6 48
  • 1.什么是二叉堆 二叉堆是一種特殊的堆谍珊,二叉堆是完全二元樹(二叉樹)或者是近似完全二元樹(二叉樹)。二叉堆有兩種:...
    RavenX閱讀 2,171評論 0 3
  • 二叉堆 堆有序定義:當(dāng)一顆二叉樹的每個節(jié)點都大于等于它的兩個子節(jié)點時急侥, 被稱為堆有序砌滞。二叉堆定義: 二叉堆是一組能...
    leoLy閱讀 2,056評論 0 2
  • 二叉堆是優(yōu)先隊列很普遍的一種實現(xiàn),它又分為最小堆最大堆坏怪,最小堆和最大堆都是完全二叉樹贝润。其結(jié)構(gòu)體定義如下: 二叉堆的...
    KardelShaw閱讀 680評論 0 0