堆,堆构建,堆排序,PriorityQueue和TopN问题

news/2024/4/23 16:52:56/文章来源:https://blog.csdn.net/weixin_45532984/article/details/126280922

零. 前言

        堆作为一种重要的数据结构,在面笔试中经常出现,排序问题中,堆排序作为一种重要的排序算法经常被问道,大顶堆小顶堆的应用经常出现,经典的问题TopN问题也是堆的重要应用,因此,了解并掌握这种数据结构是很必要的。

一. 堆的数据结构

1.由树而来

        堆的数据结构可以看作是一种由数组实现的抽象完全二叉树,通过大顶堆或者小顶堆,来达到快速找到一新数据在整个堆结构中的应有位置,继而来实现排序、TopN问题或者log级别的算法要求。

        完全二叉树,就是在一棵树中,元素从从上到下,从左到右依次变满,不会出现一个节点的左子节点不存在而右子节点存在的情况。

2.大顶堆 

        在一个大顶堆中,根节点的值比左右子树都要大(可以等于),在所有子树中都成立,但注意此时,左右子节点的大小并没有进行比较,所以是未知。

        一个典型的大根堆案例:

上图中的二叉树中的数值对应在数组中就是arr=[7,5,6,2,3,1,4],可以看作是树的层序遍历,从上到下,从左到右依次填满

3.小顶堆

         在一个小顶堆中,根节点的值比左右子树都要小(可以等于),在所有子树中都成立,但注意此时,左右子节点的大小并没有进行比较,所以是未知。

        案例同上,只不过大小相反,不再赘述。

4.数组实现堆中的对应关系

        由数组实现方法中,会有数组下标和堆的抽象完全二叉树的对应关系,即数组中的第i元素在堆的哪个位置,或者说,堆中的元素映射在数组是哪个位置。

        对于一个根节点i,它的左子节点是i*2+1,它的右子节点是i*2+2(如果存在的话,在实际中,可能左右子节点都不存在,会超出数组的实际长度,需提前判断)。

        在下方的案例,元素6的下标在数组中对应2,2*2+1=5,数组中5对应的元素是1,而右子节点的元素是4,是一一对应的,而如果知道左子节点,或者右子节点,也可以反过来求根节点的在数组中的位置,对于一个左右子节点i,(i-1)/2即是根节点。

 

二. 堆构建

1.堆的构建过程

            在手动构建堆的过程中,可以用以下的方式来手动构建一个堆【1】,默认为大顶堆:

    

 在整个堆的构建过程中,就是

(1)依次添加每个元素

(2)比较新添加的元素是否比根节点大(默认为大顶堆)

(3)如果新加入的子节点比根大就进行交换,然后一直向上换,直到不比父节点大,或者到达终点(在数组中即是0的位置)

        整个过程是从底向上的过程,可以理解为上窜,上浮过程

2.代码

class Solution {public int[] newarrtoeHeap(int[] nums) {return MakeHeap(nums);}    public int[] MakeHeap(int[] nums){int index = 0;for(int i = 0; i < nums.length; i++){HeapInsert(nums,index++);}}public void HeapInsert(int[] nums, int index){int root = (index-1)/2;while(nums[root] < nums[index]){swap(nums,root,index);index = root;root = (index-1)/2;}}public void swap(int[] nums, int i, int j){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}

三. 利用堆进行排序

1.排序过程

        在第二章节中,对于一个已经构建好的堆来说,需要利用已经构建好的堆来进行排序,排序的整体过程如下:

        (1)对于已经构建好的大根堆来说,数组最左边的元素即是数组中的最大值,把它取出即可

        (2)然而取出之后,树不再是一颗完整的树,此时不利于后续操作(如果从左或者右取一个大元素放到根元素位置,那么后面的元素都要替换),所以此时可以把最左边的元素和最右边的元素交换,那么,最大的元素就是最右边的元素,缩减此时的有效区域即可

        (3)此时虚拟的完全二叉树的根元素是从数组最右边提取的,大小位置,此时三个元素(新根元素,左子节点,右子节点需要进行比较)选出最大值作为根元素,新根元素不断和左右子节点比较直到找到属于自己合适的位置

