【数据结构】(9) 优先级队列(堆)

一、优先级队列

        优先级队列不同于队列,队列是先进先出,优先级队列是优先级最高的先出。一般有两种操作:返回最高优先级对象添加一个新对象

二、堆

2.1、什么是堆

        堆也是一种数据结构,是一棵完全二叉树,该完全二叉树中的所有子树的根都大于其孩子,即大根堆;如果都小于其孩子,就是小根堆。

2.2、堆的存储方式

        由于完全二叉树按层序遍历,节点之间没有空隙,所以存储在顺序表中不会造成空间浪费;并且顺序存储方便访问二叉树中某节点 i 的双亲(i-1)/2和孩子左:2*i+1,右:2*i+2),以便调整堆。因此,堆采用顺序结构存储。

        大根堆示例:

三、优先级队列(堆)的模拟实现

       PriorityQueue 底层是用堆实现的。

3.1、将完全二叉树调整为堆(向下调整)

3.1.1、手动模拟过程

调整下面的完全二叉树为堆。

        从最后一个父节点开始调整(parent=((usedSize-1)-1)/2):将较大的孩子 child 与父节点比较(没有右孩子不需要比较孩子大小),若 child 更大则于父节点交换。

        调整倒数第 2 棵子树(parent--)。

        调整倒数第 3 棵子树。

        调整倒数第 4 棵子树。

        如果p和c交换了,还要调整其子树(p=c),直到 p 和 c 没有交换为止,或者 c 超过二叉树大小 usedSize 为止。(向下调整)

        重复上述操作,直到调整完 p=0 的树。

3.1.2、代码实现

        向下调整过程,时间复杂度推导:如果某棵子树高为 k,则最多交换 k-1 次,高为 k 的完全二叉树最多有 N=(2^k)-1 个结点,因此 k-1 = (log(N+1)-1),时间复杂度为 O(logN)

    /**
     * 向下调整
     * 时间复杂度:O(logN)
     * @param parent 子树根节点的下标
     * @param size 子树的大小
     */
    private void shiftDown(int parent, int size) {
        int maxChild = 2*parent+1; // 默认最大孩子为左孩子
        while(maxChild < size) { // 存在左孩子就继续调整
            if(maxChild+1 < size && arr[maxChild+1] > arr[maxChild]) { // 存在右孩子且右孩子大于左孩子
                maxChild++;
            }
            if (arr[maxChild] > arr[parent]) { // 最大孩子大于父节点,交换
                swap(parent, maxChild);
                parent = maxChild; // 继续调整子树
                maxChild = 2*parent+1;
            }
            else { // 没有交换,结束调整
                break;
            }
        }
    }

        将完全二叉树调整为堆,时间复杂度推导:

T=1*(h-1)+2^1*(h-2)+……+2^(h-2)*1。

2T=2^1*(h-1)+2^2*(h-2)+……+2^(h-1)*1。

2T-T=T=2^1+2^2+……+2^(h-2)+2^(h-1)-h+1=2^h-1-h=2^log(N+1)-1-log(N+1)=N-log(N+1)

时间复杂度为 O(N)

    /**
     * 将一棵完全二叉树调整为大根堆
     * 时间复杂度:O(N)
     */
    public void shift2Heap() {
        int initParent = ((usedSize - 1)-1)/2;
        for (int i = initParent; i >= 0; i--) {
            shiftDown(i, usedSize);
        }
    }

        测试结果:

3.2、堆中插入一个新节点(向上调整)

        另一种创建堆的方法是,每插入一个新节点,就调整二叉树为堆。

3.2.1、手动模拟过程

        在结尾插入新结点80,并从最后一棵子树开始(child=usedSize-1),从下往上调整,直到 parent=0,或者没有交换为止。(向上调整)

        child=parent。

3.2.2、代码实现

        向上调整:

    /**
     * 向上调整
     * 时间复杂度:O(logN)
     * @param child 添加的孩子节点的下标
     */
    private void shiftUp(int child) {
        int parent = (child-1)/2; // 父节点下标
        while(parent >= 0 && arr[child] > arr[parent]) { // 父节点存在且孩子大于父节点,交换
            swap(child, parent);
            child = parent;
            parent = (child-1)/2; // 继续向上调整
        }
    }

        插入一个新节点:

    /**
     * 添加元素到堆中
     * 时间复杂度:O(NlogN)
     */
    public void offer(int val) {
        if (isFull()) { // 数组已满,扩容
            arr = Arrays.copyOf(arr, arr.length * 2);
        }
        arr[usedSize] = val;
        usedSize++;
        shiftUp(usedSize-1); // 向上调整
    }

        测试:

        如果插入 N 个结点,时间复杂度推导:

