写offer
offer
- 递增当前队列更改次数modCount
- 是否超过队列长度,如果是则扩容:如果原长度小于64则每次扩容2,否则每次扩容50%
- 递增size
- 如果是第一个元素直接放在队列(其实是一个Object数组)0下标处
- 添加数据siftUp,如果存在comparator则使用自定义的比较器比较,否则使用元素实现的Comparable接口的比较方法进行比较。暂定使用默认的:siftUpComparable
重建二分最小堆
siftUpComparable
优先级队列底层结构是二分最小堆,按照顺序写入数据:5,1,2,6,8,7,0
- 如果父节点(初始值为size)k大于零,继续循环
- 获取当前节点(下标=size,不是队列的size,是实际队列中元素数量的size)的父节点
- 当前节点(当前size对应的下标,如果插入第二个节点则,插入前size为1,则当前节点下标=1)大于父节点,直接将放入当前节点
- 当前节点小于父节点,交换当前节点与父节点,并循环继续重复前两步
案例
按照顺序写入数据:5,1,2,6,8,7,0,插入最后一个元素时的过程
x节点=数据0节点,k下标=6
数据0小于父节点数据2,交换父子节点
x节点=数据0节点,k下标=2
当前节点下标2大于0继续循环,数据0小于父节点数据1,交换父子节点
x节点=数据0节点,k下标=0
当前节点下标=0,结束循环,插入完成。
源码
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;
}
读poll
poll
- 如果size为0返回null
- 递减size
- 递增队列修改次数modCount
- 获取队列的第一个元素(下标0处的元素)即最高优先级的数据
- 获取最后一个节点x,并将最后一个节点置为null
- 如果递减后size不为0则重新构建二分最小堆siftDown,k=0,x=最后一个元素
- 返回result即最高优先级的数据
重建二分最小堆
siftDownComparable
- 如果第一个元素下标(初始值固定为0)小于size的一半则继续循环
- 计算元素的子下标child(左儿子),获取子元素c(左儿子),计算右儿子的下标right(即左儿子下标+1)
- 如果右儿子下标小于size(防止越界),并且左儿子大于右儿子,则更新child为右儿子下标,更新c为右儿子。
- 如果key小于等于子节点c(左右儿子节点中的最小值),终止循环
- 如果步骤3成立,子元素c(即右儿子)写入queue队列的k下标处(初始值始终为0);更新k为元素的子下标child(即右儿子下标)
- 否则:子元素c(即左儿子)写入queue队列的k下标处(初始值始终为0);更新k为元素的子下标child(即左儿子下标)
- 循环沿发生变动的节点进行比较直至叶子节点。即二分堆遍历
- 循环结束将key(即提前取出的队列中的尾巴节点)写入索引k处
案例1:下一个最小值为右子节点,尾巴节点为右子树的叶子节点
拉取一个数据,即优先级最高的root节点,数据为0
k=0;x=key=最后一个节,数据为2的节点
读取最后一个节点,并将最后一个节点置为null
k=0
k小于size的一半3继续循环,右儿子下标2小于size需要比较左右儿子,左儿子大于右儿子,key节点大于左右儿子的最小值,右儿子晋升为父节点
k=2
k小于size的一半3继续循环,右儿子下标6等于size不需要比较左右儿子,key节点(即提前取出的最后一个节点,数据为2的节点)小于左右儿子的最小值(data=7),结束循环。将key写入下标k处的节点。
案例2:下一个最小值为左子节点,尾巴节点为右子树的叶子节点
k=0;x=key=最后一个节点,数据为9的节点
读取最后一个节点,并将最后一个节点置为null
k=0
k小于size的一半3继续循环,右儿子下标2小于size需要比较左右儿子,左儿子小于右儿子,key节点大于左右儿子的最小值,左儿子晋升为父节点
k=1
k小于size的一半3继续循环,右儿子下标4小于size需要比较左右儿子,左儿子大于右儿子,key节点大于左右儿子的最小值,右儿子晋升为父节点
k=4
k大于size的一半3结束循环。将key写入下标k处的节点。
案例3:同案例2,只是尾巴节点的数据假设为(即key.data=5.5)
则倒数第二步会有所不同,如下:
k=1
k小于size的一半3继续循环,右儿子下标4小于size需要比较左右儿子,左儿子大于右儿子,key节点小于左右儿子的最小值,结束循环。将key写入下标k处的节点。
源码
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
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;
queue[k] = c;
k = child;
}
queue[k] = key;
}