        (4)不断重复上述步骤,每次选出当前区间最大值,剔除最大值,区间从右向左不断缩减

利用第二章节的案例做具体的排序过程:

        

2.代码

class Solution {public int[] sortArray(int[] nums) {return HeapSort(nums);}    public int[] HeapSort(int[] nums){int index = 0;for(int i = 0; i < nums.length; i++){HeapInsert(nums,index++);}int sum = nums.length;swap(nums,0,--sum);while(sum > 0){Heaptify(nums,0,sum);swap(nums,0,--sum);}return nums;}public void HeapInsert(int[] nums, int index){int root = (index-1)/2;while(nums[root] < nums[index]){swap(nums,root,index);index = root;root = (index-1)/2;}}public void Heaptify(int[] nums,int temp, int sum){int left = temp * 2 +1;int right = left +1;while(left < sum){int max = right < sum ? (nums[left] < nums[right] ? right : left) : left;max = nums[max] < nums[temp]? temp : max;if(max == temp) break;swap(nums,max,temp);temp = max;left = temp * 2 +1;right = left +1;}}public void swap(int[] nums, int i, int j){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}

        以上的步骤都可以归结为:

        (1)交换(首尾交换)

        (2)下沉(新元素找到自己的位置)

        (3)重复交换和下沉步骤

以上的步骤在代码中体现为下跳,下沉过程,对应在代码中为Heaptify()方法。

        第二章节和第三章节的上浮和下沉操作配合即可完成排序过程。

3.堆排序的时间、空间复杂度、是否稳定

        堆排序的最好时间复杂度、最坏时间复杂度和平均时间复杂度都是O(nlog(n)),在构建堆和重构堆的过程中,寻找目标元素或者为目标元素寻找恰当位置都是翻层寻找(*2)。

        空间复杂度O(1),只需要额外的swap函数辅助即可。

        堆排序为不稳定排序,在重构堆的过程中会改变前后相同元素的原本位置。

四. Java PriorityQueue

      在Java中,一个堆可以用PriorityQueue类来实现,默认为小根堆,如果需要大根堆,可以进行重排序。

PriorityQueue<Integer> queue = new PriorityQueue<>();

重排序,可以用lambda表达式

PriorityQueue<Integer> queue = new PriorityQueue<>((v1,v2) -> v2-v1);

或者重写Comparator比较器 

    class minHeap implements Comparator<Integer>{public int compare(Integer m1,Integer m2){return m1 - m2;}}PriorityQueue<Integer> minheap = new PriorityQueue<>(new minHeap());class maxHeap implements Comparator<Integer>{public int compare(Integer m1,Integer m2){return m2 - m1;}}PriorityQueue<Integer> maxheap = new PriorityQueue<>(new maxHeap());

        具体使用,可以参考如下文章:

        数据结构_堆_Java中的实现类

五. 堆排序、topN、PriorityQueue相关问题

1. leetcode 912 排序数组

给你一个整数数组nums,请你将该数组升序排列。

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

不再赘述,参考第三章节

2. 剑指 Offer 40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

暴力解法

class Solution {public int[] getLeastNumbers(int[] arr, int k) {int[] vec = new int[k];Arrays.sort(arr);for (int i = 0; i < k; ++i) {vec[i] = arr[i];}return vec;}
}

大根堆

class Solution {public int[] getLeastNumbers(int[] arr, int k) {int[] ans = new int[k];if(k == 0) return ans;PriorityQueue<Integer> queue = new PriorityQueue<>((v1,v2) -> v2-v1);for(int i : arr){if(queue.size() < k){queue.offer(i);}else{if(queue.peek() > i){queue.poll();queue.offer(i);}}}int index = 0;while(!queue.isEmpty()){ans[index++] = queue.poll();}// int idx = 0;// for(int num: queue) {//     ans[idx++] = num;// }return ans;}
}

本题小结:(1)此题是堆排序的典型应用案例,利用堆的性质来找到前k个大的数,或者前k个小的数

3. leetcode 347 前K个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

class Solution {public List<Integer> topKFrequent(int[] nums, int k) {// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值HashMap<Integer,Integer> map = new HashMap();for(int num : nums){if (map.containsKey(num)) {map.put(num, map.get(num) + 1);} else {map.put(num, 1);}}// 遍历map,用最小堆保存频率最大的k个元素PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return map.get(a) - map.get(b);}});for (Integer key : map.keySet()) {if (pq.size() < k) {pq.add(key);} else if (map.get(key) > map.get(pq.peek())) {pq.remove();pq.add(key);}}// 取出最小堆中的元素List<Integer> res = new ArrayList<>();while (!pq.isEmpty()) {res.add(pq.remove());}return res;}
}

本题小结:(1)此题是和上体思路一样 ,不过需要首先求频率,在得到频率之后和上题解法如出一辙。

六. 参考来源 

【1】b站 左程云 一周刷爆LeetCode p5 

【2】leetcode yukiyama 十大排序从入门到入赘

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

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

相关文章

Mac - Spotlight(聚焦)

文章目录一、Mac 中 Spotlight 的使用1、调用/打开 Spotlight2、执行搜索3、Spotlight 设置二、Mac 上的 Spotlight 开发1、关于 Spotlight2、使用 NSMetadataQuery 搜索示例三、mds 和 fsevents四、命令行访问 Spotlight五、Core Spotlight Framework六、Spotlight 插件相关资…

CSS预处理器sass和less

文章目录CSS预处理器什么是CSS预处理器Sass和LESS背景介绍Sass背景介绍LESS的背景介绍Sass安装Sass下载Ruby安装文件安装Ruby安装Sass编译Sass命令行编译命令行编译配置选项四种编译排版演示nested 编译排版格式expanded 编译排版格式compact 编译排版格式compressed 编译排版格…

登录逻辑漏洞整理集合

目录一、任意用户注册1.未验证邮箱/手机号2、不安全验证邮箱/手机号3.批量注册4.个人信息伪造5.前端验证审核绕过6.用户名覆盖二、任意用户登录1、万能密码2、验证码、密码回显3、登录检测不安全三、任意账号重置1、重置账号名2、验证码3、MVC数据对象自动绑定4、Unicode字符处…

独立产品灵感周刊 DecoHack #048 - 优秀独立开发产品推荐

如果有关注我的 Twitter 的朋友应该看到了&#xff0c;我上周末研究了两天 AI 画图&#xff0c;现在用 Ai 做图太强了&#xff0c;上周又升级 Stable Diffusion 玩了一下&#xff0c;和我去年试的时候相比强大了好多&#xff0c;而且插件LoRA模型玩法都还在快速迭代&#xff0c…

强化学习DQN之俄罗斯方块

强化学习DQN之俄罗斯方块强化学习DQN之俄罗斯方块算法流程文件目录结构模型结构游戏环境训练代码测试代码结果展示强化学习DQN之俄罗斯方块 算法流程 本项目目的是训练一个基于深度强化学习的俄罗斯方块。具体来说&#xff0c;这个代码通过以下步骤实现训练&#xff1a; 首先…

车机开发【Android SystemUI 架构音量控制详解】

SystemUI介绍 SystemUI摘要 在Android系统中SystemUI是以应用的形式运行在Android系统当中&#xff0c;即编译SystemUI模块会生产APK文件&#xff0c;源代码路径在frameworks/base/packages/SystemUI/&#xff0c;安装路径system/priv-app/-SystemUI。 什么是SystemUI 在前…

使用带有 Moveit 的深度相机来避免碰撞

文章目录 什么是深度相机?如何将 Kinect 深度相机添加到您的环境中在 Rviz 中可视化深度相机数据在取放场景中使用深度相机将深度相机与您的 Moveit 设置一起使用有很多优势。机器人可以避免未知环境中的碰撞,甚至可以对周围的变化做出反应。然而,将深度相机连接到您的设置并…

FlinkSQL行级权限解决方案及源码

FlinkSQL的行级权限解决方案及源码&#xff0c;支持面向用户级别的行级数据访问控制&#xff0c;即特定用户只能访问授权过的行&#xff0c;隐藏未授权的行数据。此方案是实时领域Flink的解决方案&#xff0c;类似离线数仓Hive中Ranger Row-level Filter方案。 源码地址: https…

数据分片(mycat)

1. 数据分片概念&#xff1a; 1.1. 分库分表 什么是分库分表&#xff1a; 将存放在一台数据库服务器中的数据&#xff0c;按照特定方式&#xff08;指的是程序开发的算法&#xff09;进行拆分&#xff0c;分散存放到多台数据库服务器中&#xff0c;以达到分散单台服务器负载的…

第51篇-某彩网登录参数分析-webpack【2023-02-21】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析一、前言 今天我们看一个webpack的网站 aHR0cHM6Ly8xMGNhaTUwMC5jYy9sb2dpbg==二、网站分析 首先…

网络协议(一)应用层(自定制协议、HTTP协议)

目录 应用层&#xff1a;负责应用程序之间的数据沟通 一、自定制协议&#xff08;私有协议&#xff09; 二、HTTP协议 1&#xff09;、请求行解析&#xff1a;GET /index.html HTTP/1.1 第一部分&#xff1a;请求方法&#xff1a;多种多样&#xff0c;描述不同的请求目的 …

大数据知识图谱项目——基于知识图谱的医疗知识问答系统(详细讲解及源码)

基于知识图谱的医疗知识问答系统 一、项目概述 本项目基于医疗方面知识的问答&#xff0c;通过搭建一个医疗领域知识图谱&#xff0c;并以该知识图谱完成自动问答与分析服务。本项目以neo4j作为存储&#xff0c;基于传统规则的方式完成了知识问答&#xff0c;并最终以关键词执…

Verilog 学习第五节(串口发送部分)

小梅哥串口部分学习part1 串口通信发送原理串口通信发送的Verilog设计与调试串口发送应用之发送数据串口发送应用之采用状态机实现多字节数据发送串口通信发送原理 1&#xff1a;串口通信模块设计的目的是用来发送数据的&#xff0c;因此需要有一个数据输入端口 2&#xff1a;…

Qt中修改界面类的类名时需要注意的几个修改点

有些时候因为一些原因&#xff0c;需要修改Qt中创建的界面类&#xff0c;需要特别注意几个修改点。 比如将test类修改为test2类 修改test.h名称为test2.h文件&#xff1b;修改test.cpp名称为test2.cpp文件&#xff1b;修改test.ui名称为test2.ui文件&#xff1b;修改pro文件中…

多层感知机的区间随机初始化方法

摘要&#xff1a; 训练是构建神经网络模型的一个关键环节&#xff0c;该过程对网络中的参数不断进行微调&#xff0c;优化模型在训练数据集上的损失函数。参数初始化是训练之前的一个重要步骤&#xff0c;决定了训练过程的起点&#xff0c;对模型训练的收敛速度和收敛结果有重要…

Java基础43 异常(Exception)

异常&#xff08;Exception&#xff09;Exception1.1 异常的概念1.2 异常体系图&#xff08;☆&#xff09;1.3 异常处理分类1.3.1 运行时异常&#xff08;☆&#xff09;1.3.2 编译时异常&#xff08;☆&#xff09;1.4 异常处理&#xff08;☆&#xff09;1.4.1 try-catch异常…

【Git】Git下载安装与使用(一)

目录 1. 前言 1.1 什么是Git 1.2 使用Git能做什么 2. Git概述 2.1 Git简介 2.2 Git下载与安装 3. Git代码托管服务 3.1 常用的Git代码托管服务 3.2 码云代码托管服务 1. 前言 1.1 什么是Git Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码…

Cookies与Session会话技术详解

引言:日常生活中&#xff0c;人和人之间沟通交流&#xff0c;涉及到一个词----会话&#xff0c;软件中一样存在会话&#xff0c;如&#xff1a;网购登录&#xff0c;访问公司OA系统也是不断的会话&#xff0c;软件中如何管理浏览器客户端和服务端之间会话过程中的会话数据呢&am…

盘点四种自动化测试模型实例及优缺点

一&#xff0c;线性测试 1.概念&#xff1a; 通过录制或编写对应应用程序的操作步骤产生的线性脚本。单纯的来模拟用户完整的操作场景。 &#xff08;操作&#xff0c;重复操作&#xff0c;数据&#xff09;都混合在一起。 2.优点&#xff1a; 每个脚本相对独立&#xff0…

【java】java sftp传输 ,java smb传输访问共享文件夹 集成springboot

文章目录java的sftp传输sftp注意事项java smb传输smb注意事项tips: 集成springboot与不集成springboot区别不大&#xff0c;springboot中无非是引入一个maven依赖 加一个Component注解 &#xff0c; 默认是单例&#xff1b; 复制代码前 请先认真看注意事项 java的sftp传输 依赖…