当前位置: 首页 > news >正文

【数据结构】(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>() {@Overridepublic 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);}}

相关文章:

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

一、优先级队列 优先级队列不同于队列&#xff0c;队列是先进先出&#xff0c;优先级队列是优先级最高的先出。一般有两种操作&#xff1a;返回最高优先级对象&#xff0c;添加一个新对象。 二、堆 2.1、什么是堆 堆也是一种数据结构&#xff0c;是一棵完全二叉树&#xff0c…...

如何提升爬虫获取数据的准确性?

提升爬虫获取数据的准确性是确保数据分析和后续应用有效性的关键。以下是一些经过验证的方法和最佳实践&#xff0c;可以帮助提高爬虫数据的准确性&#xff1a; 1. 数据清洗 数据清洗是提升数据准确性的重要步骤&#xff0c;主要包括去除重复数据、处理缺失值和异常值。 去除…...

Obsidian及Zotero常用的插件

Obsidian插件 Minimal Theme Settings&#xff08;Life&#xff0c;zotero&#xff09;【必需】 界面样式设置所需插件 Style Settings&#xff08;Life&#xff0c;zotero&#xff09;【必需】界面样式设置所需插件 Recent Files&#xff08;Life&#xff0c;zotero&#xf…...

闲鱼IP属地是通过电话号码吗?

在闲鱼这样的二手交易平台上&#xff0c;用户的IP属地信息对于维护交易安全、增强用户间的信任至关重要。然而&#xff0c;关于闲鱼IP属地是如何确定的&#xff0c;不少用户存在疑惑&#xff0c;尤其是它与电话号码之间是否存在关联。本文将深入探讨这一问题&#xff0c;揭示闲…...

C#多线程异步连接MySQL与SQLserver数据库

C#多线程异步连接MySQL与SQLserver数据库 一、前言二、多线程异步连接数据库代码2.1代码块2.2代码说明 参考文档 一、前言 当编写代码连接多台设备上的数据库时&#xff0c;如果采用同步逐个连接的方式&#xff0c;在网络畅通的情况下连接速度尚可&#xff0c;但当其中一台设备…...

51单片机-数码管

目录 1、静态数码管 1.1、数码管是如何显示出字符 1.2、数码管静态显示原理 1.3、74HC573芯片的使用 1.4、静态数码管编程 2、动态数码管 2.1、数码管动态显示原理 2.2、74HC138芯片的使用 2.3、编写动态数码管程序 1、静态数码管 1.1、数码管是如何显示出字符 单片机…...

C#学习之S参数读取(s2p文件)

目录 一、创作灵感 二、S2PFileReader类 1.代码示例 2.代码说明 a.ReadS2PFile 方法&#xff1a; b.DataTable 结构&#xff1a; 三、S2PFileReader类的调用演示 1.使用示例 一、创作灵感 虽然MATLAB处理数据很实用&#xff0c;但是C#常用于程控仪器的控制&#xff0c…...

Spring Boot “约定大于配置”

什么是“约定大于配置”&#xff1f; “约定大于配置”是一种简化开发的设计理念。简单来说&#xff0c;就是框架默认提供了常见的配置和行为&#xff0c;开发者只需要按照约定来编写代码&#xff0c;避免了繁琐的配置&#xff0c;只在需要时进行定制和调整。这种理念在Spring…...

传输层协议TCP ( 下 )

文章目录 前言序号与确认序号超时重传RTOJacobson算法内核中超时时间的计算 滑动窗口滑动窗口延迟应答流量控制 拥塞控制慢启动拥塞避免快重传快速恢复 保活机制参考资料 前言 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是互联网最重要…...

NLP 八股 DAY1:BERT

BERT全称&#xff1a;Pre-training of deep bidirectional transformers for language understanding&#xff0c;即深度双向Transformer。 模型训练时的两个任务是预测句⼦中被掩盖的词以及判断输⼊的两个句⼦是不是上下句。在预训练 好的BERT模型后⾯根据特定任务加上相应的⽹…...

