Java PriorityQueue阅读
优先队列使用最大或者最小堆来实现,使用数组来储存元素,将数组当做完全二叉树来处理,节点node的左右孩子节点为(2node+1和2node+2)
数组扩容:newCapacity =
oldCapacity+((oldCapacity<64) ?(oldCapacity+2) : (oldCapacity>>1));当newCapacity>Integer.MAX_VALUE - 8时,由private static int hugeCapacity(int minCapacity)处理
最后面的代码是我自己的仿写,有不确定的地方请参考源码
PriorityQueue参数介绍
参数名 | 作用 |
---|---|
Object[] queue | 保存输入的元素 |
size | 已经存入的元素的数量 |
DEFAULT_INITIAL_CAPACITY | 默认的队列初始化大小 |
Comparator<? super E> comparator | 比较存储的元素的顺序 |
int modCount | 队列修改的次数 |
PriorityQueue的构造方法
构造方法分为两类:
- 根据参数进行构造,如:public PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
- 将Collection作为参数进行构造,如:public PriorityQueue(Collection<? extends E> c);其中的Collection可以为SortedSet,PriorityQueue或者其他实现自Collection的类
PriorityQueue的public方法
方法名 | 作用和使用 |
---|---|
public boolean add(E e) | 向队列中添加元素,调用offer方法实现 |
public boolean offer(E e) | 向队列中添加元素,e==null抛出异常,否则返回true |
public E peek() | 返回优先队列中的第一个元素,及queue[0] |
public boolean remove(Object o) | 从队列中删除o元素,成功返回true,否则返回false |
public boolean contains(Object o) | 队列中是否存在元素o |
public Object[] toArray() | 返回队列中的元素(数组queue)的拷贝 |
public T[] toArray(T[] a) | 将queue数组中的元素转换为T类型并拷贝到T[] a中,返回a |
public Iterator iterator() | 返回一个队列的Iterator对象 |
public void clear() | 清空数组queue |
public E poll() | 删除并返回优先队列中的第一个元素(queue[0]) |
PriorityQueue的private方法
我在这里根据作用把他们分为三类:
-
增加队列的容积:
private void grow(int minCapacity):在offer方法中使用,minCapactiy = queue.length+1;增加方法为:newCapacity =
oldCapacity+((oldCapacity<64) ?(oldCapacity+2) : (oldCapacity>>1)); -
平衡队列中的数据顺序
private void siftUp(int k,E x):k:当前元素的插入位置,x插入的元素,在增加元素和删除元素时被使用,但是这个方法不负责具体实现,只是用来判断队列中是否申明了Comparator,申明了调用私有方法siftUpUsingComparator方法去具体实现,否则调用siftUpComparable方法;下面会具体说明源代码的实现
private void siftUpComparable(int k, E x):k和x同上,负责向上平衡queue(平衡方法可参考最小堆的平衡方法)平衡时的比较使用E的CompareTo方法
private void siftUpUsingComparator(int k, E x):k和x同上,负责向上平衡queue(平衡方法可参考最小堆的平衡方法)平衡时的比较使用Comparator的规则
还有向下平衡的三个私有方法:基本内容同上面三个方法
private void siftDown(int k, E x):
private void siftDownComparable(int k, E x):
private void siftDownUsingComparator(int k,E x): -
查和删除的辅助方法
private int indexOf(Object o): 查找对象o在队列中的位置,在public类remove中被调用;
private E removeAt(int i): 删除队列queue中位于第i位置的元素并平衡queue,在public类remove和私有类Iterator中的remove中被调用,下面会详细讲解这个方法;
主要源码讲解
- private void siftUpComparable(int k, E x)和private void siftUpUsingComparator(int k, E x)方法的实现:
/**
* 在queue中的第k个位置(从位置0开始)放入x,并向上调整顺序
* 与父节点比较,如果比父节点顺序靠前,与父节点位置交换
* @param k 开始时放入元素x的位置
* @param x 放入的元素
*/private void siftUpComparable(int k, E x) {
Comparable<? super E> key=(Comparable<? super E>)x;
while (k>0){
int parent=(k-1)>>>1;
Object pNode=queue[parent];
if(key.compareTo((E)pNode)>0)
break;
queue[k]=pNode;
k=parent;
}
queue[k]=key;
}
- private void siftDownComparable(int k, E x)和private void siftDownUsingComparator(int k,E x)方法的实现:
/**
* 在queue中的第loca个位置(从位置0开始)放入x,并向下调整顺序
* 先找出子节点中顺序靠前的节点,再与父节点的顺序进行比较,如果顺序靠前的子节点的顺序在父节点的前面,父节点与子节点交换位置
* @param loca 开始时放入元素x的位置
* @param x 放入的元素
*/
pri
private void siftDownComparable(int loca,E x){
Comparable<? super E> key=(Comparable<? super E>)x;
int half=size>>>1;
// size为保存元素的数组的大小,第n位的左右子节点为(2*n+1)和(2*n+2),所以当loca小于size/2时,loca不存在子节点
while (loca<half){
int child=(loca<<1)+1;
Object childNode=queue[child];
int right=child+1;
//siftDownUsingComparator方法中的比较方法为:
//comparator.compare((E)childNode,(E)queue[right])>0
if(right<size
&&((Comparable<? super E>)childNode).compareTo((E)queue[right])>0){
childNode=queue[right];
child=right;
}
if(key.compareTo((E)childNode)<=0)
break;
queue[loca]=childNode;
loca=child;
}
queue[loca]=x;
}
- 内部类Iterator的实现
//这是一个很重要的函数,优先队列中的所有remove方法都掉用了这个方法来完成
/**
*作用删除在queue数组中位于第index的元素
*实现,使用数组末尾的元素来替换第index位的元素,替换后会先调用siftDown(向下平衡)方法,
*如果该元素没有向下移动(判断标准为位置是否变化),再使用siftUp方法向上平衡。
*该元素的最终位置小于index,返回该元素,否则返回null。返回值主要是在Iteretor的remove方法中用到,为了防止删除元素后,新填充的元素顺序比删除的元素顺序靠前而没有遍历。
*/
E removeAt(int index) {
int s=--size;
if(s==index){
queue[index]=null;
}else{
E moved=(E)queue[s];
siftDown(index,moved);
//没有向下移动的时候,queue[index]与moved的引用相同
if(queue[index]==moved){
siftUp(index,moved);
if (queue[index]!=moved)
return moved;
}
}
return null;
}
//这是迭代器的实现,这里面的next方法和remove方法是优先队列中最难理解的部分,
//remove使用前next方法必须被使用
private final class Itr implements Iterator<E>{
//当前遍历在queue中的位置
private int cursor;
//标记当前遍历的元素是在数组中还是在forgetMeNot,在forgetMeNot中lastRet=-1;
private int lastRet=-1;
//保存removeAt的返回值
private ArrayDeque<E> forgetMeNot;
//当前遍历的元素
private E lastRetElt;
Itr(){}
@Override
public boolean hasNext() {
return cursor<size||
(forgetMeNot!=null&&!forgetMeNot.isEmpty());
}
//先遍历queue中的在遍历forgetMeNot中的
@Override
public E next() {
if(cursor<size)
return (E) queue[lastRet=cursor++];
if(forgetMeNot!=null){
lastRet=-1;
lastRetElt=forgetMeNot.poll();
if(lastRetElt!=null)
return lastRetElt;
}
throw new NoSuchElementException();
}
//removeAt返回!=null时,说明末尾的元素比删除的元素的顺序更靠前(前面有说),继续遍历
//queue不能遍历到这个元素,所以把它加入forgetMeNot中,返回null时,说明已经遍历过的元素
//位置没有变化,并且用没有遍历过的元素替代了删除的元素,所以cursor--;
public void remove(){
if(lastRet!=-1){
E moved=PriorityQueueRewrite.this.removeAt(lastRet);
lastRet=-1;
if(moved==null){
cursor--;
}else{
if(forgetMeNot==null){
forgetMeNot=new ArrayDeque<>();
}
forgetMeNot.add(moved);
}
}else if(lastRetElt!=null){
PriorityQueueRewrite.this.removeEq(lastRetElt);
lastRetElt=null;
}else throw new IllegalStateException();
}
}
- queue的接口的实现:
添加:在数组末尾添加,然后向上平衡
poll:返回数组头部元素,将数组末尾的元素移到首部,然后向下平衡
实现详情请参考最大堆的的实现