Java集合(四) —— PriorityQueue源碼分析

Java集合(一) —— Collection源碼分析
Java集合(二) —— ArrayList源碼分析
Java集合(三) —— LinkedList源碼分析
Java集合(四) —— PriorityQueue源碼分析
Java集合(五) —— HashSet源碼分析
Java集合(六) —— LinkedHashSet源碼分析
Java集合(七) —— TreeSet源碼分析
Java集合(八) —— HashMap源碼分析
Java集合(九) —— LinkedHashMap源碼分析
Java集合(十) —— TreeMap源碼分析

1.PriorityQueue繼承關系圖

PriorityQueue.png

2數據結構

// 數組
transient Object[] queue;

1.優(yōu)先隊列并不是按照FIFO進行數據操作的,其數據具有優(yōu)先級刹帕,優(yōu)先級高的元素先出隊吵血。優(yōu)先隊列是基于小頂堆(堆排序的一種)原理實現(xiàn)的。
2.堆:堆是一個樹型結構偷溺,是一棵完全二叉樹蹋辅。完全二叉樹是一層一層按照進入的順序排列的。按照這個特性可以使用數組按照完全二叉樹實現(xiàn)堆挫掏。


1469176-20190329000534658-1905270280.png

3.堆排序:利用堆這種數據結構所設計的一種排序算法侦另。

  • 大頂堆:根節(jié)點的值最大。子節(jié)點一定小于或等于父節(jié)點尉共。
  • 小頂堆:根節(jié)點的值最小褒傅。子節(jié)點一定大于或等于父節(jié)點。(Java中的優(yōu)先隊列(PriorityQueue)是基于小頂堆實現(xiàn)的袄友。)

4.堆排序思想:

  • 構建初始堆殿托,將待排序序列構成一個大頂堆或小頂堆。
  • 將堆頂元素與堆尾元素交換剧蚣,并斷開(從待排序列中移除)堆尾元素支竹。
  • 重復構建堆旋廷。
  • 重復2~3,直到待排序列只剩一個元素(堆頂元素)唾戚。

3.性能分析

優(yōu)先隊列初始化堆時柳洋,其時間復雜度為O(n);(怎樣才是一個初始化好的堆叹坦?要求子節(jié)點不大于/不小于父節(jié)點熊镣,但是不要求是完全排好序的)
入隊和出隊時只是調整堆結構,并不需要將所有元素排序募书,其時間復雜度為O(log2n)(具體計算另行百度)绪囱。

4.源碼分析

1.成員變量

// 數組默認容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 數據結構為數組
transient Object[] queue;
// 數組大小
private int size = 0;
// 比較器
private final Comparator<? super E> comparator;
// 修改次數
transient int modCount = 0; 

2.構造方法

public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

// 指定初始容量
public PriorityQueue(int initialCapacity) {
    this(initialCapacity, null);
}

// 指定比較器
public PriorityQueue(Comparator<? super E> comparator) {
    this(DEFAULT_INITIAL_CAPACITY, comparator);
}

// 指定初始容量和比較器
public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) {
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

// 使用集合構建優(yōu)先隊列
@SuppressWarnings("unchecked")
public PriorityQueue(Collection<? extends E> c) {
    if (c instanceof SortedSet<?>) {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
        initElementsFromCollection(ss);
    }
    else if (c instanceof PriorityQueue<?>) {
        PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        initFromPriorityQueue(pq);
    }
    else {
        this.comparator = null;
        initFromCollection(c);
    }
}

@SuppressWarnings("unchecked")
public PriorityQueue(PriorityQueue<? extends E> c) {
    this.comparator = (Comparator<? super E>) c.comparator();
    initFromPriorityQueue(c);
}

@SuppressWarnings("unchecked")
public PriorityQueue(SortedSet<? extends E> c) {
    this.comparator = (Comparator<? super E>) c.comparator();
    initElementsFromCollection(c);
}

3.常用方法

  • 新增元素
public boolean add(E e) {
    return offer(e);
}

public boolean offer(E e) {
    if (e == 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;
}

private void siftUp(int k, E x) {
    if (comparator != null)
        // 使用自定義的比較器調整隊列
        siftUpUsingComparator(k, x);
    else
        // 使用默認的比較方法調整隊列
        siftUpComparable(k, x);
}

@SuppressWarnings("unchecked")
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];
        // 新增元素與父節(jié)點元素比較(默認小頂堆莹捡,最小元素為根節(jié)點)
        if (key.compareTo((E) e) >= 0)
            // 新增元素大于父節(jié)點元素鬼吵,則跳出循環(huán),不用調整堆的結構
            break;
        // 否則需要調整堆的結構篮赢,循環(huán)調整元素的位置
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

關于擴容

private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // 擴容機制齿椅,容量小于64時,新容量 = 舊容量*2+2启泣;大于64時涣脚,新容量 = 舊容量 + 舊容量 >> 1
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 數組復制
    queue = Arrays.copyOf(queue, newCapacity);
}

