數(shù)據(jù)結(jié)構(gòu)之BinaryHeap解析

預(yù)備知識(shí)
完全二叉樹(shù)的定義:
若設(shè)二叉樹(shù)的高度為h省有,除第 h 層外患雇,其它各層 (1~h-1) 的結(jié)點(diǎn)數(shù)都達(dá)到最大個(gè)數(shù)前硫,第 h 層從右向左連續(xù)缺若干結(jié)點(diǎn),這就是完全二叉樹(shù)

滿(mǎn)二叉樹(shù)特點(diǎn):
1、深度為k且含有2k-1個(gè)結(jié)點(diǎn)的二叉樹(shù)
2、所有節(jié)點(diǎn)的度要么為0骤星,要么為2,且所有葉子節(jié)點(diǎn)都在最后一層
完全二叉樹(shù)特點(diǎn):
1汤善、樹(shù)中所含n個(gè)節(jié)點(diǎn)與滿(mǎn)二叉樹(shù)的節(jié)點(diǎn)編號(hào)一一對(duì)應(yīng)
2、葉子節(jié)點(diǎn)只會(huì)出現(xiàn)在最后兩層票彪,且葉子節(jié)點(diǎn)都是靠左對(duì)齊

使用數(shù)組來(lái)實(shí)現(xiàn)完全二叉樹(shù)時(shí)有以下規(guī)律:
葉子節(jié)點(diǎn)個(gè)數(shù)=floor((n+1)/2) n是元素?cái)?shù)量
父節(jié)點(diǎn)索引=floor((i-1)/2) i是當(dāng)前節(jié)點(diǎn)索引
左子節(jié)點(diǎn)索引=2i+1


堆(heap)红淡,又稱(chēng)為優(yōu)先隊(duì)列(priority queue),堆并不是隊(duì)列抹镊,在隊(duì)列中锉屈,我們可以進(jìn)行的操作是向隊(duì)列中添加元素和按照元素進(jìn)入隊(duì)列的先后順序取出元素荤傲。而在堆中垮耳,我們不是按照元素進(jìn)入隊(duì)列的先后順序,而是按照元素的優(yōu)先級(jí)取出元素

堆通常是一個(gè)可以被看做一棵 [完全二叉樹(shù)] 的數(shù)組對(duì)象
我們常用的二叉堆就是一顆任意節(jié)點(diǎn)的優(yōu)先級(jí)不小于其子節(jié)點(diǎn)的完全二叉樹(shù)

思考:為什么說(shuō) 堆通常是一個(gè)可以被看做一棵 [完全二叉樹(shù)] 的數(shù)組對(duì)象
二叉樹(shù)相較于數(shù)組更為復(fù)雜遂黍,其結(jié)構(gòu)特點(diǎn)決定所需占用內(nèi)存空間的大小高于數(shù)組

優(yōu)先級(jí)可以是大小比較或其他條件下的比較(因?yàn)橐笤匾獏⑴c比較终佛,所以堆中不允許元素為null)
比如常見(jiàn)的大根堆:任意節(jié)點(diǎn)的值總是≥子節(jié)點(diǎn)的值,小根堆則相反

堆接口的基本設(shè)計(jì)
int size( ); ? 元素的數(shù)量
boolean isEmpty( ); ? 是否為空
void clear( ); ? ?清空堆中所有元素
void add(E element); ? 插入元素
E get( ); ? 獲得堆頂元素
E remove( ); ? 刪除堆頂元素(取出優(yōu)先級(jí)最高的數(shù)據(jù))
E replace(E element); ? 刪除堆頂元素的同時(shí)插入一個(gè)新元素

以大根堆為例
進(jìn)行各接口的實(shí)現(xiàn)解析

add
插入時(shí)雾家,我們首先將要插入的數(shù)據(jù)放在數(shù)組的尾部铃彰。但是這樣破壞了堆的特性,因此我們需要進(jìn)行調(diào)整芯咧,保證堆的特性
大根堆形成條件:任意節(jié)點(diǎn)的值大于子節(jié)點(diǎn)的值牙捉,堆頂節(jié)點(diǎn)的值最大
1、將插入的節(jié)點(diǎn)和父節(jié)點(diǎn)進(jìn)行比較敬飒,若插入節(jié)點(diǎn)值大于父節(jié)點(diǎn)的值(不符合大根堆形成條件)則 他們之間互換位置
2邪铲、若插入節(jié)點(diǎn)小于父節(jié)點(diǎn)的值(符合大根堆形成條件)或已經(jīng)達(dá)到堆頂,則 返回
3无拗、重復(fù)執(zhí)行以上步驟

