优先级队列(堆)
1. 二叉树的顺序存储
1.1 存储方式
使用数组保存二叉树结构:将二叉树用层序遍历的方式放入数组中
一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费
这种方式的主要用法就是堆的表示
1.2 下标关系
已知parent的下标,则:
left下标:2parent+1
right下标:2parent+2
已知孩子(不区分左右)下标:
parent下标:(child-1/2)
2. 堆
2.1概念
- 堆逻辑上是一颗完全二叉树
- 堆物理上是保存在数组中
- 满足任意节点的值都大于其子树中节点的值,叫做大堆(大根堆,最大堆)
- 反之,则是小堆(小根堆,小堆)
- 堆的基本作用:快速找集合中的最值
2.2建堆 和 向下调整(以大堆为例)
前提:左右子树必须已经是一个堆,才能调整
public class TestHeap{
public int [] elem;
public int usedSize;
public TestHeap(){
this.usedSize = usedSize;
}
//建大堆
public void creatHeap(int [] array){
for(int i = 0;i < array.length;i++){
this.elem[0] = array[i];
this.usedSize++;
}
//最后一个子树根节点的位置:(array.length-1-1)/2
for(int parent = (array.length-1-1)/2;parent>=0;parent--){
adjustDown(parent,this.usedSize);
}
}
//向下调整
public void adjustDown(int root,int len){//root:子树的根节点 len:数组大小
int parent = root;
int child = 2*child+1;
while(child < len){
//找到最后一个子树
//child+1小于len 说明有右孩子 并且当左孩子的值小于右孩子的值时进入if语句
if( (child + 1 ) < len && this.elem[child] < this.elem[child + 1]){
child = child + 1;
}
//如果没有右孩子则什么也不用做
//此时child的值就是左右孩子的最大值
if(this.elem[child] > this.elem[parent]){
//如果孩子节点的值大于根节点,就交换
int tmp = this.elem[child];
this.elem[child] = this.elem[parent];
this.elem[parent] = tmp;
parent = child;//交换之后,再继续向下
child = parent*2+1;
}else{
break;//如果根节点大于孩子节点 退出循环
}
}
}
}
时间复杂度:O(N)
3. 堆的应用-优先级队列
3.1 入队列(以大堆为例)
public void push(int val){
//判满
if(isFull()){
this.elem = Arrays.copyOf(this.elem,this.elem.length*2);
}
//尾插 放在数组的最后一个位置
this.elem[this.usedSize] = val;
usedSize++;
//调用向上调整的函数,调整为大堆
adJustUp(this.usedSize-1);
}
public void adJustUp(int child){
int parent = (child-1) / 2;
while(child > 0){
if(this.elem[child] > this.elem[parent]){
int tmp = this.elem[child];
this.elem[child] = this.elem[parent];
this.elem[parent] = tmp;
child = parent;
parent = (child-1) / 2;
}else{
break;
}
}
}
public boolean isFull(){
return this.usedSize == this.elem.length;
}
3.2 弹出队首元素
public void pop(){
if(isEmpty()){
return ;
}
//将队首元素和队尾元素交换
int tmp = this.elem[0];
this.elem[0] = this.elem[this.usedSize -1 ];
this.elem[this.usedSize-1] = tmp;
this.usedSize--;
//调用向下调整方法,调整为大堆
adjustDown(0,this,usedSize);
}
public boolean isEmpty(){
return this.usedSize == 0;
}
3.3 得到队首元素
public int peek(){
if(isEmpty()){
throw new RuntimeException("队列为空");
}
return this.elem[0];
}
4. topK问题
一组数据,找前K个最大值(最小值),可以建立一个大小为K的小堆(大堆)。
public static void main(String [] args){
int [] array = {3,6,9,8,5,2,1,4,7};
topK(array,3);
}
public static void topK(int [] array,int k){
//new一个大小为k,实现Comparator的优先级队列
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k,new Comparator<Integer>(){
@Override
//重写compare o2 - o1 建立大堆
public int compare(Integer o1, Integer o2) {
return o2 - o1;
//默认o1 - o2 建小堆
}
});
//遍历数组,将前K个元素放入堆中,其他元素如果小于堆顶元素,就放入对中
for(int i = 0; i < array.length ; i++){
if(maxHeap.size() < k){
maxHeap.offer(array[i]);
}else{
int top = maxHeap.peek();
if(array[i] < top){
maxHeap.poll();
maxHeap.offer(array[i]);
}
}
}
System.out.println(maxHeap);
}
每一个比堆顶元素小的元素都放入堆中,因为堆是大堆,所以堆顶元素是当前堆中的最大值,这样遍历下来,就将数组中3个最小的元素都放入了堆中,并且第3小的元素是堆顶元素