2.移除隊列頭元素

/**
 * 移除隊列頭元素,內部調用了poll方法寥茫,如果隊列為空則拋出異常
 */
public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
/**
 * 移除隊列頭元素遣蚀,如果隊列為空,則返回null
 */
@SuppressWarnings("unchecked")
public E poll() {
    if (size == 0)
        return null;
    // 數組大小-1
    int s = --size;
    modCount++;
    // 獲取隊列第一個元素
    E result = (E) queue[0];
    // 獲取隊列最后一個元素
    E x = (E) queue[s];
    // 將數組最后一個元素置為null
    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);
}

@SuppressWarnings("unchecked")
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; // 左子樹
        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;
}

@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        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;
        k = child;
    }
    queue[k] = x;
}

3.獲取隊列頭元素

/**
 * 獲取隊列頭元素芭梯,內部調用了peek方法,如果隊列為空則拋出異常
 */
public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

/**
 * 獲取隊列頭元素弄喘,如果隊列為空則返回null
 */
public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

5.總結

  • PriorityQueue優(yōu)先隊列玖喘,不適合一些經常出隊入隊的場景,但是他的優(yōu)先級特性非常適合一些對順序有要求的數據處理場景蘑志。
  • 堆(完全二叉樹)的數據結構不難理解芒涡,但是在優(yōu)先隊列中,出隊和入隊都可能引起堆結構的調整(實際上就是數組元素位置的調整)卖漫,調整算法有點晦澀難懂,有興趣的朋友可以好好了解該算法的實現(xiàn)赠群。
  • 優(yōu)先隊列在工作中的場景比較有限(至少我工作中沒有使用過)羊始,不喜歡的可以略過。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末查描,一起剝皮案震驚了整個濱河市突委,隨后出現(xiàn)的幾起案子柏卤,更是在濱河造成了極大的恐慌,老刑警劉巖匀油,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缘缚,死亡現(xiàn)場離奇詭異,居然都是意外死亡敌蚜,警方通過查閱死者的電腦和手機桥滨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弛车,“玉大人齐媒,你說我怎么就攤上這事》柞耍” “怎么了喻括?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贫奠。 經常有香客問我唬血,道長,這世上最難降的妖魔是什么唤崭? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任拷恨,我火速辦了婚禮,結果婚禮上浩姥,老公的妹妹穿的比我還像新娘挑随。我一直安慰自己,他們只是感情好勒叠,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布兜挨。 她就那樣靜靜地躺著,像睡著了一般眯分。 火紅的嫁衣襯著肌膚如雪拌汇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天弊决,我揣著相機與錄音噪舀,去河邊找鬼。 笑死飘诗,一個胖子當著我的面吹牛与倡,可吹牛的內容都是我干的。 我是一名探鬼主播昆稿,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼纺座,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了溉潭?” 一聲冷哼從身側響起净响,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤少欺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后馋贤,有當地人在樹林里發(fā)現(xiàn)了一具尸體赞别,經...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年配乓,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿滔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡扰付,死狀恐怖堤撵,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情羽莺,我是刑警寧澤实昨,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站盐固,受9級特大地震影響荒给,放射性物質發(fā)生泄漏。R本人自食惡果不足惜刁卜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一志电、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛔趴,春花似錦挑辆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至箫荡,卻和暖如春魁亦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羔挡。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工洁奈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绞灼。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓利术,卻偏偏與公主長得像,于是被迫代替她去往敵國和親低矮。 傳聞我的和親對象是個殘疾皇子印叁,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內容