優(yōu)先級(jí)隊(duì)列PriorityQueue源碼分析

優(yōu)先級(jí)隊(duì)列是一種抽象數(shù)據(jù)類(lèi)型。優(yōu)先隊(duì)列中的每個(gè)元素都有各自的優(yōu)先級(jí),優(yōu)先級(jí)最高的元素最先得到服務(wù);優(yōu)先級(jí)相同的元素按照其在優(yōu)先隊(duì)列中的順序得到服務(wù)。優(yōu)先隊(duì)列往往用堆(數(shù)據(jù)結(jié)構(gòu))來(lái)實(shí)現(xiàn)。

初級(jí)實(shí)現(xiàn)
有許多簡(jiǎn)單低效的實(shí)現(xiàn)。如用一個(gè)有序的數(shù)組;或使用無(wú)序數(shù)組,在每次取出時(shí)搜索全集合,這種方法插入的效率為O(1),但取出時(shí)效率為?O(n)。

典型實(shí)現(xiàn)
出于性能考慮,優(yōu)先隊(duì)列用堆 (數(shù)據(jù)結(jié)構(gòu))來(lái)實(shí)現(xiàn),具有O(log n)時(shí)間復(fù)雜度的插入元素性能,O(n)的初始化構(gòu)造的時(shí)間復(fù)雜度。如果使用自平衡二叉查找樹(shù),插入與刪除的時(shí)間復(fù)雜度為O(log n),構(gòu)造二叉樹(shù)的時(shí)間復(fù)雜度為O(n log n)。


Collection家族關(guān)系圖
源碼分析

繼承關(guān)系

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable 

無(wú)參構(gòu)造函數(shù)

    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
    
    transient Object[] queue;
    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;
    }

無(wú)參構(gòu)造調(diào)用了intComparator參數(shù)的構(gòu)造方法,可以看到PriorityQueue底層是一個(gè)Object[],默認(rèn)初始容量是11,比較器默認(rèn)為空,自然排序。

Collection參數(shù)構(gòu)造

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);
        }
    }

    private void initElementsFromCollection(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if (a.getClass() != Object[].class)
            a = Arrays.copyOf(a, a.length, Object[].class);
        int len = a.length;
        if (len == 1 || this.comparator != null)
            for (Object e : a)
                if (e == null)
                    throw new NullPointerException();
        this.queue = a;
        this.size = a.length;
    }

    private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
        if (c.getClass() == PriorityQueue.class) {
            this.queue = c.toArray();
            this.size = c.size();
        } else {
            initFromCollection(c);
        }
    }

    private void initFromCollection(Collection<? extends E> c) {
        initElementsFromCollection(c);
        heapify();
    }

Collection參數(shù)構(gòu)造分三種類(lèi)型處理

  • 1.SortedSet
    SortedSet是一個(gè)接口,實(shí)現(xiàn)類(lèi)是TreeSet,在這里可以認(rèn)為是有序的Set,在initElementsFromCollection方法中將原SortedSet中的元素按照原來(lái)的順序賦值給了自身的queue
  • 2.PriorityQueue
    如果是PriorityQueue,將按原來(lái)的元素順序賦值給自身的queue
  • 3 其他
    按照自然順序賦值,調(diào)用heapify()將數(shù)據(jù)調(diào)整為二叉堆

二叉堆是一種特殊的堆,是一棵完全二叉樹(shù)或者是近似完全二叉樹(shù),同時(shí)二叉堆還滿(mǎn)足堆的特性:父節(jié)點(diǎn)的鍵值總是保持固定的序關(guān)系于任何一個(gè)子節(jié)點(diǎn)的鍵值,且每個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)都是一個(gè)二叉堆。
當(dāng)父節(jié)點(diǎn)的鍵值總是大于或等于任何一個(gè)子節(jié)點(diǎn)的鍵值時(shí)為最大堆。 當(dāng)父節(jié)點(diǎn)的鍵值總是小于或等于任何一個(gè)子節(jié)點(diǎn)的鍵值時(shí)為最小堆。

二叉堆圖解

Comparator參數(shù)構(gòu)造

    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }

默認(rèn)數(shù)組長(zhǎng)度,將comparator比較器賦值


add(E e)和offer(E e)

    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 grow(int minCapacity) {
        int oldCapacity = queue.length;
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }

    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];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

add()調(diào)用offer(),兩個(gè)方法走相同邏輯

  • 1.判斷插入元素是否為空,不允許插入null
  • 2.修改次數(shù)++
  • 3.元素個(gè)數(shù)大于等于queue的容量就擴(kuò)容,擴(kuò)容規(guī)則:

原容量長(zhǎng)度小于64,則增加原容量長(zhǎng)度+2的長(zhǎng)度,即擴(kuò)容后的長(zhǎng)度等于原長(zhǎng)度的2倍+2;原容量長(zhǎng)度大于等于64,則新增原容量長(zhǎng)度的>>1,即新增原容量長(zhǎng)度的一半

  • 4.size+1,這里是size并非數(shù)組的長(zhǎng)度,而是元素個(gè)數(shù)
  • 5.如果是第1個(gè)添加的元素,就將第0個(gè)索引賦值,如果不是第一個(gè)添加的元素,將元素加入二叉堆“上移”。

上移siftUp(int k, E x) 方法在這里著重分析下,如果比較器comparator != null,則走siftUpUsingComparator(k, x)方法,否則走siftUpComparable(k, x),siftUpComparable(k, x)方法則需要元素實(shí)現(xiàn)Comparable接口進(jìn)行比較。siftUpUsingComparator(k, x)和siftUpComparable(k, x)上移邏輯是一樣的。

siftUpComparable(k, x)方法分析

  • k>0表示判斷k不是根的情況下,也就是元素x有父節(jié)點(diǎn)
  • 如果不是根節(jié)點(diǎn),通過(guò)(k - 1) >>> 1拿到父節(jié)點(diǎn),二叉堆獲取父節(jié)點(diǎn)位置公式(n-1)/2
  • 如果新增的元素k比其父節(jié)點(diǎn)e大,則不需要"上移",跳出循環(huán)結(jié)束,否則與父節(jié)點(diǎn)交換位置,并將k指向父節(jié)點(diǎn)位置,進(jìn)入下一層循環(huán)
  • 找到新增元素x的合適位置k之后進(jìn)行賦值

poll()

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[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) {
            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;
    }

poll()出隊(duì)對(duì)于被刪除元素來(lái)說(shuō),是下移。poll()主要邏輯

  • 1.隊(duì)列為空,返回null
  • 2.元素個(gè)數(shù)-1
  • 3.修改次數(shù)++
  • 4.取出隊(duì)首元素result,隊(duì)尾元素x
  • 5.當(dāng)隊(duì)尾索引不等于0,也就是隊(duì)列中多于1個(gè)元素,下移,siftDown(int k, E x)邏輯:

下移siftDown(int k, E x) 方法在這里著重分析下,如果比較器comparator != null,則走siftDownUsingComparator(k, x)方法,否則走siftDownComparable(k, x),siftDownComparable(k, x)方法則需要元素實(shí)現(xiàn)Comparable接口進(jìn)行比較。siftDownUsingComparator(k, x)和siftDownComparable(k, x)下移邏輯是一樣的。

siftDownUsingComparator(int k, E x)分析

參數(shù)說(shuō)明:k表示移除元素下標(biāo)0,x表示隊(duì)尾元素

  • int half = size >>> 1,無(wú)符號(hào)右移1位,等同于int half = size / 2 , 得到葉子節(jié)點(diǎn)索引half
  • 循環(huán)k < half,表示,下移最多下移到葉子節(jié)點(diǎn)。
  • int child = (k << 1) + 1,獲取左子節(jié)點(diǎn)索引,等同于int child = k * 2 + 1,右子節(jié)點(diǎn)索引等于左子節(jié)點(diǎn)索引+1
  • Object c = queue[child],c暫時(shí)表示左子節(jié)點(diǎn)的元素
  • 當(dāng)存在右子節(jié)點(diǎn),并且通過(guò)比較器比較,c賦值為右子節(jié)點(diǎn)元素
  • 如果隊(duì)尾元素x比k索引的元素左右子節(jié)點(diǎn)都要小,則不需"下移",結(jié)束循環(huán)
  • 將queue索引k賦值為子節(jié)點(diǎn)的元素
  • k = child,將k賦值為子節(jié)點(diǎn)索引,進(jìn)入下一層循環(huán)
  • 結(jié)束循環(huán)后將x 插入合適的位置

總結(jié)

  • 1.PriorityQueue基于二叉堆實(shí)現(xiàn)
  • 2.非線(xiàn)程安全
  • 3.元素不能為null
  • 4.PriorityQueue不是完全有序的,堆頂存儲(chǔ)著最高優(yōu)先級(jí)的元素
  • 5.插入和移除元素時(shí),按照優(yōu)先級(jí)調(diào)整根節(jié)點(diǎn)元素
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。