优先级队列(PriorityQueue)_1_模拟实现优先级队列
1、概念
2、优先级队列的模拟实现
JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。
2.1 堆的概念
堆在逻辑上是一棵完全二叉树,物理上存储在数组中,满足根比子树的节点大称为大根堆,反之,根比子树节点小称为小根堆。堆的基本作用是:快速找到集合中的最值。
2.2 堆的存储方式

将元素存储在数组中后可以根据二叉树章节(二叉树 1-CSDN博客)讲解过的性质5进行还原。
假设i为节点在数组的下标,则有:
1、若i==0,则i下标的节点是整棵树的根节点,否则i的双亲节点为(i-1)/2。
2、若i有左孩子,则左孩子下标为2*i+1。
3、若i有右孩子,则右孩子下标为2*i+2。
2.3 堆的创建
这是创建堆的所需成员变量和构造方法和堆的初始化:
public class TestHeap {
private int[] elem;
private int usedSize;
public TestHeap() {
this.elem = new int[10];
}
public void initHeap(int[] array){
for (int i = 0; i < elem.length; i++) {
elem[i] = array[i];
usedSize++;
}
}
下面我们将创建一个大根堆:
如下图,将一棵普通的完全二叉树,调整为了大根堆,其方法为,找到最下面的一棵子树,然后将其根结点与子树进行比较,调整每一棵子树根结点的位置,该方法称为向下调整。
首先我们找到最后一个父亲结点,需要用到我们刚才讲得性质5,公式为:(数组长度-1(也就是最后一个叶子节点)-1)/2。
调整过程:设p是最后一个父亲结点的下标,那么p--就可以把每棵子树调整结束,再设置一个c来获取左右子树的最大值,根据大小根堆的性质分别进行调整。
public void creatHeap(){
for (int parent = (usedSize-1-1) / 2; parent >= 0 ; parent--) {
shiftDown(parent,usedSize);
}
}
向下调整的方法:
private void shiftDown(int parent,int usedSize){
int child = (2*parent)+1;//左孩子
//判断左孩子是否合法
while(child < usedSize){
if(child+1 < usedSize && elem[child] < elem[child+1]){//右孩子是否合法
child++;
}
//child一定是左右孩子最大值的下标
if(elem[child] > elem[parent]) {
swap(child, parent);
parent = child;
child = 2*parent+1;
}else{
//本身已经是大根堆了
break;
}
}
}
//交换元素
private void swap(int i,int j){
int tmp = elem[i];
elem[i] = elem[j];
elem[j] = tmp;
}
向下调整创建堆的时间复杂度是O(N),如果使用向上调整时间复杂度是O(n*log2n)。
下面是向下调整时间复杂的计算过程:
2.4 堆的插入
插入要考虑两件事:
1. 将元素往哪里放?(堆是否满? -- > 扩容)
2. 放进去怎么放到合适的位置?
将新插入的元素放入到最底层的空间中,空间不够的时候需要扩容,然后将最后插入的结点按照堆的性质向上调整。
public void offer(int val){
if(isFull()){
this.elem = Arrays.copyOf(elem,2*elem.length);
}
this.elem[usedSize] = val;//useSize=0
//向上调整
shiftUp(usedSize);
usedSize++;
}
private void shiftUp(int child) {
int parent = (child-1)/2;
while(child > 0) {
if (elem[child] > elem[parent]) {
swap(child,parent);
child = parent;
parent = (child-1)/2;
} else {
break;
}
}
}
时间复杂度:O(log2N)。
2.5 堆的删除
注意:这里删除的一定是堆顶元素。
1、将堆顶元素和最后一个元素交换位置。
2、useSized--。
3、对堆顶元素进行向下调整。
代码如下:
public int poll(){
int tmp = elem[0];
//0下标元素和最后一个元素交换
swap(0,usedSize-1 );
//向下调整0~usedsize-1
usedSize--;//删除
shiftDown(0,usedSize);
return tmp;
}
private void shiftDown(int parent,int usedSize){
int child = (2*parent)+1;//左孩子
//判断左孩子是否合法
while(child < usedSize){
if(child+1 < usedSize && elem[child] < elem[child+1]){//右孩子是否合法
child++;
}
//child一定是左右孩子最大值的下标
if(elem[child] > elem[parent]) {
swap(child, parent);
parent = child;
child = 2*parent+1;
}else{
//本身已经是大根堆了
break;
}
}
}
//交换元素
private void swap(int i,int j){
int tmp = elem[i];
elem[i] = elem[j];
elem[j] = tmp;
}
时间复杂度:O(log2N)。
2.6 获取堆顶元素
public int peek(){
return this.elem[0];
}
}