public void add(E element){
    elementNotNullCheck(element);
    insureCapacity(size+1);
    elements[size++]=elements;
    siftUp(size-1);
}   
private void siftUp(int index){
    E element=elements[index];
    //index必須滿(mǎn)足有父節(jié)點(diǎn)
    while(index>0){
        //父節(jié)點(diǎn)索引Pindex=floor((index-1)/2
        int parentIndex=(index-1)>>1;
        //若插入節(jié)點(diǎn)的值小于父節(jié)點(diǎn)的值带到,符合大根堆構(gòu)造條件無(wú)需交換直接返回
        if(compare(elements[parentIndex],element)>=0) break;
        //不符合構(gòu)造條件,節(jié)點(diǎn)位置進(jìn)行互換后繼續(xù)下一輪循從當(dāng)前父節(jié)點(diǎn)向上重新尋找合適的位置
        temp=elements[parentIndex];
        elements[parentIndex]=elements[index];
        elements[index]=temp;
    
        //重新賦值英染,進(jìn)入下一輪循環(huán)揽惹,繼續(xù)向上與父節(jié)點(diǎn)進(jìn)行比較 
        index=parentIndex;  
    }
}

向上尋找合適位置的過(guò)程中被饿,添加節(jié)點(diǎn)經(jīng)過(guò)不斷地循環(huán)上濾在最終找到合適位置時(shí)才進(jìn)行插入,插入節(jié)點(diǎn)并不需要每一次都交換位置搪搏,之前一直是父節(jié)點(diǎn)的在進(jìn)行位置交換狭握,所以可以進(jìn)行優(yōu)化

private void siftUp(int index){
    E element=elements[index];
    //index必須滿(mǎn)足有父節(jié)點(diǎn)
    while(index>0){
        //父節(jié)點(diǎn)索引Pindex=floor((index-1)/2
        int parentIndex=(index-1)>>2;
        //若插入節(jié)點(diǎn)的值小于父節(jié)點(diǎn)的值,符合大根堆構(gòu)造條件無(wú)需交換直接返回
        if(compare(elements[parentIndex],element)>=0) break;

        //不符合構(gòu)造條件疯溺,父節(jié)點(diǎn)的位置下移至子節(jié)點(diǎn)
        elements[index]=elements[parentIndex];
        //重新賦值已當(dāng)前父節(jié)點(diǎn)為起點(diǎn)作為子節(jié)點(diǎn)進(jìn)入下一輪循環(huán)哥牍,繼續(xù)向上與新的父節(jié)點(diǎn)進(jìn)行比較
        index=parentIndex;
    }
    //循環(huán)結(jié)束直到找到正確的位置
    elements[index]=element;
}

remove
刪除元素(取出優(yōu)先級(jí)最高的數(shù)據(jù)),因?yàn)槿〕龆阎凶畲笾岛蟊仨氁WC不影響堆的構(gòu)造喝检,所以并不只是將堆頂元素直接賦值為null就完事了
大根堆形成條件:任意節(jié)點(diǎn)的值大于子節(jié)點(diǎn)的值嗅辣,所以堆頂節(jié)點(diǎn)為最大值
1、將最后一個(gè)索引節(jié)點(diǎn)的值賦給堆頂節(jié)點(diǎn)
2挠说、堆頂節(jié)點(diǎn)的值與子節(jié)點(diǎn)的值進(jìn)行比較澡谭,若堆頂節(jié)點(diǎn)值小于子節(jié)點(diǎn)的值(不符合大根堆形成條件),則他們之間互換位置
3损俭、重復(fù)執(zhí)行以上步驟

public E remove(){
    emptyCheck();

    int root=elements[0];
    elements[0]=elements[--size];
    elements[size]=null;
    siftDown(0);
    return root;
}
private void siftDown(int index){
    int e=elements[index];
    //非葉子節(jié)點(diǎn)的數(shù)量:floor(size/2)
    int critical=size>>2;
    
    //最后一個(gè)葉子節(jié)點(diǎn)的索引 = 非葉子節(jié)點(diǎn)的數(shù)量
    //保證index是非葉子節(jié)點(diǎn)
    while(index<critical){
        //左子節(jié)點(diǎn)索引
        int childIndex = (index << 1) + 1;
        
        //c右子節(jié)點(diǎn)索引:childIndex+1
        if(childIndex+1<size){
            //選出左右子節(jié)點(diǎn)中的最大值
            int child=math.max(elements[childIndex],elements[childIndex+1]);
        }
        //若堆頂節(jié)點(diǎn)值大于子節(jié)點(diǎn)的值蛙奖,符合條件,則返回
        if(compare(child,e)<=0) break;

        //位置互換
        // int temp=child;
        // child=elements[index];
        // elements[index]=temp;
        //堆頂節(jié)點(diǎn)不斷向下尋找合適位置插入的過(guò)程中杆兵,與子節(jié)點(diǎn)不斷地進(jìn)行位置互換
        elements[index]=elements[childIndex];
        index=childIndex;
    }
    //最終找到合適位置
    elements[index]=e;
}

