Class PriorityQueue

Module java.base
Package java.util
Class PriorityQueue

java.lang.Object
	java.util.AbstractCollection<E>
		java.util.AbstractQueue<E>
			java.util.PriorityQueue<E>

Type Parameters:
E - the type of elements held in this queue
All Implemented Interfaces:
Serializable, Iterable, Collection, Queue


public class PriorityQueue
extends AbstractQueue
implements Serializable

An unbounded priority queue based on a priority heap. The elements of the priority queue are ordered according to their natural ordering, or by a Comparator provided at queue construction time, depending on which constructor is used. A priority queue does not permit null elements. A priority queue relying on natural ordering also does not permit insertion of non-comparable objects (doing so may result in ClassCastException).
The head of this queue is the least element with respect to the specified ordering. If multiple elements are tied for least value, the head is one of those elements – ties are broken arbitrarily. The queue retrieval operations poll, remove, peek, and element access the element at the head of the queue.

A priority queue is unbounded, but has an internal capacity governing the size of an array used to store the elements on the queue. It is always at least as large as the queue size. As elements are added to a priority queue, its capacity grows automatically. The details of the growth policy are not specified.

This class and its iterator implement all of the optional methods of the Collection and Iterator interfaces. The Iterator provided in method iterator() and the Spliterator provided in method spliterator() are not guaranteed to traverse the elements of the priority queue in any particular order. If you need ordered traversal, consider using Arrays.sort(pq.toArray()).

Note that this implementation is not synchronized. Multiple threads should not access a PriorityQueue instance concurrently if any of the threads modifies the queue. Instead, use the thread-safe PriorityBlockingQueue class.

Implementation note: this implementation provides O(log(n)) time for the enqueuing and dequeuing methods (offer, poll, remove() and add); linear time for the remove(Object) and contains(Object) methods; and constant time for the retrieval methods (peek, element, and size).

This class is a member of the Java Collections Framework.

Constructor Summary

PriorityQueue()

Creates a PriorityQueue with the default initial capacity (11) that orders its elements according to their natural ordering.

PriorityQueue(int initialCapacity)

Creates a PriorityQueue with the specified initial capacity that orders its elements according to their natural ordering.

PriorityQueue(int initialCapacity, Comparator<? super E> comparator)

Creates a PriorityQueue with the specified initial capacity that orders its elements according to the specified comparator.

PriorityQueue(Collection<? extends E> c)

Creates a PriorityQueue containing the elements in the specified collection.

PriorityQueue(Comparator<? super E> comparator)

Creates a PriorityQueue with the default initial capacity and whose elements are ordered according to the specified comparator.

PriorityQueue(PriorityQueue<? extends E> c)

Creates a PriorityQueue containing the elements in the specified priority queue.

PriorityQueue(SortedSet<? extends E> c)

Creates a PriorityQueue containing the elements in the specified sorted set.

Method Summary

boolean add(E e) Inserts the specified element into this priority queue. 
boolean offer(E e) Inserts the specified element into this priority queue.
E peek() Retrieves, but does not remove, the head of this queue, or returns null if this queue is empty.
E poll() Retrieves and removes the head of this queue, or returns null if this queue is empty.	
boolean contains(Object o) Returns true if this queue contains the specified element.
void forEach(Consumer<? super E> action) Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.
Comparator<? super E> comparator() Returns the comparator used to order the elements in this queue, or null if this queue is sorted according to the natural ordering of its elements.
int size() Returns the number of elements in this collection.

Iterator<E> iterator() Returns an iterator over the elements in this queue.

void clear() Removes all of the elements from this priority queue.	
boolean remove(Object o) Removes a single instance of the specified element from this queue, if it is present.
boolean removeAll(Collection<?> c) Removes all of this collection's elements that are also contained in the specified collection (optional operation).
boolean removeIf(Predicate<? super E> filter) Removes all of the elements of this collection that satisfy the given predicate.
boolean retainAll(Collection<?> c) Retains only the elements in this collection that are contained in the specified collection (optional operation).

final Spliterator<E> spliterator() 	Creates a late-binding and fail-fast Spliterator over the elements in this queue.

Object[] toArray() Returns an array containing all of the elements in this queue.
<T> T[] toArray(T[] a) Returns an array containing all of the elements in this queue; the runtime type of the returned array is that of the specified array.
Methods declared in class java.util.AbstractQueue

addAll, element, remove

Methods declared in class java.util.AbstractCollection

containsAll, isEmpty, toString

Methods declared in class java.lang.Object

clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Methods declared in interface java.util.Collection

containsAll, equals, hashCode, isEmpty, parallelStream, stream, toArray

优先队列 PriorityQueue

