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

news2025/2/21 8:35:43

一、优先级队列

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

二、堆

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);
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4、IP查找工具-Angry IP Scanner

在前序文章中&#xff0c;提到了多种IP查找方法&#xff0c;可能回存在不同场景需要使用不同的查找命令&#xff0c;有些不容易记忆&#xff0c;本文将介绍一个比较优秀的IP查找工具&#xff0c;可以应用在连接树莓派或查找IP的其他场景中。供大家参考。 Angry IP Scanner下载…

【Linux】命令操作、打jar包、项目部署

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;Xshell下载 1&#xff1a;镜像设置 二&#xff1a;阿里云设置镜像Ubuntu 三&#xf…

瑞萨RA-T系列芯片ADCGPT功能模块的配合使用

在马达或电源工程中&#xff0c;往往需要采集多路AD信号&#xff0c;且这些信号的优先级和采样时机不相同。本篇介绍在使用RA-T系列芯片建立马达或电源工程时&#xff0c;如何根据需求来设置主要功能模块ADC&GPT&#xff0c;包括采样通道打包和分组&#xff0c;GPT触发启动…

Unity Shader学习6:多盏平行光+点光源 ( 逐像素 ) 前向渲染 (Built-In)

0 、分析 在前向渲染中&#xff0c;对于逐像素光源来说&#xff0c;①ForwardBase中只计算一个平行光&#xff0c;其他的光都是在FowardAdd中计算的&#xff0c;所以为了能够渲染出其他的光照&#xff0c;需要在第二个Pass中再来一遍光照计算。 而有所区别的操作是&#xff0…

tailwindcss学习01

系列教程 01 入门 02 vue中接入 入门 # 注意使用cmd不要powershell npm init -y # 如果没有npx则安装 npm install -g npx npm install -D tailwindcss3.4.17 --registry http://registry.npm.taobao.org npx tailwindcss init修改tailwind.config.js /** type {import(tai…

DIN:引入注意力机制的深度学习推荐系统,

实验和完整代码 完整代码实现和jupyter运行&#xff1a;https://github.com/Myolive-Lin/RecSys--deep-learning-recommendation-system/tree/main 引言 在电商与广告推荐场景中&#xff0c;用户兴趣的多样性和动态变化是核心挑战。传统推荐模型&#xff08;如Embedding &…

【前端】如何安装配置WebStorm软件?

文章目录 前言一、前端开发工具WebStorm和VS Code对比二、官网下载三、安装1、开始安装2、选择安装路径3、安装选项4、选择开始菜单文件夹5、安装成功 四、启动WebStorm五、登录授权六、开始使用 前言 WebStorm 是一款由 JetBrains 公司开发的专业集成开发环境&#xff08;IDE…

【Golang学习之旅】Go 语言微服务架构实践(gRPC、Kafka、Docker、K8s)

文章目录 1. 前言&#xff1a;为什么选择Go语言构建微服务架构1.1 微服务架构的兴趣与挑战1.2 为什么选择Go语言构建微服务架构 2. Go语言简介2.1 Go 语言的特点与应用2.2 Go 语言的生态系统 3. 微服务架构中的 gRPC 实践3.1 什么是 gRPC&#xff1f;3.2 gRPC 在 Go 语言中的实…

Spring核心思想之—AOP(面向切面编程)

