知识储备
数据结构知识储备:
堆的定义:百度百科
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;(要么父节点都大于等于子节点的值,要么父节点的值都小于等于子节点的值)
- 堆总是一棵完全二叉树。
简而言之,堆是一棵完全二叉树,这个二叉树的节点的值要满足下面的规则。假设node表示节点,node.val 表示节点值,node.left 表示该节点的左子节点,node.right 表示该节点的右子节点。那么,堆要满足的规则是:
(node.val <= node.left.val && node.val <= node.right.val) || (node.val >= node.left.val && node.val >= node.right.val).
当 node.val <= node.left.val && node.val <= node.right.val 时,父节点的值都小于等于子节点的值,称为小根堆(或小顶堆)。反之,父节点的值都大于等于子节点的值,称为大根堆(或大顶堆)
注意:完全二叉树的数据结构可以使用链表,也可以使用数组。在PriorityQueue中就是使用数组来表示完全二叉树。
PriorityQueue源码
类部分注释
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.
或者在**队列构造时提供的Comparator,这取决于使用哪个构造函数。
A priority queue does not permit null elements.
优先队列不允许空元素。
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
// Object数组 保存队列元素
transient Object[] queue;
// 默认的 初始化数组长度
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 队列所含元素个数
private int size = 0;
// 队列元素的比较器
private final Comparator<? super E> comparator;
...
}
既然优先队列是基于优先堆实现的,那么我们就必须搞清楚优先堆的优先是什么意思。优先堆的优先指的是**先使用堆中最小元素还是最大元素。**堆的定义要求堆要么是小根堆,要么是大根堆。如果是小根堆,自然先使用最小元素,如果是大根堆,自然先使用最大元素。可以通过构造函数来
注:一般情况,**看源码注释,着重看源码的第一段注释。**我一般是细读第一段的注释,剩下的注释稍微读一读。
父节点和子节点的编号是有联系的,更确切的说父子节点的编号之间有如下关系:
leftNo = parentNo*2+1
rightNo = parentNo*2+2
parentNo = (nodeNo-1)/2
通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储堆的原因。
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;
}
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] 或者 k == 0 为止。注意这里的比较可以是元素的自然顺序,也可以是依靠比较器的顺序。
remove()和poll()
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小于或等于左右孩子中的任何一个为止。
//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;
}
源码阅读建议:利用断点跟踪到源码里面看数组元素的变化。
public static void main(String[] args) {
// 无参构造器,默认使用小根堆
PriorityQueue<Integer> integerPriorityQueue = new PriorityQueue<>();
integerPriorityQueue.add(1);
integerPriorityQueue.add(3);
integerPriorityQueue.add(2);
// integerPriorityQueue.add(0);
while (!integerPriorityQueue.isEmpty()) {
Integer poll = integerPriorityQueue.poll();
System.out.println(poll);
}
// 自定义比较器:按照数字大小倒序,也就是使用大根堆
PriorityQueue<Integer> reversePriorityQueue = new PriorityQueue<>(Comparator.reverseOrder());
reversePriorityQueue.add(1);
reversePriorityQueue.add(3);
reversePriorityQueue.add(2);
reversePriorityQueue.add(0);
while (!reversePriorityQueue.isEmpty()) {
Integer poll = reversePriorityQueue.poll();
System.out.println(poll);
}
// 自定义 初始化数组大小,设置initialCapacity 的意义在于——如果开发者能提前预估 PriorityQueue 的大小,可以减少扩容,提升性能
PriorityQueue<Integer> sizePriorityQueue = new PriorityQueue<>(4);
sizePriorityQueue.add(1);
sizePriorityQueue.add(3);
sizePriorityQueue.add(2);
sizePriorityQueue.add(0);
sizePriorityQueue.add(4);
while (!sizePriorityQueue.isEmpty()) {
Integer poll = sizePriorityQueue.poll();
System.out.println(poll);
}
}
原文地址:博客园