PriorityQueue (优先级队列)
1、集合特点
PriorityQueue 优先级队列 是queue接口下的集合 存储单值
由实现的接口 — 》 特点:
1)Collection接口:集合顶级接口
是存放一组单值的最大接口。 所谓的单值是指集合中的每个元素都是一个对象。
一般很少直接使用此接口直接操作。
2)Queue接口:具有队列的性质
队列接口,具有队列先入先出的特点。此接口的子类可以实现队列操作 优先级队列有队列的性质。
传统先入先出:先添加的元素先弹出 == 先添加元素优先级最高。把优先级高的先弹出
传统队列的优先级比较原则:先添加的元素先弹出
优先级队列的先入先出:我们需要提供一个优先级比较原则,优先级队列根据这个原则。
判断谁是最小的,谁最小谁的优先级高。谁的优先级高谁先被弹出。
3)Serializable :这个集合是可以被序列化的
4)Iterator : 这个集合是可以使用迭代器进行从前往后的迭代。
2、集合使用
底层是数组
维护小根堆结构:父节点小于左节点和右节点,而左右节点的大小不能确定
父节点:i
左孩子:2i+1
右孩子:2i+2
优先级队列进行使用(主要是提供比较原则)
如何提供优先级队列的比较原则?
和TreeMap中 提供大小比较原则相同
相当于我们也需要给优先级队列提供一个大小比较的原则 哪个比较小哪个优先级就高
优先级队列我们也是通过提供比较器的方式 提供的大小比较的方式的。(外比较器,内比较器)
3、基本方法及源码实现
1)构造方法及全局变量
(可以使用外比较器的构造函数:如果未提供外比较器则设为null)
public class MyPriorityQueue<E> {
private Object []queue;
private final static int DEFAULT_CAPACITY=11;//数组的初始容量
private final Comparator<E> comparator ;//继承的外比较器
private int size;//元素的个数
public MyPriorityQueue() {
queue =new Object[DEFAULT_CAPACITY ];
this .comparator =null ;
}
public MyPriorityQueue (Comparator<E> comparator ){//有外部比较器的构造函数
queue =new Object[DEFAULT_CAPACITY ];
this .comparator =comparator ;
}
2)boolean remove(Object o) o代表所要删除的元素 删除一个指定元素 如果元素不存在返回false,如果删除成功返回true
a)先找到待删除元素的下标
b)如果删除的是最后一个元素,直接删除
c)如果不是最后一个元素,将待删元素置空,最后一个元素放在删除元素下标的位置
d)为了维护小根堆结构,要进行比较:先向下调整,因为父节点小于子节点,所以最后一个节点大于待删节点的父节点的概率比较大,所以先和下面的节点进行比较,如果未进行向下调整则需要进行向上调整,因为小根堆不能保证左右节点的大小关系,如果左大于右,则可能进行向上调整,进行了向下调整则不需要进行向上调整
/**
* 删除操作
* @param o
* @return
*/
public boolean remove(Object o){
int i=indexOf(o);//找到o元素所在的下标
if(i==-1){
return false ;
}else {
//开始进行删除
int s=--size ;//获取最后一个元素的下标值 并将个数-1
if(i==s){//如果删除的是最后一个元素,不需要调整,直接删除
queue [i]=null ;
}
//将最后一个元素放在待删除下标下进行对比
E movd= (E) queue [s];
queue [i]=null ;
siftDown(i,movd );//先向下调整,由于底层是小根堆结构,所以向下调整的概率比较大
if(queue [i]==movd ){//若未进行向下调整,则需向上调整
siftUp(i,movd );
}
}return true ;
}
private int indexOf(Object o) {
if(o==null ){
return -1;
}
for(int i=0;i<size ;i++){
if(o.equals(queue [i]) ){
return i;
}
}
return -1;
}
/**
* 向下调整操作
* i:调整元素的下标
* x:待调整元素
* @param i
* @param x
*/
private void siftDown(int i, E x) {//i 是x的下标
//循环退出的条件:父节点的值比本身小
//大小比较原则,通过外比较器或者内比较器 外比较器的优先级高于内比较器
if(comparator !=null ){//有外比较器,进行外比较器调整
siftDownByComparator(i,x);
}else {
siftDownByComparable(i,x);
}
}
/**
* 内比较器的向下调整
* @param i
* @param x
*/
private void siftDownByComparable(int i, E x) {
Comparable <? super E> key=(Comparable <? super E> )x;
//int left=2*i+1;
//int right=left+1;
int half=size>>>1;//计算出没有孩子节点的最小下标
while (i<half ){
int child=i<<1+1;
Object c=queue [child ];
//c是左孩子的值
//child是左孩子的下标
int right=child +1;//right是右孩子的坐标,此时并不能保证一定有右孩子
if(right <size ) {
Object r = queue[right];
Comparable<? super E> ckey = (Comparable<? super E>) c;
if (ckey.compareTo((E) r) > 0) {
child = right;
c = r;
//c是右孩子的值
//child是右孩子的下标
}
}
if(key .compareTo((E)c)<0 ){
break;
}
queue [i]=c;//将孩子的值与赋给父节点
i=child ; //更新父节点
}
queue [i]=x;//将父节点的值赋给孩子
}
/**
* 外比较器的向下调整
* @param i
* @param x
*/
private void siftDownByComparator(int i, E x) {
//int left=2*i+1;
//int right=left+1;
int half=size>>>1;//计算出没有孩子节点的最小下标
while (i<half ){
int child=i<<1+1;
Object c=queue [child ];
//c是左孩子的值
//child是左孩子的下标
int right=child +1;//right是右孩子的坐标,此时并不能保证一定有右孩子
if(right <size ) {
Object r = queue[right];
if (comparator .compare((E) c,(E)r)>0 ) {
child = right;
c = r;
//c是右孩子的值
//child是右孩子的下标
}
}
if(comparator .compare(x,(E)c)<0 ){
break;
}
queue [i]=c;//将孩子的值与赋给父节点
i=child ; //更新父节点
}
queue [i]=x;//将父节点的值赋给孩子
}
3)boolean offer(E e) { 添加一个指定元素 添加元素不能为空,否则返回空指针异常
a)先判断是否需要扩容if(i>=queue .length )
b)如果需要扩容则进入到扩容方法,当size小于64时,进行2倍加2扩容,当大于64时,进行1/2扩容,而新数组的容量也不能大于Integer .MAX_VALUE -8
c)然后进行添加操作,如果是第一个添加则放到0号下标下,其他则进行向上调整(如果当前比父节点小,则交换位置维护小根堆结构)
小根堆结构:父节点小于左节点和右节点,而左右节点的大小不能确定
d)因为比较器的优先级:外比较器》内比较器,所以调整方法又分为外比较器的向上调整和内比较器的向上调整 外比较器主要是comparator .compare(e , x)>= 0 内比较器主要是Comparable <? super E> key=(Comparable <? super E> )e;将比较器的对象取出,然后key .compareTo(x)>=0
/**
* 队列增加操作
* @param e
* @return
*/
public boolean offer(E e){
if(e==null ){
throw new NullPointerException();
}
int i=size ;
if(i>=queue .length ){
grow(size +1);
}
size ++;
if(i==0){
queue [0]=e;
}else {
//先将元素放到最后一个位置上,再向上进行调整
//如果新插入的元素比父节点小,则与父节点进行交换
//反之,不做调整
siftUp(i,e);
}
return true ;
}
private static int MAX_ARRAY_SIZE=Integer .MAX_VALUE -8;//容量边界
/**
* 扩容操作
* @param mincapacity
*/
private void grow(int mincapacity) {//当前容量小于64时,2倍加2扩容,大于64时,1/2扩容
int oldcapacity=queue .length ;
// if(size <64){
// newcapacity = 2*oldcapacity +2;
// }else {
// newcapacity =oldcapacity >>1+oldcapacity ;
// }
int newcapacity =oldcapacity +((oldcapacity < 64)? (oldcapacity +2): (oldcapacity >>1));//三位运算符
if(newcapacity - MAX_ARRAY_SIZE>0){//如果newcapacity大于MAX_ARRAY_SIZE,为了对新数组容量边界控制,进行调整
newcapacity =hugeCapacity(mincapacity );
}
queue =Arrays .copyOf(queue ,newcapacity );
}
private int hugeCapacity(int mincapacity) {//控制newcapacity的容量
if(mincapacity <0){//
throw new OutOfMemoryError() ;
}
return (mincapacity > MAX_ARRAY_SIZE ) ? Integer .MAX_VALUE : MAX_ARRAY_SIZE ;//如果 mincapacity > MAX_ARRAY_SIZE,则使newcapacity=Integer .MAX_VALUE
//如果 mincapacity < MAX_ARRAY_SIZE,则使newcapacity=MAX_ARRAY_SIZE
}
private void siftUp(int i, E e) {//i 计算父节点的位置
//循环退出的条件:父节点的值比本身小
//大小比较原则,通过外比较器或者内比较器 外比较器的优先级高于内比较器
if(comparator !=null ){//有外比较器,进行外比较器调整
siftUpByComparator(i,e);
}else {
siftUpByComparable(i,e);
}
}
/**
* 外比较器的向上调整
* @param i
* @param e
*/
private void siftUpByComparator(int i, E e) {
while (i>0){
int par=(i-1)>>1;
E x=(E)queue [par ];
if(comparator .compare(e , x)>= 0 ){//当前节点不小于父节点
break;
}
queue [i]=x;
i=par ;
}
queue [i]=e;
}
/**
* 内比较器的向上调整
* @param i
* @param e
*/
private void siftUpByComparable(int i, E e) {
Comparable <? super E> key=(Comparable <? super E> )e;
while (i>0){
int par=(i-1)>>>1;//无符号右移
E x=(E)queue [par ];
if(key .compareTo(x)>=0 ){//当前节点不小于父节点
break;
}
queue [i]=x;
i=par ;
}
queue [i]=e;
}
4)boolean add(E e) { 添加一个指定元素
return offer(e);
}
public boolean add(E e){
return offer(e);
}
/**
* 返回优先级最小元素 并不删除, 如果没有元素返回null
* @return
*/
public E peek(){
if(size ==0 ){
return null ;
}
return (E) queue [0];
}
5)E peek() 取出队头元素但不删除 如果当前队头没有元素返回null
队头元素:优先级最高那个元素 小根堆的堆顶元素 数组0号下标
队头没有元素: 优先级队列中没有元素 返回null
/**
* 返回优先级最小元素 并不删除, 如果没有元素返回null
* @return
*/
public E peek(){
if(size ==0 ){
return null ;
}
return (E) queue [0];
}
6)E element() 取出队头元素但不删除 如果当前队头没有元素抛出异常
减少重复代码的出现
/**
* 返回优先级最小元素 并不删除,如果没有元素返回异常
* @return
*/
public E element(){
// if(size ==0){
// throw new NullPointerException() ;
// }
// return (E) queue [0];
E x=peek() ;
if(x==null ){
throw new NullPointerException() ;
}else {
return x;
}
}
7)E poll() 取出队头元素并删除 如果当前队头没有元素返回null
/**
* 返回优先级最小的元素 并删除 如果没有元素返回null
* @return
*/
public E poll(){
if(size ==0 ){
return null ;
}
E res= (E) queue [0];
//删除队头元素
int s=--size ;
E x= (E) queue [s];
queue [s]=null ;
if(s!=0){//进行向下调整 s==0时说明只有一个元素,不需要调整
siftDown(0,x);//i 是x的下标
}
return res;
}
8)迭代器和ArrayList的差不多
/**
* 迭代器的实现
* @return
*/
public Iterator<E> iterator() {
return new Itr();
}
private final class Itr implements Iterator<E> {
private int cur=0;//当前元素的下标
@Override
public boolean hasNext() {
return cur < size ;
}
@Override
public E next() {
int i=cur ;
if(i>=size ){
throw new NoSuchElementException() ;
}
cur =cur +1;
return (E)queue [i ];
}
@Override
public void remove() {
try {
MyPriorityQueue .this .remove(queue [cur ]);
}catch(IndexOutOfBoundsException e) {
throw new ConcurrentModificationException() ;
}
}
}