演示synchronized锁机制用法的简单Demo

演示synchronized锁机制用法的简单Demo。我们以"银行开户"场景为例&#xff1a;每个用户只能创建一个账户&#xff08;模拟类似原代码中每个用户只能有一个私有空间的限制&#xff09;。 第1步&#xff1a;创建项目结构 demo-lock ├── src/main/java/com/exampl…...

Datawhale 数学建模导论二 笔记1

第6章 数据处理与拟合模型 本章主要涉及到的知识点有&#xff1a; 数据与大数据Python数据预处理常见的统计分析模型随机过程与随机模拟数据可视化 本章内容涉及到基础的概率论与数理统计理论&#xff0c;如果对这部分内容不熟悉&#xff0c;可以参考相关概率论与数理统计的…...

差分解方程

差分解方程 差分法在数值求解偏微分方程&#xff08;PDEs&#xff09;和常微分方程&#xff08;ODEs&#xff09;时&#xff0c;可以分为隐式格式和显式格式。以下是两者的主要区别&#xff1a; 显式格式&#xff08;Explicit Scheme&#xff09; 时间推进&#xff1a; 显式格…...

EasyExcel 复杂填充

EasyExcel ​Excel表格中用{}或者{.} 来表示包裹要填充的变量&#xff0c;如果单元格文本中本来就有{、}左右大括号&#xff0c;需要在括号前面使用斜杠转义\{ 、\}。 ​代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。 …...

ESP32通过MQTT连接阿里云平台实现消息发布与订阅

文章目录 前言 一、准备工作 二、阿里云平台配置 三、代码实现 总结 前言 本文将介绍如何使用ESP32开发板通过MQTT协议连接阿里云物联网平台&#xff0c;并实现消息的发布与订阅功能。我们将使用Arduino IDE进行开发&#xff0c;并借助PubSubClient库实现MQTT通信。 一、准备…...

NVIDIA Jetson Orin Nano 刷机过程

1. 背景 新到手 NVIDIA Jetson Orin Nano 插上显示屏&#xff0c;显示如下&#xff1a; 这是UEFI Shell&#xff0c;UEFI Shell&#xff08;统一可扩展固件接口外壳程序&#xff09;是一种基于UEFI规范的交互式命令行工具&#xff0c;它运行在UEFI固件环境中&#xff0c;为用…...

C#学习之数据转换

目录 一、创作说明 二、数据类型之间的转换 1.数据类型之间的转换表格 2.代码示例 三、进制之间的转换 1.进制之间的转换表格 2.代码示例 四、ASCII 编码和字符之间的转换 1.ASCII 编码和字符之间的转换表格 2.代码示例 五、总结 一、创作说明 C#大多数时候都是和各…...

typecho快速发布文章

typecho_Pytools typecho_Pytools工具由python编写&#xff0c;可以快速批量的在本地发布文章&#xff0c;不需要登陆后台粘贴md文件内容&#xff0c;同时此工具还能查看最新的评论消息。… 开源地址: GitHub Gitee 使用教学&#xff1a;B站 一、主要功能 所有操作不用登陆博…...

深度学习R4周:LSTM-火灾温度预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 任务&#xff1a; 数据集中提供了火灾温度&#xff08;Tem1&#xff09;、一氧化碳浓度&#xff08;CO 1&#xff09;烟雾浓度&#xff08;Soot 1&#xff09;…...

探索Java中的集合类_特性与使用场景

1. 引言 1.1 Java集合框架概述 Java集合框架(Java Collections Framework, JCF)是Java中用于存储和操作一组对象的类和接口的统称。它提供了多种数据结构来满足不同的需求,如列表、集合、映射等。JCF的核心接口包括Collection、List、Set、Queue和Map,以及它们的各种实现…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...