java基本数据类型集合篇:PriorityQueue

在刷leecode的23题MergekSortedLists时,看到一种解法是使用PriorityQueue来解决的,官方答案中只有python的代码,尝试了之后发现java中也有PriorityQueue类,因此记录一下对这个类的分析。

PriorityQueue类位于java.util包,方法也不多,是一个基于优先级堆的无界优先级队列,队列数据的排序依据于元素自身的比较器或者是在PriorityQueue构造时传入一个比较器。说到底就是一个有序的队列。
java基本数据类型集合篇:PriorityQueue_第1张图片
PriorityQueue继承自抽象队列AbstractQueue并实现了Serializable接口。

1

首先看一下PriorityQueue类的几个构造函数

    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) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

创建一个大小为initialCapacity的Object数组,如果未指明initialCapacity,默认大小为11。如果有comparator则将comparator对象赋给PriorityQueue对象的comparator属性。

还有一个构造函数是以一个现有的集合类来构造一个PriorityQueue对象。

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

如果原集合是一个有序集合,那么将其自带的comparator对象赋给PriorityQueue对象的comparator属性,然后调用initElementsFromCollection方法

private void initElementsFromCollection(Collection<? extends E> c) {
        Object[] a = c.toArray();
        // If c.toArray incorrectly doesn't return Object[], copy it.
        if (a.getClass() != Object[].class)
            a = Arrays.copyOf(a, a.length, Object[].class);
        int len = a.length;
        if (len == 1 || this.comparator != null)
            for (int i = 0; i < len; i++)
                if (a[i] == null)
                    throw new NullPointerException();
        this.queue = a;
        this.size = a.length;
    }

将集合转换为一个Object数组,赋值给PriorityQueue对象的queue属性,并初始化PriorityQueue对象的大小。
如果原集合是一个PriorityQueue对象,那么将其自带的comparator对象赋给新的PriorityQueue对象的comparator属性,然后调用initFromPriorityQueue方法

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

如果构造函数的参数是一个PriorityQueue对象,那么将PriorityQueue对象转化为一个Object数组赋值给PriorityQueue对象的queue属性,并初始化PriorityQueue对象的大小;如果参数是PriorityQueue的子类对象,那么会调用initFromCollection函数进行初始化。
此处要注意的是instanceof判断中 子类 instanceof 父类 = true,子类.getClass() == 父类.class = false。
因此可见除了PriorityQueue类和实现了SortedSet接口的类将都由initFromCollection函数进行初始化。

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

首先调用函数initElementsFromCollection初始化PriorityQueue的queue和size属性,然后调用heapify函数将queue堆排序,完成PriorityQueue对象的构造。

2

接下来我们看一下PriorityQueue的几个操作函数。

2.1

由于addoffer两个函数是一种实现,所以在一起说

	/**
     * Inserts the specified element into this priority queue.
     *
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    public boolean add(E e) {
        return offer(e);
    }

    /**
     * Inserts the specified element into this priority queue.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    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;
    }

可以看到插入新元素时,会先将修改计数modCount加一,然后检查queue数组的大小是否够用,如果不够将会调用grow函数扩大空间。

	/**
     * Increases the capacity of the array.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

当queue数组原来的大小小于64时,数组大小将会翻倍,否则会增大50%,如果超过最大值会抛出OutOfMemoryError。当原数组queue非空,那么将调用siftUp函数将新插入的值存放到合适的位置,使得queue数组保持最小堆状态。

2.2

接下来是remove函数,这个函数会将第一个符合的对象移除。

	/**
     * Removes a single instance of the specified element from this queue,
     * if it is present.  More formally, removes an element {@code e} such
     * that {@code o.equals(e)}, if this queue contains one or more such
     * elements.  Returns {@code true} if and only if this queue contained
     * the specified element (or equivalently, if this queue changed as a
     * result of the call).
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if this queue changed as a result of the call
     */
    public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
    }

    /**
     * Version of remove using reference equality, not equals.
     * Needed by iterator.remove.
     *
     * @param o element to be removed from this queue, if present
     * @return {@code true} if removed
     */
    boolean removeEq(Object o) {
        for (int i = 0; i < size; i++) {
            if (o == queue[i]) {
                removeAt(i);
                return true;
            }
        }
        return false;
    }

remove函数和removeEq函数的区别在于使用==还是.equals()来判断相同。具体的移除逻辑在removeAt函数中

	/**
     * Removes the ith element from queue.
     *
     * Normally this method leaves the elements at up to i-1,
     * inclusive, untouched.  Under these circumstances, it returns
     * null.  Occasionally, in order to maintain the heap invariant,
     * it must swap a later element of the list with one earlier than
     * i.  Under these circumstances, this method returns the element
     * that was previously at the end of the list and is now at some
     * position before i. This fact is used by iterator.remove so as to
     * avoid missing traversing elements.
     */
    @SuppressWarnings("unchecked")
    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;
    }

函数首先将修改计数modCount增加1,而后判断i对应的是否是queue数组的最后一个元素,如果是,那么直接将其置为null,否则将i对应的元素挖掉并将queue数组的最后一个元素补入,并执行siftDown让queue数组保持最小堆状态。

2.3

peek函数将会返回数组queue中的首个元素,而poll函数会返回数组queue中的首个元素并将数组首个元素挖掉并将最后一个元素补入执行siftDown让queue数组保持最小堆状态。实现上类似执行了removeAt(0)。

3

综上PriorityQueue比较适合用于处理有排序需求的队列问题。
附上leecode链接

你可能感兴趣的:(数据结构,leecode)