<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-13  評論-22  文章-0  trackbacks-0


    PriorityQueue

    本文github地址

    總體介紹

    前面以Java ArrayDeque為例講解了StackQueue,其實還有一種特殊的隊列叫做PriorityQueue,即優(yōu)先隊列。優(yōu)先隊列的作用是能保證每次取出的元素都是隊列中權(quán)值最小的(Java的優(yōu)先隊列每次取最小元素,C++的優(yōu)先隊列每次取最大元素)。這里牽涉到了大小關(guān)系,元素大小的評判可以通過元素本身的自然順序(natural ordering),也可以通過構(gòu)造時傳入的比較器Comparator,類似于C++的仿函數(shù))。

    Java中PriorityQueue實現(xiàn)了Queue接口,不允許放入null元素;其通過堆實現(xiàn),具體說是通過完全二叉樹(complete binary tree)實現(xiàn)的小頂堆(任意一個非葉子節(jié)點的權(quán)值,都不大于其左右子節(jié)點的權(quán)值),也就意味著可以通過數(shù)組來作為PriorityQueue的底層實現(xiàn)。

    PriorityQueue_base.png

    上圖中我們給每個元素按照層序遍歷的方式進(jìn)行了編號,如果你足夠細(xì)心,會發(fā)現(xiàn)父節(jié)點和子節(jié)點的編號是有聯(lián)系的,更確切的說父子節(jié)點的編號之間有如下關(guān)系:

    leftNo = parentNo*2+1

    rightNo = parentNo*2+2

    parentNo = (nodeNo-1)/2

    通過上述三個公式,可以輕易計算出某個節(jié)點的父節(jié)點以及子節(jié)點的下標(biāo)。這也就是為什么可以直接用數(shù)組來存儲堆的原因。

    PriorityQueuepeek()element操作是常數(shù)時間,add(), offer(), 無參數(shù)的remove()以及poll()方法的時間復(fù)雜度都是log(N)。

    方法剖析

    add()和offer()

    add(E e)offer(E e)的語義相同,都是向優(yōu)先隊列中插入元素,只是Queue接口規(guī)定二者對插入失敗時的處理不同,前者在插入失敗時拋出異常,后則則會返回false。對于PriorityQueue這兩個方法其實沒什么差別。

    PriorityQueue_offer.png

    新加入的元素可能會破壞小頂堆的性質(zhì),因此需要進(jìn)行必要的調(diào)整。

    //offer(E e)
    public boolean offer(E e) {
        if (e == null)//不允許放入null元素
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);//自動擴(kuò)容
        size = i + 1;
        if (i == 0)//隊列原來為空,這是插入的第一個元素
            queue[0] = e;
        else
            siftUp(i, e);//調(diào)整
        return true;
    }
    上述代碼中,擴(kuò)容函數(shù)grow()類似于ArrayList里的grow()函數(shù),就是再申請一個更大的數(shù)組,并將原數(shù)組的元素復(fù)制過去,這里不再贅述。需要注意的是siftUp(int k, E x)方法,該方法用于插入元素x并維持堆的特性。
    //siftUp()
    private void siftUp(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;//parentNo = (nodeNo-1)/2
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)//調(diào)用比較器的比較方法
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }
    新加入的元素x可能會破壞小頂堆的性質(zhì),因此需要進(jìn)行調(diào)整。調(diào)整的過程為:k指定的位置開始,將x逐層與當(dāng)前點的parent進(jìn)行比較并交換,直到滿足x >= queue[parent]為止。注意這里的比較可以是元素的自然順序,也可以是依靠比較器的順序。

    element()和peek()

    element()peek()的語義完全相同,都是獲取但不刪除隊首元素,也就是隊列中權(quán)值最小的那個元素,二者唯一的區(qū)別是當(dāng)方法失敗時前者拋出異常,后者返回null。根據(jù)小頂堆的性質(zhì),堆頂那個元素就是全局最小的那個;由于堆用數(shù)組表示,根據(jù)下標(biāo)關(guān)系,0下標(biāo)處的那個元素既是堆頂元素。所以直接返回數(shù)組0下標(biāo)處的那個元素即可。

    PriorityQueue_peek.png

    代碼也就非常簡潔:

    //peek()
    public E peek() {
        if (size == 0)
            return null;
        return (E) queue[0];//0下標(biāo)處的那個元素就是最小的那個
    }

    remove()和poll()

    remove()poll()方法的語義也完全相同,都是獲取并刪除隊首元素,區(qū)別是當(dāng)方法失敗時前者拋出異常,后者返回null。由于刪除操作會改變隊列的結(jié)構(gòu),為維護(hù)小頂堆的性質(zhì),需要進(jìn)行必要的調(diào)整。

    PriorityQueue_poll.png
    代碼如下:

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];//0下標(biāo)處的那個元素就是最小的那個
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);//調(diào)整
        return result;
    }
    上述代碼首先記錄0下標(biāo)處的元素,并用最后一個元素替換0下標(biāo)位置的元素,之后調(diào)用siftDown()方法對堆進(jìn)行調(diào)整,最后返回原來0下標(biāo)處的那個元素(也就是最小的那個元素)。重點是siftDown(int k, E x)方法,該方法的作用是k指定的位置開始,將x逐層向下與當(dāng)前點的左右孩子中較小的那個交換,直到x小于或等于左右孩子中的任何一個為止。
    //siftDown()
    private void siftDown(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            //首先找到左右孩子中較小的那個,記錄到c里,并用child記錄其下標(biāo)
            int child = (k << 1) + 1;//leftNo = parentNo*2+1
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;//然后用c取代原來的值
            k = child;
        }
        queue[k] = x;
    }

    remove(Object o)

    remove(Object o)方法用于刪除隊列中跟o相等的某一個元素(如果有多個相等,只刪除一個),該方法不是Queue接口內(nèi)的方法,而是Collection接口的方法。由于刪除操作會改變隊列結(jié)構(gòu),所以要進(jìn)行調(diào)整;又由于刪除元素的位置可能是任意的,所以調(diào)整過程比其它函數(shù)稍加繁瑣。具體來說,remove(Object o)可以分為2種情況:1. 刪除的是最后一個元素。直接刪除即可,不需要調(diào)整。2. 刪除的不是最后一個元素。此處不再贅述。

    PriorityQueue_remove2.png

    具體代碼如下:

    //remove(Object o)
    public boolean remove(Object o) {
        //通過遍歷數(shù)組的方式找到第一個滿足o.equals(queue[i])元素的下標(biāo)
        int i = indexOf(o);
        if (i == -1)
            return false;
        int s = --size;
        if (s == i) //情況1
            queue[i] = null;
        else {
            E moved = (E) queue[s];
            queue[s] = null;
            siftDown(i, moved);//情況2
            
        }
        return true;
    }

    posted on 2016-05-12 21:22 CarpenterLee 閱讀(1447) 評論(2)  編輯  收藏

    評論:
    # re: Java PriorityQueue源碼剖析 2016-05-15 18:36 | 有機綠茶
    剖析得很詳細(xì),尤其是那些圖片,很形象,學(xué)習(xí)啦!  回復(fù)  更多評論
      
    # re: Java PriorityQueue源碼剖析 2016-05-16 07:27 | CarpenterLee
    @有機綠茶
    只有代碼太枯燥了,附圖更生動些。  回復(fù)  更多評論
      

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 成人免费无遮挡无码黄漫视频| 免费大黄网站在线观看| 一本色道久久综合亚洲精品蜜桃冫| 国产大片线上免费看| 免费人成在线观看视频高潮| 亚洲免费视频观看| 免费一级大黄特色大片| 午夜影院免费观看| 日韩亚洲翔田千里在线| 亚洲黄色三级网站| 亚洲国产精品无码久久98| 成年美女黄网站18禁免费| 中文字幕免费播放| 亚洲中文字幕久久精品无码A| 亚洲色成人WWW永久网站| 最近中文字幕mv免费高清视频7 | 亚洲av第一网站久章草| 亚洲av无码av制服另类专区| 国产一级做a爱免费视频| 24小时在线免费视频| h视频免费高清在线观看| 亚洲情A成黄在线观看动漫软件 | 久久亚洲国产最新网站| 亚洲精品成人片在线播放| 在线看片人成视频免费无遮挡| 花蝴蝶免费视频在线观看高清版| 亚洲精品无码久久久久牙蜜区| 18gay台湾男同亚洲男同| 亚洲精品无码专区久久同性男| 男男AV纯肉无码免费播放无码| 国内少妇偷人精品视频免费| 亚洲AV成人精品一区二区三区| 亚洲伊人色一综合网| 亚洲AV无码专区国产乱码电影| 国产免费人人看大香伊| 国产免费AV片在线播放唯爱网| 暖暖免费在线中文日本| 99在线视频免费观看| 一级毛片免费播放试看60分钟| 鲁死你资源站亚洲av| 精品亚洲456在线播放|