<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,即優先隊列。優先隊列的作用是能保證每次取出的元素都是隊列中權值最小的(Java的優先隊列每次取最小元素,C++的優先隊列每次取最大元素)。這里牽涉到了大小關系,元素大小的評判可以通過元素本身的自然順序(natural ordering),也可以通過構造時傳入的比較器Comparator,類似于C++的仿函數)。

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

    PriorityQueue_base.png

    上圖中我們給每個元素按照層序遍歷的方式進行了編號,如果你足夠細心,會發現父節點和子節點的編號是有聯系的,更確切的說父子節點的編號之間有如下關系:

    leftNo = parentNo*2+1

    rightNo = parentNo*2+2

    parentNo = (nodeNo-1)/2

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

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

    方法剖析

    add()和offer()

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

    PriorityQueue_offer.png

    新加入的元素可能會破壞小頂堆的性質,因此需要進行必要的調整。

    //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);//自動擴容
        size = i + 1;
        if (i == 0)//隊列原來為空,這是插入的第一個元素
            queue[0] = e;
        else
            siftUp(i, e);//調整
        return true;
    }
    上述代碼中,擴容函數grow()類似于ArrayList里的grow()函數,就是再申請一個更大的數組,并將原數組的元素復制過去,這里不再贅述。需要注意的是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)//調用比較器的比較方法
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }
    新加入的元素x可能會破壞小頂堆的性質,因此需要進行調整。調整的過程為:k指定的位置開始,將x逐層與當前點的parent進行比較并交換,直到滿足x >= queue[parent]為止。注意這里的比較可以是元素的自然順序,也可以是依靠比較器的順序。

    element()和peek()

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

    PriorityQueue_peek.png

    代碼也就非常簡潔:

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

    remove()和poll()

    remove()poll()方法的語義也完全相同,都是獲取并刪除隊首元素,區別是當方法失敗時前者拋出異常,后者返回null。由于刪除操作會改變隊列的結構,為維護小頂堆的性質,需要進行必要的調整。

    PriorityQueue_poll.png
    代碼如下:

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];//0下標處的那個元素就是最小的那個
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);//調整
        return result;
    }
    上述代碼首先記錄0下標處的元素,并用最后一個元素替換0下標位置的元素,之后調用siftDown()方法對堆進行調整,最后返回原來0下標處的那個元素(也就是最小的那個元素)。重點是siftDown(int k, E x)方法,該方法的作用是k指定的位置開始,將x逐層向下與當前點的左右孩子中較小的那個交換,直到x小于或等于左右孩子中的任何一個為止。
    //siftDown()
    private void siftDown(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            //首先找到左右孩子中較小的那個,記錄到c里,并用child記錄其下標
            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接口內的方法,而是Collection接口的方法。由于刪除操作會改變隊列結構,所以要進行調整;又由于刪除元素的位置可能是任意的,所以調整過程比其它函數稍加繁瑣。具體來說,remove(Object o)可以分為2種情況:1. 刪除的是最后一個元素。直接刪除即可,不需要調整。2. 刪除的不是最后一個元素。此處不再贅述。

    PriorityQueue_remove2.png

    具體代碼如下:

    //remove(Object o)
    public boolean remove(Object o) {
        //通過遍歷數組的方式找到第一個滿足o.equals(queue[i])元素的下標
        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 | 有機綠茶
    剖析得很詳細,尤其是那些圖片,很形象,學習啦!  回復  更多評論
      
    # re: Java PriorityQueue源碼剖析 2016-05-16 07:27 | CarpenterLee
    @有機綠茶
    只有代碼太枯燥了,附圖更生動些。  回復  更多評論
      

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


    網站導航:
     
    主站蜘蛛池模板: 亚洲精品亚洲人成在线播放| 国产免费内射又粗又爽密桃视频| 久99久无码精品视频免费播放| 97碰公开在线观看免费视频| 亚洲日韩精品一区二区三区| 青草青草视频2免费观看| 综合在线免费视频| 亚洲福利视频一区二区三区| 免费观看在线禁片| 亚洲AV午夜成人片| 最新国产乱人伦偷精品免费网站 | 女人体1963午夜免费视频| 免费a级毛片在线观看| 亚洲日韩精品一区二区三区| 亚洲精品偷拍无码不卡av| 国产午夜亚洲精品不卡电影| 日本人的色道免费网站| 亚洲日韩小电影在线观看| 色九月亚洲综合网| 久久精品免费一区二区喷潮| 亚洲国产精品无码一线岛国 | 亚洲精品无码日韩国产不卡?V| 亚洲色中文字幕在线播放| 一级毛片**不卡免费播| 亚洲精品自在在线观看| 男人和女人高潮免费网站| 免费视频中文字幕| 亚洲欧美日韩久久精品| 青草草色A免费观看在线| 亚洲男人天堂2017| 永久在线免费观看| 亚洲国产精品综合一区在线| 18女人腿打开无遮掩免费| 亚洲欧洲在线播放| 日韩中文字幕精品免费一区| 亚洲人成色4444在线观看| 免费一级肉体全黄毛片| free哆拍拍免费永久视频| 亚洲成年轻人电影网站www | 久久久久国产精品免费网站| 国产亚洲精品国产|