T=2*1+2^2*2+……+2^(h-2)*(h-2)+2^(h-1)*(h-1)

2T=2^2+2^3*2+……+2^(h-1)*(h-2)+2^h*(h-1)

2T-T=T=2^h*(h-1)-2^2-2^3-……-2^(h-1)-2=2^h*(h-1)-2*(2^(h-1)-1)

=2^h*(h-1)-2^h+2=2+2^h*(h-2)+2=(N+1)*(log(N+1)-2)+2

时间复杂度为:O(NlogN)

3.3、删除堆顶元素

        步骤

  • 堆顶与堆尾元素交换
  • 删除堆尾元素。
  • 对堆从树根开始向下调整
    public int poll() {
        if (isEmpty()) {
            return -1;
        }
        int ret = arr[0];
        arr[0] = arr[usedSize-1];
        usedSize--;
        shiftDown(0, usedSize); // 向下调整
        return ret;
    }

四、PriorityQueue 的使用

4.1、注意事项

  • PriorityQueue 是线程不安全的,PriorityBlockingQueue 是线程安全的。
  • PriorityQueue 中放置的元素必须是可比较大小的,否则会抛出异常。
  • 不能插入 null,否则会抛出异常。

        而我们之前学习的 LinkList 是可以插入 null 的:

  • 默认构建小根堆。

4.2、构造函数的使用

        无参构造器:默认容量 11,默认比较器为空。

        带初始容量参数的构造器:指定初始容量,默认比较器为空。

        用一个集合创建优先级队列:传入某集合对象。

        因为 String 不是 Number 及其子类,所以语法错误。

        Integer 是 Number 的子类,成功调用。

        指定初始容量、比较器的构造函数

        transient 的作用:让不想被序列化的属性不被序列化,保证信息的隐私,比如密码。序列化就是把对象的信息转成字符串放到磁盘里;反序列化就是把磁盘里的字符串转成对象信息。transient 就让修饰的属性信息的生命周期仅在调用者的内存中而不会写进磁盘中持久化

4.3、offer 插入一个元素

        插入一个元素,offer 源码:

        扩容,grow 源码:

        向上调整,shiftUp 源码:

        如果元素是基础类型的包装类,会使用自身重写的 compareTo:

        如果构造函数参数指定了比较器:

4.4、poll 删除堆顶元素

        删除堆顶元素,poll 源码:

        可以看到源码的向下调整的方法,与我们实现的方法大致相同。

五、OJ练习

5.1、top-k 问题:最小的 k 个数

面试题 17.14. 最小K个数 - 力扣(LeetCode)

  • 解法1:用排序算法,从小到大排序,取前 k 个。最快的排序算法时间复杂度 O(NlogN)。
  • 解法2:创建小根堆,取 k 次堆顶元素,每次删除堆顶元素后,向下调整。时间复杂度 O(NlogN)
    /**
     * 方法1:使用小根堆实现topK小,
     * 时间复杂度:O(NlogN) + O(k*logN) = O(NlogN)
     */
    public static int[] topK1(int[] arr, int k) {
        if(k > arr.length) { // k大于数组长度,直接返回数组
            return arr;
        }

        if(k <= 0) { // k小于等于0,返回空数组
            return new int[0];
        }

        // 创建一个小根堆,O(NlogN)
        PriorityQueue<Integer> pq = new PriorityQueue<>(k);
        for (int x : arr) {
            pq.offer(x);
        }
        // 取k次堆顶元素,存入数组,O(k*logN)
        int[] ret = new int[k];
        for (int i = 0; i < k; i++) {
            ret[i] = pq.poll();
        }
        return ret;
    }
  • 解法三:创建大小为 k 的大根堆,从 k 开始遍历数组,遇到比堆顶(堆中的最大值)小的 x,就删除堆顶,插入 x。效率最高,O(N*logk)
    /**
     * 方法2:使用大根堆实现topK小,
     * 时间复杂度:O(k*logk) + O((N-k)*logk) + O(k*logk) = O(N*logk)
     */
    public static int[] topK2(int[] arr, int k) {
        if(k > arr.length) { // k大于数组长度,直接返回数组
            return arr;
        }
        if(k <= 0) { // k小于等于0,返回空数组
            return new int[0];
        }

        // 自定义比较器,实现大根堆
        Comparator<Integer> cmp = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        };

        // 创建一个大小为 k 的大根堆,O(k*logk)
        PriorityQueue<Integer> pq = new PriorityQueue<>(k, cmp);
        for (int i = 0; i < k; i++) {
            pq.offer(arr[i]);
        }

        // 遍历数组,如果元素小于堆顶元素,替换堆顶元素,并调整堆,O((N-k)logk)
        for (int i = k; i < arr.length; i++) {
            if (arr[i] < pq.peek()) {
                pq.poll();
                pq.offer(arr[i]);
            }
        }
        // 取出堆中的元素,存入数组,O(k*logk)
        int[] ret = new int[k];
        for (int i = 0; i < k; i++) {
            ret[i] = pq.poll();
        }
        return ret;
    }