批量建堆
外部接口調(diào)用

public void main(Stirng[] args){
    BinaryHeap<Integer> heap = new BinaryHeap<>();
    heap.add(2);
    heap.add(5);
    heap.add(1);
    heap.add(3);

    int[] data={2,5,1,3}
    BinaryHeap<Integer> heap = new BinaryHeap<>(data, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
    });
}

若想將一段指定的數(shù)據(jù)添加進(jìn)堆中雁仲,然后取出優(yōu)先值,怎么實(shí)現(xiàn)琐脏?
1攒砖、一個(gè)接一個(gè)的進(jìn)行插入
2、直接將數(shù)組扔進(jìn)堆中日裙,批量建堆(堆化)

heapify

public BinaryHeap(E[] elements, Comparator<E> comparator) {
    super(comparator);
    
    if (elements == null || elements.length == 0) {
        this.elements = (E[]) new Object[DEFAULT_CAPACITY];
    } else {
        size = elements.length;
        int capacity = Math.max(elements.length, DEFAULT_CAPACITY);
        this.elements = (E[]) new Object[capacity];
        for (int i = 0; i < elements.length; i++) {
            this.elements[i] = elements[i];
        }
        heapify();
    }
}

private void heapify() {
    // 自上而下的上濾
    // for (int i = 1; i < size; i++) {
    //  siftUp(i);
    // }
    
    // 自下而上的下濾
    for (int i = (size >> 1) - 1; i >= 0; i--) {
        siftDown(i);
    }
}

批量建堆的過(guò)程可以叫做堆化吹艇,堆化的兩種方式:
1、自上而下的上濾昂拂,上濾操作(葉子節(jié)點(diǎn)與父節(jié)點(diǎn)比較)
2受神、自下而上的下濾,下濾操作(父節(jié)點(diǎn)與葉子節(jié)點(diǎn)比較)
效率分析:
完全二叉樹(shù)的葉子節(jié)點(diǎn)數(shù)量(n/2格侯,n為總節(jié)點(diǎn)數(shù)量)為總結(jié)點(diǎn)的1/2鼻听,若采用方式一,則一半的節(jié)點(diǎn)(最后一層的葉子節(jié)點(diǎn))需要進(jìn)行上濾

所以采用方式2 效率相對(duì)更高

清空
此處最大堆的結(jié)構(gòu)利用了數(shù)組联四,所以與數(shù)組的清空方式一樣

public void clear() {
    for (int i = 0; i < size; i++) {
        elements[i] = null;
    }
    size = 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撑碴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碎连,更是在濱河造成了極大的恐慌灰羽,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異廉嚼,居然都是意外死亡玫镐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)怠噪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恐似,“玉大人,你說(shuō)我怎么就攤上這事傍念〗靡模” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵憋槐,是天一觀的道長(zhǎng)双藕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)阳仔,這世上最難降的妖魔是什么忧陪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮近范,結(jié)果婚禮上嘶摊,老公的妹妹穿的比我還像新娘。我一直安慰自己评矩,他們只是感情好叶堆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著斥杜,像睡著了一般虱颗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上果录,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天上枕,我揣著相機(jī)與錄音,去河邊找鬼弱恒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棋恼,可吹牛的內(nèi)容都是我干的返弹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼爪飘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼义起!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起师崎,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤默终,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體齐蔽,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡两疚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了含滴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诱渤。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谈况,靈堂內(nèi)的尸體忽然破棺而出勺美,到底是詐尸還是另有隱情,我是刑警寧澤碑韵,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布赡茸,位于F島的核電站,受9級(jí)特大地震影響祝闻,放射性物質(zhì)發(fā)生泄漏坛掠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一治筒、第九天 我趴在偏房一處隱蔽的房頂上張望屉栓。 院中可真熱鬧,春花似錦耸袜、人聲如沸友多。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)域滥。三九已至,卻和暖如春蜈抓,著一層夾襖步出監(jiān)牢的瞬間启绰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工沟使, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留委可,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓腊嗡,卻偏偏與公主長(zhǎng)得像着倾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子燕少,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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