PriorityQueue源码解析
Posted qulllee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PriorityQueue源码解析相关的知识,希望对你有一定的参考价值。
PriorityQueue
PriorityQueue
基于小根堆
实现,是一个无界队列,不允许null元素。底层存储使用数组,索引n的元素的左右两个孩子索引分别为2n + 1
和2n + 2
。
PriorityQueue
元素通过比较器排序,如果比较器为空,则使用自然排序。
PriorityQueue
默认容量大小为11,当存储数组中总元素个数等于数组长度时,触发扩容。扩容时,如果原存储数组长度小于64,则扩容为原来的1倍,否则扩容50%,扩容中使用Arrays.copyOf
复制旧数组元素到新数组。
关键属性
// 默认数组容量11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 存储队列元素 queue[n]的左右孩子分别为queue[2n + 1]、queue[2(n+1)]
transient Object[] queue;
// 优先队列中总元素个数
private int size = 0;
// 排序比较器
private final Comparator<? super E> comparator;
// 结构性修改次数
transient int modCount = 0;
构造函数
/**
* 使用默认容量(11)、元素之间使用自然排序
*/
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
/**
* 指定初始容量,元素之间使用自然排序
*/
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
/**
* 指定初始容量,指定比较器
*/
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
// 初始存储数组,大小为initialCapacity
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
新增元素
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
// 结构性修改次数+1
modCount++;
int i = size;
// 如果总元素个数=存储数组长度,触发扩容
if (i >= queue.length)
grow(i + 1);
size = i + 1;
// (第一次插入元素)如果队列中无元素,直接设置数组0位元素为新元素
if (i == 0)
queue[0] = e;
else
// 将新增元素添加到队列中
siftUp(i, e);
return true;
}
/**
* 添加元素至队列,上浮
* @param k 队列中总元素个数
* @param x 新增元素
*/
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
/**
* 使用自然排序排列
* 插入元素时,先将新增元素放在最后一个元素位置,然后将新增元素与父级元素比较,如果大于等于父级元素,新增元
* 素便插入在最后一个位置;否则循环向上查找父节点,直到新增元素不小于父节点
* @param k 队列中总元素个数
* @param x 新增元素
*/
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
// 找到索引为k的节点的父节点
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 新增节点大于等于父节点,不需要再调整
if (key.compareTo((E) e) >= 0)
break;
// 将k位置设置为父节点
queue[k] = e;
// 搜索节点上浮
k = parent;
}
// 上浮到k
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;
}
扩容
/**
* 扩容。新建数组,将数组扩容后,复制旧数组元素到新数组中
* 当总元素个数等于数组长度时,触发扩容
* 当数组长度小于64时,扩容一倍;否则扩容50%
*/
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);
}
/**
* 溢出处理
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
获取元素
/**
* 获取元素。取出根节点(数组的第一个元素)
* 取出第一个元素后,将最后一个元素移动到index=0位置,然后与index = 1、index = 2 位置的元素比较,如果
* 最后一个元素比两个孩子大,则找出左右孩子中较小的,作为根节点。
*/
public E poll() {
// 数组为空,返回null
if (size == 0)
return null;
// 队列总元素-1
int s = --size;
// 结构性修改次数+1
modCount++;
// 获取数组第一个元素
E result = (E) queue[0];
// 最后一个元素
E x = (E) queue[s];
queue[s] = null;
// 下沉最后一个元素
if (s != 0)
siftDown(0, x);
return result;
}
/**
* 下沉元素
* @param k 0
* @param x 最后一个元素
*/
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
/**
* 下沉元素
* @param k 0
* @param x 最后一个元素
*/
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
// 由于index = k位置的元素的左右孩子在2n + 1 和 2(n + 1)位置,
// 所以,如果k 大于 总元素数量的一半,那么该位置的元素也就不存在孩子节点,不需要调整
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
// 左孩子位置
int child = (k << 1) + 1; // assume left child is least
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;
// 最后一个节点比index = k 的左右节点大,那么左右节点中的较小者上浮做父节点,继续下沉最后一个节点
// 较小孩子上浮
queue[k] = c;
// 最后一个节点继续下城
k = child;
}
// 最后一个节点下沉到k位置
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;
}
移除元素
/**
* 移除指定元素
* 如果存在指定元素,返回true,否则,返回false
*/
public boolean remove(Object o) {
// 获取指定元素在数组中位置
int i = indexOf(o);
// 不存在指定元素,返回false
if (i == -1)
return false;
// 存在指定元素,移除,并返回true
else {
removeAt(i);
return true;
}
}
/**
* 获取指定元素的在数组中位置
*/
private int indexOf(Object o) {
if (o != null) {
// 遍历数组,如果存在相同元素,返回元素所在下标
for (int i = 0; i < size; i++)
if (o.equals(queue[i]))
return i;
}
// 不存在相同元素,返回-1
return -1;
}
/**
* 移除指定下标的元素
*/
private E removeAt(int i) {
// assert i >= 0 && i < size;
// 结构性修改+1
modCount++;
// 总元素个数-1
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;
}
以上是关于PriorityQueue源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Queue(队列)接口和其实现类PriorityQueue(优先级队列)源码解析