优先队列本质上就是一个最小堆。而堆又是什么呢?它是一个数组,不过满足一个特殊的性质。我们以一种完全二叉树的视角去看这个数组,并用二叉树的上下级关系来映射到数组上面。如果是最大堆,则二叉树的顶点是保存的最大值,最小堆则保存的最小值。

最小堆满足的一个基本性质是堆顶端的元素是所有元素里最小的那个。如果我们将顶端的元素去掉之后,为了保持堆的性质,需要进行调整。对堆的操作和调整主要包含三个方面,增加新的元素,删除顶端元素和建堆时保证堆性质的操作。
优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java 的优先队列每次取最小元素
,C++ 的优先队列每次取最大元素)。元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator)。

Java 中 PriorityQueue 实现了 Queue 接口,不允许放入 null 元素;其通过堆实现,具体说是通过完全二叉树(complete binary tree)实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值),也就意味着可以通过数组来作为 PriorityQueue 的底层实现。

Java PriorityQueue_数组


父子节点的编号之间有如下关系:

leftNo = parentNo2 + 1
rightNo = parentNo
2 + 2

parentNo = (nodeNo-1) / 2

通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储堆的原因。

PriorityQueue 的 peek() 和 element 操作是常数时间,add(), offer(), 无参数的 remove() 以及 poll() 方法的时间复杂度都是 log(N)。

add() 和 offer()

add(E e) 和 offer(E e) 的语义相同,都是向优先队列中插入元素,只是 Queue 接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回 false。对于PriorityQueue 这两个方法其实没什么差别。

//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 下标处的那个元素即可。

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

remove() 和 poll()

remove() 和 poll() 方法的语义也完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回 null。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。

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. 删除的不是最后一个元素,从删除点开始以最后一个元素为参照调用一次 siftDown() 即可。

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

PriorityQueue 采用数组实现,也是一棵完全二叉树,构成堆结构。数组初始大小为 11。

Queue 框架

Java PriorityQueue_后端_02

## PriorityQueue 常用方法

public boolean add(E e); //在队尾添加元素,并调整堆结构
public E remove(); //在队头删除元素,并返回,再调整堆结构
public E element(); //返回队头元素(不删除)
public boolean isEmpty(); //判断队列是否为空
 
public int size(); //获取队列中元素个数
public void clear(); //清空队列
public boolean contains(Object o); //判断队列中是否包含指定元素(从队头到队尾遍历)
public Iterator<E> iterator(); //迭代器

最小优先队列

import java.util.PriorityQueue

int[] a={6,4,7,3,9,8,1,2,5,0};
PriorityQueue<Integer> q = new PriorityQueue<>();
for (int i : a) q.add(i);
// 0 1 3 4 2 8 7 6 5 9 
while (!q.isEmpty()){
	int e = q.remove();
	System.out.print(e + " ");
}

Java PriorityQueue_后端_03

最大优先队列

import java.util.Comparator;
import java.util.PriorityQueue;
 
int[] a={6,4,7,3,9,8,1,2,5,0};
PriorityQueue<Integer> que=new PriorityQueue<Integer>(new Comparator<Integer>() {
	public int compare(Integer o1, Integer o2) {return o2-o1;}
});
for(int e:a) {que.add(e);}
for(int e:que) {System.out.print(e+" ");}
System.out.println();
while(!que.isEmpty()) {
	int e=que.remove();
	System.out.print(e+" ");
}

运行结果:

9 7 8 5 4 6 1 2 3 0

9 8 7 6 5 4 3 2 1 0

Java PriorityQueue_后端_04

topK 问题

topK问题是指:从海量数据中寻找最大的前k个数据,比如从1亿个数据中,寻找最大的1万个数。

使用优先队列,能够很好的解决这个问题。先使用前1万个数构建最小优先队列,以后每取一个数,都与队头元素进行比较,若大于队头元素,就将队头元素删除,并将该元素添加到优先队列中;若小于队头元素,则将该元素丢弃掉。如此往复,直至所有元素都访问完。最后优先队列中的1万个元素就是最大的1万个元素。

求 {6,4,7,3,9,8,1,2,5,0} 中最大的5个数为例。

import java.util.PriorityQueue;
 
int[] a={6,4,7,3,9,8,1,2,5,0};
PriorityQueue<Integer> que=new PriorityQueue<Integer>();
for(int i=0;i<5;i++) {
	que.add(a[i]);
}
for(int i=5;i<10;i++) {
	if(a[i]>que.element()) {
		que.remove();
		que.add(a[i]);
	}
}
while(!que.isEmpty()) {
	int e=que.remove();
	System.out.print(e+" ");
}

运行结果:
5 6 7 8 9