前言
从字面意思来看 ,就能知道,PriorityQueue是和优先级有关系的一种队列
一、PriorityQueue
- 内部由一个Object 数组存储数据
transient Object[] queue;
- 存储在数组中的数据体现出一个完全二叉堆的结构
- 提供peek方法获取数组中的第一个元素(优先级就是这么来的,我们保证数组里面的第一个数据在某种规则下是最优的)
二、完全二叉堆定义
- 是一个二叉树结构(所有可以用数组来实现)
- 每一个元素的左移子节点都比自身数据大或者小(小顶堆 大顶堆)
三、代码示列
public static void main(String[] args) {
Queue queue = new PriorityQueue();
int[] arr = {39,29,33,50,57,3,78};
for (int i=0;i<arr.length;i++){
queue.add(arr[i]);
}
System.err.println("优先级最高的元素 :" + queue.peek());
System.err.println("队列里面的全部数据");
Iterator iterator = queue.iterator();
while (iterator.hasNext()){
System.err.print(iterator.next()+ " ");
}
System.err.println("");
queue.poll();
System.err.println("移除队列第一个数据后的队列");
Iterator iterator1 = queue.iterator();
while (iterator1.hasNext()){
System.err.print(iterator1.next()+ " ");
}
}
{39,29,33,50,57,3,78}
这个是用来测试的数据,接下来我们用这个数据来分析PriorityQueue的新增,删除流程
四、新增流程
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
这个是添加数据的源码,当添加第一个数据是直接放到数组的第一个位置,当队列里面有数据之后再添加数据就要走siftUp方法了
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
我们这个例子中没有传入comparator,直接看下siftUpComparable
方法
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;
}
这个方法有两个参数k (当前数组的容量,可以理解成当前添加数据可能添加到数组的位置,预占位),x就是当前添加元素的值了
第6行判断条件
if (key.compareTo((E) e) >= 0)
对于入参都是整数来说,如果A小于B A.compareTo(B)返回的是-1,这里也就是key要大于1才满足这个条件
接下来我们根据这个规则具体来分析下{39,29,33,50,57,3,78}新增的流程,后面描述的数组都是指PriorityQueue内部存储数据的数组
- 第一个39数据入队列,直接放到数组的第一个位置
- 第2个数据29入队列(k=1 ,x=29) ,这个时候调用到
siftUpComparable
方法
- 算术右移获取父节点位置0
- 父节点位置的值是39
- 新添加数据小于父节点的值不满足if条件K号位置设置为父节点的值(也就是数组1号位设置成39)
- k 设置成父节点的位置(这时候就是0)
- 不满足k>0的条件退出循环 数组k号位设置当前新增的值
-
第3个数据33入队列(k=2 ,x=33)
- 算术右移获取父节点位置0
- 父节点位置的值是29
- 新添加数据大于父节点的值,满足if条件退出循环
- 设置数组k(2号位)号位置的值为当前入参33
-
第4个数据50入队列(k=3 ,x=50)
- 算术右移获取父节点位置1
- 父节点位置的值是39
- 新添加数据大于父节点的值 ,满足if条件退出循环
- 设置数组k(3号位)号位置的值为当前入参50
-
第5个数据57 入队列(k=4 ,x=57)
- 算术右移获取父节点位置1
- 父节点位置的值是39
- 新添加数据大于父节点的值 ,满足if条件退出循环
- 设置数组k(4号位)号位置的值为当前入参57
-
第6个数据3 入队列(k=5 ,x=3)
- 算术右移获取父节点位置2
- 父节点位置的值是33
- 新添加数据小于父节点的值 ,不满足if条件退出循环
- 父节点的值设置到数组k(这时候是5)号位值
- k 重新设值(前面拿到的父节点位置2)
- 继续循环查询k(这时候是2)的父节点位置为0
- 2号位父节点的值是29,依然不满足if条件,父节点的值(这里是29)设置到数组k(这时候是2)号位值
- k重新设置成0
- 退出循环设置数组k(这时候是0)号位置的值为当前入参3
-
第7个数据78入队列 (k=6 ,x=78)
- 算术右移获取父节点位置2
- 父节点位置的值是29
- 新添加数据大于父节点的值 ,满足if条件退出循环
- 设置数组k(6号位)号位置的值为当前入参78
用树来表示就是这样
正好满足父节点的左右子节点都大于它的规则,这样也就保证了堆最顶端的元素是最小的(可以理解最小就是优先级最高)
跟代码运行结果一致
五 、删除 POLL流程
PriorityQueue提供了这两个方法,这两个都是会删除队列里面的数据,就是有可能让PriorityQueue的二叉堆结构失效
还是上面的例子,poll删除了PriorityQueue内部数组的第一个数据3,就需要有一个最小的数据补上了,不然后面再执行poll操作拿不到数据
另外如果删除了内部数组的第2个数据29 这个位置也要补一个数据上来 ,总体来说 PriorityQueue队列删除跟poll操作后会从队列里面其它位置挪一个数来填充删除元素的位置,这个对应的就是siftDownComparable
方法
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;
}
这里有两个入参 k代表删除数据在数组中的位置 x代表原来数组中的最后一个元素(在进入这个方法前 先获取到最后一个元素 ,然后释放最后一个数据的空间,本身就删除了一个数据,所以就释放数组最后一个元素的空间 ,然后安装规则判断 原来最后一个元素需要补位到哪里)
这里面的流程是这样的,我么就已poll流程为例
这时候siftDownComparable方法的入参是0 78
左右节点位置分别是1 跟 2 ,按照if里面的规则右节点的值更小,所以那右节点的值跟最后一个数据比较,看谁替换到已经删除的0号位,这里显然是2号位的子节点29
这个时候K设置成2,再次获取左右子节点 是 5 跟 6 ,因为6等于减1后的数组长度,所以这里直接拿5号位的元素跟 原来最后一个元素比较,还是5号位的子节点数据小点 这时K号位的值重新设置成33
K设置成5 不满足小于层数的条件退出循环,数组5号为重新设值78(原来最后一个元素)
删除后的队列的树状图
跟代码运行结果一致