其内部其实是一个最小堆
最小堆具有三个特性:
1.最小堆是完全二叉树。
2.任意节点如果有左右孩子,那么它的值必须小于或者等于左右孩子的值。
3.任意非叶子节点的左右子树也都是堆。
图示举例:
而最小堆可以由数组或者二叉链表实现,通过源码可知PriorityQueue是使用的数组实现最小堆。
transient Object[] queue;因此queue[n]节点的左孩子为queue[2n+1],右孩子为queue[2n+2]。但需要小心是否数组越界!
也可以因此推导,queue[m]的父节点下标是 queue[(m-1)/2] 。因为Java中整数运算存在向下取整现象,因此针对左右孩子均满足。但注意当m=0时 (m-1)/2为0,但根节点不存在所谓的父节点,这样计算是错误的。
调用PriorityQueue的 iterator()方法时,返回的是一个名为Itr的内部类,这个类实现了Iterator接口并且被声明为private final class 。并在内部利用cursor指针迭代内部数组queue。
简单来说就是对queue数组进行了一次遍历。
调用add()或者offer()方法均可,因为本质上add()内部就是调用的offer()函数。
插入元素时分情况考虑,并且总是插入到堆的末尾。
堆为空:
直接插入,无需扩容,无需调整。(构造函数中初始化queue,默认初始容量11)
堆不为空:
1.判断是否需要扩容,需要的话则调用grow方法。
2.插入元素后可能会破坏堆的平衡,调用siftUp(上滤)方法调整堆,直到平衡。
public boolean offer(E e) { if (e == null) throw new NullPointerException(); modCount++; int i = size; if (i >= queue.length) //实际大小超过queue容量时进行扩容 grow(i + 1); size = i + 1; if (i == 0) //堆为空时 queue[0] = e; else //堆不空时,上滤调整 siftUp(i, e); return true; }
与ArrayList扩容方式类似
若数组容量小于64,则扩大为原来的2倍
若数组容量大于等于64,则扩大为原来的1.5倍
最后还要检查新容量不能大于MAX_ARRAY_SIZE (为int的最大值-8,-8是在序列化的情况下有的JVM实现需要预留一个头部写入数组大小,不-8的话有可能造成数组越界)
且将原数组内容拷贝到新数组中,并丢弃原数组中内容
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); }
自底向上,不断地比较,交换
这里假设插入的元素为e
首先将e插入到queue末尾,再不断地比较e与父节点p的关系
若 e < p则交换,且将指针回溯到p
若e >= p则表示当前位置合适,并终止
//在k位置插入元素x 一般情况k是size+1 时间复杂度O(logN) 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; }
即弹出堆顶元素,并将末尾元素直接放到堆顶,size--
再调用siftDown(下滤)进行堆调整
自顶向下,使堆再平衡
直到下标越界
节点的值要同时小于左孩子及右孩子(选其中最小的交换)
//k为插入位置,一般情况是0 x为插入元素 时间复杂度O(logN) private void siftDownComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>)x; int half = size >>> 1; // 取size中间值 while (k < half) { //当k大于一半的size时表示没有左右孩子 int child = (k << 1) + 1; 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; }
queue中存的数组本就无序
//时间复杂度不是O(NlogN) 而是O(N) private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }