吃透Java集合系列七:PriorityQueue

文章首發(fā)csdn博客地址:https://blog.csdn.net/u013277209?viewmode=contents

一:PriorityQueue實現(xiàn)方式

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


在這里插入圖片描述

二:源碼分析

重要變量以及構(gòu)造函數(shù)

  • 根據(jù)堆的特性,存儲結(jié)構(gòu)肯定是數(shù)組详瑞。
  • 支持不同優(yōu)先級掂林,肯定有比較器,也就是說支持自定義排序和順序排序蛤虐。
  • PriorityQueue的構(gòu)造函數(shù)有很多党饮,主要參數(shù)是容量和比較器。
public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
//默認容量11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//堆的存儲結(jié)構(gòu)驳庭,存儲元素
transient Object[] queue;
//當前存儲的元素數(shù)量
private int size = 0;
//自定義比較器
private final Comparator<? super E> comparator;
public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

擴容機制

每次擴容刑顺,當前容量小于64時就擴容為原來的2倍+2,當前容量大于等于64時擴容為原來的1.5倍饲常。

    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

add()和offer()

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


在這里插入圖片描述
    public boolean add(E e) {
        return offer(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);//調(diào)整
        return true;
    }
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
    private void siftUpUsingComparator(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;              //如果當前值大于等于父節(jié)點則跳出循環(huán)
            queue[k] = e;//如果當前值小于父節(jié)點,則父節(jié)點與當前值交換位置
            k = parent;//當前節(jié)點位置調(diào)整為原父節(jié)點位置离陶,進入下一次循環(huán)
        }
        queue[k] = x;
    }
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) 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;
    }
    

新加入的元素x可能會破壞小頂堆的性質(zhì)稼虎,因此需要進行調(diào)整。調(diào)整的過程為:從k指定的位置開始招刨,將x逐層與當前點的parent進行比較并交換霎俩,直到滿足x >= queue[parent]為止。注意這里的比較可以是元素的自然順序沉眶,也可以是依靠比較器的順序打却。

element()和peek()

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

    public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
    public E peek() {
        return (size == 0) ? null : (E) queue[0];
    }

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

在這里插入圖片描述

    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;
    }
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            //首先找到左右孩子中較小的那個坯沪,記錄到c里,并用child記錄其下標
            int child = (k << 1) + 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;
    }
    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

上述代碼首先記錄0下標處的元素擒滑,并用最后一個元素替換0下標位置的元素腐晾,之后調(diào)用siftDown()方法對堆進行調(diào)整,最后返回原來0下標處的那個元素(也就是最小的那個元素)丐一。重點是siftDown(int k, E x)方法藻糖,該方法的作用是從k指定的位置開始,將x逐層向下與當前點的左右孩子中較小的那個交換库车,直到x小于或等于左右孩子中的任何一個為止巨柒。

remove(Object o)

remove(Object o)方法用于刪除隊列中跟o相等的某一個元素(如果有多個相等,只刪除一個)柠衍,該方法不是Queue接口內(nèi)的方法洋满,而是Collection接口的方法。由于刪除操作會改變隊列結(jié)構(gòu)珍坊,所以要進行調(diào)整牺勾;又由于刪除元素的位置可能是任意的,所以調(diào)整過程比其它函數(shù)稍加繁瑣垫蛆。具體來說禽最,remove(Object o)可以分為2種情況:1. 刪除的是最后一個元素腺怯。直接刪除即可袱饭,不需要調(diào)整。2. 刪除的不是最后一個元素呛占,從刪除點開始以最后一個元素為參照調(diào)用一次siftDown()即可虑乖。此處不再贅述。


在這里插入圖片描述
    public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
    }
    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        if (s == i) // removed last element
            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)容合作請聯(lián)系作者
  • 序言:七十年代末仅叫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子糙捺,更是在濱河造成了極大的恐慌诫咱,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洪灯,死亡現(xiàn)場離奇詭異坎缭,居然都是意外死亡,警方通過查閱死者的電腦和手機签钩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門掏呼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铅檩,你說我怎么就攤上這事憎夷。” “怎么了昧旨?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵拾给,是天一觀的道長。 經(jīng)常有香客問我兔沃,道長鸣戴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任粘拾,我火速辦了婚禮窄锅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缰雇。我一直安慰自己入偷,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布械哟。 她就那樣靜靜地躺著疏之,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暇咆。 梳的紋絲不亂的頭發(fā)上锋爪,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音爸业,去河邊找鬼其骄。 笑死,一個胖子當著我的面吹牛扯旷,可吹牛的內(nèi)容都是我干的拯爽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼钧忽,長吁一口氣:“原來是場噩夢啊……” “哼毯炮!你這毒婦竟也來了逼肯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤桃煎,失蹤者是張志新(化名)和其女友劉穎篮幢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體为迈,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡洲拇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了曲尸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赋续。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖另患,靈堂內(nèi)的尸體忽然破棺而出纽乱,到底是詐尸還是另有隱情,我是刑警寧澤昆箕,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布鸦列,位于F島的核電站,受9級特大地震影響鹏倘,放射性物質(zhì)發(fā)生泄漏薯嗤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一纤泵、第九天 我趴在偏房一處隱蔽的房頂上張望骆姐。 院中可真熱鬧,春花似錦捏题、人聲如沸玻褪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽带射。三九已至,卻和暖如春循狰,著一層夾襖步出監(jiān)牢的瞬間窟社,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工绪钥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灿里,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓昧识,卻偏偏與公主長得像钠四,于是被迫代替她去往敵國和親盗扒。 傳聞我的和親對象是個殘疾皇子跪楞,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

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

  • 時間來匆匆去匆匆缀去!轉(zhuǎn)眼間我以成了三十三歲的中年人,可是現(xiàn)實卻十分殘酷甸祭,每天都要起早貪黑的工作缕碎!也正如此我并...
    靈追風少年閱讀 238評論 0 0
  • (文章轉(zhuǎn)自微信公眾號:奇喵校園) 點擊閱讀原文 收聽音頻。2016-英語一大作文范文 ①As it is viv...
    奇喵學(xué)院閱讀 273評論 0 1
  • 能代表意大利的建筑物池户,大家都想到了哪個建筑咏雌?有些人可能想到了比薩斜塔,而其他的人可能想到了羅馬斗獸場校焦。 羅馬斗獸場...
    LilyLiu_305d閱讀 833評論 0 8
  • 《過春天》一部簡單赊抖、深入人心的film 講述一個香港戶口住在深圳的女中學(xué)生,為了賺錢和好友去日本寨典,走上非法代購之路...
    abc307閱讀 115評論 0 0
  • “丁零零……”凌晨三點氛雪,鬧鐘清脆的鈴聲把我從睡夢中叫醒。我一骨碌爬起來耸成,飛快地穿好衣服报亩。你也許會問:“怎么起...
    Superme丶蘇柒夏閱讀 445評論 0 0