目录 一 .AOP概述 二. Spring AOP 使用 2.1 引入AOP依赖 2.2 编写AOP程序 三. Spring AOP详情 3.1 切点(Pointcut) 3.2 连接点(Join Point&#xff09; 3.3通知&#xff08;Advice&#xff09; 3.4切面(Aspect) 3.5通知 3.6 PointCut &#xff08;公共切点&#xff09;…

关于使用雪花算法生成唯一ID,返回给前端ID不一致的问题

问题 在某个项目中,使用雪花算法生成的唯一ID,从数据库查询到数据后返回给前端,但是前端接受到的数据ID和数据库原先生成的不一致 但是前端展示的数据: 原因 原因是后端使用Long类型来存储雪花算法生成的ID,但是这个数值已经超过前端数值类型的范围,导致前端在存储这个数值…

axios post请求 接收sse[eventsource]数据的

axios 接收sse数据的 axios 接收sse数据的 EventSource什么 基于 HTTP 协议实现&#xff0c;通过与服务器建立一个持续连接&#xff0c;实现了服务器向客户端推送事件数据的功能。在客户端&#xff0c;EventSource 对象通过一个 URL 发起与服务器的连接。连接成功后&#xff0…

大语言模型常用微调与基于SFT微调DeepSeek R1指南

概述 大型语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;的微调&#xff08;Fine-tuning&#xff09;是指在一个预训练模型的基础上&#xff0c;使用特定领域或任务的数据对模型进行进一步训练&#xff0c;以使其在该领域或任务上表现更好。微调是迁移…

聚焦地灾防治,助力城市地质安全风险防控

城市是人类社会发展的重要载体&#xff0c;承载着经济繁荣、文化交流和人口聚集等重要功能。然而&#xff0c;由于城市建设过程中地质条件复杂&#xff0c;地质灾害风险隐患存在&#xff0c;城市地质安全等问题日益突出&#xff0c;引起人们的广泛关注。为保障城市发展的安全和…

为什么WP建站更适合于谷歌SEO优化?

在当今数字时代&#xff0c;建立一个网站似乎变得容易&#xff0c;但要构建一个真正能够带来流量和订单的网站却并非易事。特别是在谷歌SEO优化方面&#xff0c;不同的建站程序在SEO支持方面的效果差异显著。对于希望提升搜索引擎表现的用户来说&#xff0c;WordPress无疑是最佳…

用deepseek学大模型08-长短时记忆网络 (LSTM)

deepseek.com 从入门到精通长短时记忆网络(LSTM),着重介绍的目标函数&#xff0c;损失函数&#xff0c;梯度下降 标量和矩阵形式的数学推导&#xff0c;pytorch真实能跑的代码案例以及模型,数据&#xff0c; 模型应用场景和优缺点&#xff0c;及如何改进解决及改进方法数据推导…

(蓝桥杯——10. 小郑做志愿者)洛斯里克城志愿者问题详解

题目背景 小郑是一名大学生,她决定通过做志愿者来增加自己的综合分。她的任务是帮助游客解决交通困难的问题。洛斯里克城是一个六朝古都,拥有 N 个区域和古老的地铁系统。地铁线路覆盖了树形结构上的某些路径,游客会询问两个区域是否可以通过某条地铁线路直达,以及有多少条…

小胡说技书博客分类(部分目录):服务治理、数据治理与安全治理对比表格

文章目录 一、对比表格二、目录2.1 服务2.2 数据2.3 安全 一、对比表格 下表从多个维度对服务治理、数据治理和安全治理进行详细对比&#xff0c;为读者提供一个直观而全面的参考框架。 维度服务治理数据治理安全治理定义对软件开发全流程、应用交付及API和接口管理进行规范化…

开源模型应用落地-DeepSeek-R1-Distill-Qwen-7B-LoRA微调-LLaMA-Factory-单机单卡-V100(一)

一、前言 如今&#xff0c;大语言模型领域热闹非凡&#xff0c;各种模型不断涌现。DeepSeek-R1-Distill-Qwen-7B 模型凭借其出色的效果和性能&#xff0c;吸引了众多开发者的目光。而 LLaMa-Factory 作为强大的微调工具&#xff0c;能让模型更好地满足个性化需求。 在本篇中&am…

uni-app发起网络请求的三种方式

uni.request(OBJECT) 发起网络请求 具体参数可查看官方文档uni-app data:请求的参数; header&#xff1a;设置请求的 header&#xff0c;header 中不能设置 Referer&#xff1b; method&#xff1a;请求方法&#xff1b; timeout&#xff1a;超时时间&#xff0c;单位 ms&a…

EasyRTC:智能硬件适配,实现多端音视频互动新突破

一、智能硬件全面支持&#xff0c;轻松跨越平台障碍 EasyRTC 采用前沿的智能硬件适配技术&#xff0c;无缝对接 Windows、macOS、Linux、Android、iOS 等主流操作系统&#xff0c;并全面拥抱 WebRTC 标准。这一特性确保了“一次开发&#xff0c;多端运行”的便捷性&#xff0c…