5.2、堆排序

将序列从小到大排序:

  • 方法1:创建小根堆,每次取堆顶,放入一个新的数组中。

空间复杂度:O(N),创建了一个新数组存放结果。当数据很多时,浪费内存。

时间复杂度:O(NlogN)。

  • 方法2:创建大根堆,执行 N-1 次循环,每次将堆顶最大元素与未排序的堆尾元素交换,交换后对未排序的部分进行调整。

空间复杂度:O(1)

时间复杂度:O(NlogN)

        初始状态:

        第一次交换,并调整:

        第二次交换,并调整:

    // 堆排序
    public void sort() {
        for (int i = usedSize-1; i > 0; i--) {
            // 交换堆顶和最后一个未排序的元素
            swap(0, i);
            // 向下调整堆,元素 i-1 已排序
            shiftDown(0, i-1);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/970990.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI大模型的文本流如何持续吐到前端,实时通信的技术 SSE(Server-Sent Events) 认知

写在前面 没接触过 SSE&#xff08;Server-Sent Events&#xff09;&#xff0c;AI大模型出来之后&#xff0c;一直以为文本流是用 WebSocket 做的偶然看到返回到报文格式是 text/event-stream,所以简单认知&#xff0c;整理笔记博文内容涉及 SSE 认知&#xff0c;以及对应的 D…

计时器任务实现(保存视频和图像)

下面是一个简单的计时器任务实现&#xff0c;可持续地每秒保存一幅图像&#xff0c;也可持续地每60秒保存一个视频&#xff0c;图像和视频均以当前时间命名&#xff1a; TimerTask类的实现如下&#xff1a; class TimerTask { public:TimerTask(const std::string& path):…

力扣19题——删除链表的倒数第 N 个结点

#题目 #代码 //定义虚拟头结点ListNode curnew ListNode(0,head); //定义两个指针指向虚拟头结点ListNode lcur;ListNode rcur;for(int i0;i<n;i){rr.next;}while(r!null){rr.next;ll.next;} //l.next就是我们要删除的那个元素if(l.next!null){l.nextl.next.next;}return c…

网络工程师 (42)IP地址

一、定义与功能 IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一个逻辑地址&#xff0c;以此来屏蔽物理地址的差异。这种地址分配方式确保了用户在连网的计算机上操作时&#xff0c;能够高效且方便地从众多计算机中选出自己所需…

记忆力训练day19

万能字母组合编码法 所有的文字和字母的背后都有画面 练的不是记单词&#xff0c;练的是注意力给到单词&#xff0c;出什么画面&#xff0c;然后画面与画面之间进行连接 拆的过程就是找熟词的过程 要关注自己的回忆路径是什么&#xff1f;也就是你是怎么回忆起来的&#xff0c…

flutter image_cropper插件安装后 打包apk 报错命名空间问题

本篇文章主要讲解&#xff0c;Flutter安装完新依赖打包apk报错 A problem occurred configuring project ‘:image_cropper’. 命名空间问题的解决办法及原因说明。 日期&#xff1a;2025年2月15日 作者&#xff1a;任聪聪 一、报错现象&#xff1a; 报文信息&#xff1a; FAI…

八、SPI读写XT25数据

8.1 SPI 简介 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是一种同步串行通信协议&#xff0c;广泛用于嵌入式系统中连接微控制器与外围设备&#xff0c;如传感器、存储器、显示屏等。 主要特点 1. 全双工通信&#xff1a;支持同时发送…

kibana es 语法记录 elaticsearch

目录 一、认识elaticsearch 1、什么是正向索引 2、什么是倒排索引 二、概念 1、说明 2、mysql和es的对比 三、mapping属性 1、定义 四、CRUD 1、查看es中有哪些索引库 2、创建索引库 3、修改索引库 4、删除索引库 5、新增文档 6、删除文档 5、条件查询 一、认识…

三、Unity基础(主要框架)

一、Unity场景概念 如果把游戏运行过程理解成表演&#xff0c;那么场景就是舞台&#xff1b; 场景本质上是一个配置文件&#xff0c;这个配置文件决定了场景中有哪些东西&#xff1b; 二、Scene和Game窗口 1、Scene 滚轮缩放、拖动 单独选中也可以 最下面这个是全能工具…

pdf文档提取信息

目录 一、前言二、核心代码说明1、PyPDF2提取文本2、pdfplumber提取文本和表格3、fitz提取文本和图片4、fitz按页提取图片一、前言 本博客文章介绍pdf的文本、图片、表格等信息提取的技术方案对比。目前比较熟知的是pdfplumber 、PyPDF2 、fitz(PyMuPDF)。 它们之间对比如下 …

Git指南-从入门到精通

代码提交和同步命令 流程图如下&#xff1a; 第零步: 工作区与仓库保持一致第一步: 文件增删改&#xff0c;变为已修改状态第二步: git add &#xff0c;变为已暂存状态 bash $ git status $ git add --all # 当前项目下的所有更改 $ git add . # 当前目录下的所有更改 $ g…

我们来学HTTP/TCP -- 三次握手?

三次握手 题记三次呼叫结语 题记 来&#xff0c;我们来演示下川普王和普京帝会面了 哎呦&#xff01;你好你好&#xff0c;握手…哎嗨&#xff01;侬好侬好&#xff0c;握手…欧嘿呦玛斯&#xff0c;握手… 抓狂啊&#xff01;作孽啊!!! 不说人话啊! 关键的是&#xff0c;“三…

kubectl top输出与Linux free命令不一致原因?

当你在 Kubernetes 集群中使用 kubectl top 命令查看资源使用情况时&#xff0c;可能会发现与在节点上直接运行 Linux free 命令得到的结果不一致。这种不一致可能源于多个原因&#xff0c;以下是一些关键因素&#xff1a; MobaXterm中文版下载&#xff1a; https://pan.quark…

【设计模式】【行为型模式】迭代器模式(Iterator)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…

论文解读之DeepSeek R1

今天带来DeepSeek R1的解读 一、介绍 deepseek主打复杂推理任务&#xff0c;如数学、代码任务。 R1以预训练过的V1-base初始化&#xff0c;主要发挥了RL在长思维链上的优势&#xff0c;R1-Zero直接RL而在前置步骤中不进行SFT&#xff0c;即缺少了有监督的指令微调阶段&#…

Linux:用 clang 编译带 sched_ext 功能内核

文章目录 1. 前言2. 编译过程2.1 准备内核源代码2.2 安装编译工具2.3 配置、编译、运行2.3.1 配置2.3.2 编译2.3.3 运行 3. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 编译过程 …

FPGA之​​​​​​​​​​​​​​HRBANK与HOBANK有什么区别?

在FPGA设计中&#xff0c;HP Bank&#xff08;High-Performance Bank&#xff09;与HR Bank&#xff08;High-Range Bank&#xff09;是针对I/O电气特性划分的不同区域&#xff0c;二者的主要区别在于支持的电压范围、信号速率以及应用场景。以下是具体对比&#xff1a; 核心区…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_ssl_init 函数

#if (NGX_OPENSSL)ngx_ssl_init(log); #endif objs/ngx_auto_config.h 中 #ifndef NGX_OPENSSL #define NGX_OPENSSL 1 #endif 所以这个条件编译成立 NGX_OPENSSL 是一个宏定义&#xff0c;用于控制与 OpenSSL 相关的功能是否被启用 若用户通过./configure参数&#xff08;如-…

pandas(13 Caveats Gotchas和SQL比较)

前面内容&#xff1a;pandas(12 IO工具和稀松数据) 目录 一、Caveats警告 & Gotchas预见 1.1 在Pandas中使用if/Truth语句 1.2 位运算布尔 1.3 isin操作 1.4 重新索引reindex和 loc&iloc 使用注意事项 1.5 loc和iloc 二、Python Pandas 与SQL的比较 2.1 数…

MongoDB 7 分片副本集升级方案详解(下)

#作者&#xff1a;任少近 文章目录 1.4 分片升级1.5 升级shard11.6 升级shard2,shard31.7 升级mongos1.8重新启用负载均衡器1.9 推荐MongoDB Compass来验证数据 2 注意事项&#xff1a; 1.4 分片升级 使用“滚动”升级从 MongoDB 7.0 升级到 8.0&#xff0c;即在其他成员可用…