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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025.2.15

web [HNCTF 2022 Week1]Interesting_include&#xff1a; 直接打开 PHP代码片段包含两部分&#xff1a;一个主脚本和一个潜在的被包含文件。主脚本负责处理GET请求&#xff0c;特别是filter参数&#xff0c;而被包含文件&#xff08;假设为./flag.php&#xff09;似乎包含了我…

CentOS 7.8 安装MongoDB 7教程

文章目录 CentOS 7.8 安装MongoDB 7教程一、准备工作1. 系统更新2. 权限 二、添加MongoDB软件源1. 创建MongoDB的yum源文件2. 添加以下内容3. 保存并退出编辑器 三、安装MongoDB1. 更新yum缓存2. 安装MongoDB 四、启动MongoDB服务1. 启动MongoDB2. 设置MongoDB开机自启动 五、配…

ElasticSearch基础和使用

ElasticSearch基础 1 初识ES相关组件 &#xff08;1&#xff09;Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。Elasticsearch结合kibana、Logstash、Beats组件 也就是elastic stack&#xff08;ELK&#xff09; 广泛应…

[C++]多态详解

目录 一、多态的概念 二、静态的多态 三、动态的多态 3.1多态的定义 3.2虚函数 四、虚函数的重写&#xff08;覆盖&#xff09; 4.1虚函数 4.2三同 4.3两种特殊情况 &#xff08;1&#xff09;协变 &#xff08;2&#xff09;析构函数的重写 五、C11中的final和over…

【git-hub项目:YOLOs-CPP】本地实现01:项目构建

目录 写在前面 项目介绍 最新发布说明 Segmentation示例 功能特点 依赖项 安装 克隆代码仓库 配置 构建项目 写在前面 前面刚刚实现的系列文章: 【Windows/C++/yolo开发部署01】 【Windows/C++/yolo开发部署02】 【Windows/C++/yolo开发部署03】 【Windows/C++/yolo…

在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档教程

既然我们已经在本地部署了DeepSeek,肯定希望能够利用本地的模型对自己软件开发、办公文档进行优化使用,接下来就先在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档的教程奉上。 前提: (1)已经部署好了DeepSeek,可以看我的文章:个人windows电脑上安装DeepSe…

安装 Docker Desktop 修改默认安装目录到指定目录

Docker Desktop安装目录设置 Docker Desktop 默认安装位置 &#xff08;C:\Program Files\Docker\Docker) 是这个 &#xff0c;导致系统盘占用过大&#xff0c;大概2G ; 那么如何安装到其他磁盘呢&#xff1f; 根据docker desktop 官网 Docker Desktop install 我们可以看到&a…

DeepSeek官方发布R1模型推荐设置

今年以来&#xff0c;DeepSeek便在AI领域独占鳌头&#xff0c;热度一骑绝尘。其官方App更是创造了惊人纪录&#xff0c;成为史上最快突破3000万日活的应用&#xff0c;这一成绩无疑彰显了它在大众中的超高人气与强大吸引力。一时间&#xff0c;各大AI及云服务厂商纷纷投身其中&…

常见的IP地址分配方式有几种:深入剖析与适用场景‌

在数字互联的世界里&#xff0c;IP地址如同网络世界的“门牌号”&#xff0c;是设备间通信的基础。随着网络技术的飞速发展&#xff0c;IP地址的分配方式也日趋多样化&#xff0c;以适应不同规模、不同需求的网络环境。本文将深入探讨当前主流的几种IP地址分配方式&#xff0c;…

NLP 八股 DAY1:BERT

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

Flutter_学习记录_动画的简单了解

用AnimationController简单实现如下的效果图&#xff1a; 1. 只用AnimationController实现简单动画 1.1 完整代码案例 import package:flutter/material.dart;class AnimationDemo extends StatefulWidget {const AnimationDemo({super.key});overrideState<AnimationDe…

sql sqlserver的特殊函数COALESCE和PIVOT的用法分析

一、COALESCE是一个返回参数中第一个非NULL值的函数&#xff0c; 列如&#xff1a;COALESCE&#xff08;a,b,c,d,e&#xff09;;可以按照顺序取abcde&#xff0c;中的第一个非空数据&#xff0c;abcde可以是表达式 用case when 加ISNULL也可以实现&#xff0c;但是写法复杂了…

类与对象C++详解(上)

目录 1.类的定义 1.1 类定义格式 补充: struct与class的区别&#xff08;c语言与c&#xff09; 1.2 访问限定符 1.3 类域 2.实例化 3.对象大小 4.this指针 1.类的定义 1.1 类定义格式 class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&…

LabVIEW 天然气水合物电声联合探测

天然气水合物被认为是潜在的清洁能源&#xff0c;其储量丰富&#xff0c;预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性&#xff0c;天然气水合物的探测面临诸多挑战&#xff0c;涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…

Windows上安装Go并配置环境变量(图文步骤)

前言 1. 本文主要讲解的是在windows上安装Go语言的环境和配置环境变量&#xff1b; Go语言版本&#xff1a;1.23.2 Windows版本&#xff1a;win11&#xff08;win10通用&#xff09; 下载Go环境 下载go环境&#xff1a;Go下载官网链接(https://golang.google.cn/dl/) 等待…

神经网络常见激活函数 9-CELU函数

文章目录 CELU函数导函数函数和导函数图像优缺点pytorch中的CELU函数tensorflow 中的CELU函数 CELU 连续可微指数线性单元&#xff1a;CELU&#xff08;Continuously Differentiable Exponential Linear Unit&#xff09;,是一种连续可导的激活函数&#xff0c;结合了 ELU 和 …

《安富莱嵌入式周报》第350期:Google开源Pebble智能手表,开源模块化机器人平台,开源万用表,支持10GHz HRTIM的单片机,开源CNC控制器

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1YPKEeyEeM/ 《安富莱嵌入式周报》第350期&#xff1a;Google开…

小米平板怎么和电脑共享屏幕

最近尝试使用小米平板和电脑屏幕分屏互联 发现是需要做特殊处理的&#xff0c;需要下载一款电脑安装包&#xff1a;小米妙享 关于这个安装包&#xff0c;想吐槽的是&#xff1a; 没有找到官网渠道&#xff0c;是通过其他网络方式查到下载的 不附录链接&#xff0c;原因是因为地…

(学习总结23)Linux 目录、通配符、重定向、管道、shell、权限与粘滞位

Linux 目录、通配符、重定向、管道、shell、权限与粘滞位 Linux 目录通配符重定向管道shell 介绍Linux 权限Linux 权限的概念用户切换命令 su Linux权限管理文件访问者的分类&#xff1a;常用文件类型与其标识符&#xff1a;文件基本权限和权限值的表示方法&#xff1a;更改文件…

深入解析操作系统控制台:阿里云Alibaba Cloud Linux(Alinux)的运维利器

作为一位个人开发者兼产品经理&#xff0c;我的工作日常紧密围绕着云资源的运维和管理。在这个过程中&#xff0c;操作系统扮演了至关重要的角色&#xff0c;而操作系统控制台则成为了我们进行系统管理的得力助手。本文将详细介绍阿里云的Alibaba Cloud Linux操作系统控制台的功…