优先级队列 堆 PriorityQueue

本文详细介绍了优先级队列(堆)的基本概念,包括堆的分类(小根堆与大根堆)、向下调整算法、建堆过程。此外,还阐述了入队列(向上调整)和出队列的操作,为读者提供了一个完整的优先级队列实现框架。通过这些内容,读者将能够掌握如何在实际中应用堆数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

优先级队列(堆)


前言

1、掌握堆的概念和实现

2、掌握PriorityQueue的使用

一、概念

堆逻辑上是一课完全二叉树,是用二叉树的层序遍历方式放入数组中,也就是说是用顺序数组来存储,这样可以节省空间。

重要公式:

1、已知父亲节点小标是i

左孩子下标:2*i+1;

右孩子下标:2*i+2;

2、已知孩子节点是j

求父亲节点的下标:

父亲节点下标:(j-1)/2

image-20211025114044273

1.1 堆的分类

小堆:(小根堆,最小堆)父亲节点小于左右孩子节点,注意的是左右孩子没有大小关系;

image-20211025120331614

大堆:(大根堆,最大堆)父亲节点大于左右孩子节点,注意的是左右孩子没有大小关系;

image-20211025120345018

1.2向下调整

以向下调整实现堆的过程,以大根堆为例:

向下调整(堆1)

这里只演示了一半,完整版文件太大传不了😂😂😂😂,知道思想差不多也可以推了

向下调整,是每颗子树向下调整:

1、最开始调整的时候,是从最后一个子树开始调整;

2、有个很重要的公式是:找到最后一个子树的根节点

parent=(length-1-1)/2

image-20211026170639984

向下调整代码如下:

 //大根堆是向下调整
    public void shiftDown(int parent) {
        int chlid=2*parent+1;//之前推了孩子公式

        //会有循环,条件是child小于数组才能操作
        while(chlid<this.usedSize) {
            //判断有没有右孩子,然后比较左右孩子的大小
            if(chlid+1<this.usedSize && this.elem[chlid]<this.elem[chlid+1]) {
                chlid++;
            }
            //走到这,子数已经是最大值,剩下的就是交换
            if(this.elem[chlid]>this.elem[parent]) {
                int tmp=this.elem[parent];
                this.elem[parent]=this.elem[chlid];
                this.elem[chlid]=tmp;
                parent=chlid;
                chlid=2*parent+1;
            } else {
                break;
            }
        }
    }

时间复杂度:一般都是最坏的情况,是从根一路遍历底层,所以就是完全二叉树的高度O(log(n))

1.3 建堆

完整代码(大根堆):

public class TestHeap {
    public int[] elem;
    public int usedSize;

    public TestHeap() {
        this.elem=new int[10];

    }
    public void creatBigHeap(int[] array) {
        for(int i=0;i<array.length;i++) {
            this.elem[i]=array[i];
            this.usedSize++;
        }

        for(int i=(this.usedSize-1-1)/2;i>=0;i--) {
            shiftDown(i);
        }
    }
    //大根堆是向下调整
    public void shiftDown(int parent) {
        int chlid=2*parent+1;

        //会有循环,条件是child小于数组才能操作
        while(chlid<this.usedSize) {
            //判断有没有右孩子,比较左右孩子的大小
            if(chlid+1<this.usedSize && this.elem[chlid]<this.elem[chlid+1]) {
                chlid++;
            }
            //走到这,子数已经是最大值,剩下的就是交换
            //这是大根堆,小根堆大于小于号改变一下就行了
            if(this.elem[chlid]>this.elem[parent]) {
                int tmp=this.elem[parent];
                this.elem[parent]=this.elem[chlid];
                this.elem[chlid]=tmp;
                parent=chlid;
                chlid=2*parent+1;
            } else {
                break;
            }
        }
}

时间复杂度:向下调整+循环:O(n * log(n))

实际上是O(n)

想了解可以看这个链接:堆排序中建堆过程时间复杂度O(n)怎么来的?

二、入队列

思路:把入队列的元素,放到数组的最后一个位置,保证这个堆是大根堆或者是小根堆,先判断顺序数组满不满,满就扩容,然后实现向上调整

2.1 向上调整

思路:定义孩子节点,和父亲节点,新的元素与父亲节点比较,大就交换,然后更新child,使child=parent

image-20211026173551578

代码:

 public void shiftUp(int child) {
        int parent = (child-1)/2;
        while (parent >= 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;
            }
        }
    }

三、出队列

思路:因为是优先级队列,所以是出堆顶元素,用堆顶元素跟数组最后一个元素交换,再usedSize–,此时虽然交换完,但是他不是大根堆或者小根堆,所以用向下调整0下标这课树,来保证最后是大根堆。

image-20211026213007377

代码:

 public int poll() {
        if(isEmpty()) {//队列为空抛出异常
            throw new RuntimeException("队列为空!");
        }
     //交换
        int tmp = this.elem[0];
        this.elem[0] = this.elem[this.usedSize-1];
        this.elem[this.usedSize-1] = tmp;
        this.usedSize--;
     //向下调整
        shiftDown(0);
        return tmp;
    }
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸢也

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值