LeetCode 热题 100 | 堆(一)

目录

1  什么是堆排序

1.1  什么是堆

1.2  如何构建堆

1.3  举例说明

2  215. 数组中的第 K 个最大元素

2.1  子树大根化

2.2  遍历所有子树

2.3  弹出栈顶元素

2.4  完整代码


菜鸟做题,语言是 C++

1  什么是堆排序

1.1  什么是堆

堆的定义和分类:

  • 堆是一棵完全二叉树
  • 分为:大根堆、小根堆

堆的特点:

  • 大根堆:在任一子树中,根节点都比左、右子节点大
  • 小根堆:在任一子树中,根节点都比左、右子节点小

这就是为什么它叫 “大根” 堆或者 “小根” 堆吧?

1.2  如何构建堆

假设给定数组 [3,2,1,5,6,4],要求我们把它构建为一个大根堆。

首先,我们可以把它想象成完全二叉树层序遍历的结果:

注意:这里我说的是 “想象成”!因为到时候我们直接处理数组就行了,不需要构建一个二叉树出来。

接着,既然在大根堆的任一子树中,根节点都比左、右子节点大,那么我们只需要遍历上述二叉树的每棵子树,然后让根节点最大即可。具体来说,如果 左、右子节点中的较大者 比根节点大,那么就让它和根节点交换位置。

代码实现交换用的就是一个 swap() 函数。

1.3  举例说明

如图 1 所示,我们从二叉树的最后一棵子树开始遍历(黄色部分)。由于根节点 “1” 比左子节点 “4” 小,因此让它们交换位置(红圈部分):

为什么要从最后一棵子树开始遍历,从第一棵开始不行吗?答:到时候我们要处理受到影响的子树,“根据根节点找左或右子节点” 貌似要比 “根据左或右子节点找根节点” 容易。

如图 2 所示,我们接着遍历下一棵子树(黄色部分)。由于根节点 “2” 比右子节点 “6” 小(即左、右子节点中的较大者),因此让它们交换位置(红圈部分):

如图 3 所示,我们继续遍历下一棵子树(黄色部分)。由于根节点 “3” 比左子节点 “6” 小(即左、右子节点中的较大者),因此让它们交换位置(红圈部分):

注意!这里完成交换以后,“3” 被换入了左下角子树中,使得该子树的根节点不再是最大值,因此我们需要重新处理左下角子树!如图 4 蓝色和红圈部分所示:

事实上,只要左右子节点不是叶节点,那么发生交换之后一定要重新处理受到影响的子树。

2  215. 数组中的第 K 个最大元素

构建堆 → 弹出堆顶 → 调整堆 → 弹出堆顶 → 调整堆

由于大根堆堆顶元素的值最大,即二叉树根节点的值最大,因此只要我们不断地弹出 堆顶元素 + 调整大根堆,就能依次得到第 xxx 大的元素。

Q:大根堆的本质不是数组吗?如何实现堆顶元素(即第 0 个元素)的弹出?

A:将堆顶元素(即第 0 个元素)与最后一个元素交换,并且人为让数组长度减一。

由于将堆顶元素移到了最后且令数组长度减一,那么调整大根堆时就不会再遍历到该堆顶元素了。

2.1  子树大根化

功能:完成对一棵子树的处理。

子树大根化的函数如下,代码逻辑为:

  1. 获取左、右子节点的位置(说明见后文)
  2. 根节点分别与左、右子节点比大小
  3. 若根节点小于左、右子节点,则交换位置
  4. 递归处理受到影响的左或右子树

再次强调,根节点只会和左、右子节点中的较大者交换位置。同时,如果此次交换涉及到的是左子节点,那么只需要递归处理受到影响的左子树,而没有必要处理右子树。

void maxHeapify(vector<int> & nums, int root, int heapSize) {// 获取左、右子节点位置int left = root * 2 + 1, right = root * 2 + 2;int largest = root;// 与左子节点比较if (left < heapSize && nums[left] > nums[largest]) {largest = left;}// 与右子节点比较if (right < heapSize && nums[right] > nums[largest]) {largest = right;}// 处理if (largest != root) {swap(nums[largest], nums[root]);maxHeapify(nums, largest, heapSize);}
}

说明: 为什么左、右子节点的位置是这样获取的?

int left = root * 2 + 1, right = root * 2 + 2;

因为完全二叉树有一个结论:如果根节点是第 i 个节点,那么它的左子节点是第 2i 个节点,右子节点是第 2i + 1 个节点(从 1 开始计数)。如下图所示:

本质就是等比数列罢了。

2.2  遍历所有子树

使用 for 循环遍历所有子树,同时进行子树大根化:

void buildMaxHeap(vector<int> & nums, int heapSize) {for (int root = heapSize / 2; root >= 0; --root) {maxHeapify(nums, root, heapSize);} 
}

说明:为什么 root 是从 heapSize / 2 开始的?

假设 size 是二叉树节点的总数,那么最后一个节点显然是第 size 个节点(从 1 开始计数)。最后一个节点所属的子树是最后一棵子树,即我们的遍历起点。再根据前文介绍,易得该子树的根节点是第 size / 2 个节点。因此,root 应该从 size / 2 开始。

这里的 size 就是指 heapSize,没有写 heapSize 是因为写不下了。

2.3  弹出栈顶元素

代码如下:

for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {swap(nums[0], nums[i]); // 交换栈顶和最后一个元素--heapSize; // 人为让数组长度减一maxHeapify(nums, 0, heapSize); // 调整大根堆
}

由于我们寻找的是第 K 个最大元素,因此循环条件是 i >= nums.size() - k + 1,即循环 k 次。同时,由于弹出栈顶操作主要影响的是第 0 棵子树,因此只需要 maxHeapify(nums, 0, heapSize),而不是重新构建大根堆。

2.4  完整代码
class Solution {
public:void maxHeapify(vector<int> & nums, int root, int heapSize) {int left = root * 2 + 1, right = root * 2 + 2;int largest = root;if (left < heapSize && nums[left] > nums[largest]) {largest = left;}if (right < heapSize && nums[right] > nums[largest]) {largest = right;}if (largest != root) {swap(nums[largest], nums[root]);maxHeapify(nums, largest, heapSize);}}void buildMaxHeap(vector<int> & nums, int heapSize) {for (int root = heapSize / 2; root >= 0; --root) {maxHeapify(nums, root, heapSize);} }int findKthLargest(vector<int> & nums, int k) {int heapSize = nums.size();buildMaxHeap(nums, heapSize);for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {swap(nums[0], nums[i]);--heapSize;maxHeapify(nums, 0, heapSize);}return nums[0];}
};


堆排序到底是谁想出来的,可恶 (〃>皿<)

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

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

相关文章

【c++入门】命名空间,缺省参数与函数重载

&#x1f525;个人主页&#xff1a; Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff01;本篇内容我们进入一个新的阶段&#xff0c;进入c的学习&#xff01;希望我的博客内容能对你有帮助&#xff01; 目录 1.c关键字2.第一个c代码3.命名空间3.1 nam…

CTF-辨别细菌

题目描述&#xff1a;try your best to find the flag. 进入靶场后发现是一个游戏&#xff0c;需要全部答对才可以得到最后的flag 查看了一下源码&#xff0c;发现有一个答案模板的模块 尝试解释一下代码 <!-- 答案模版 --> <script id"template_game_pi…

数据结构/C++:红黑树

数据结构/C&#xff1a;红黑树 概念实现基本结构插入uncle为红色节点uncle为黑色节点 总代码展示 概念 红黑树是一种二叉搜索树&#xff0c;一般的二叉搜索会发生不平衡现象&#xff0c;导致搜索效率下降&#xff0c;于是学者们开始探索如何让二叉搜索树保持平衡&#xff0c;这…

Agent驱动的RPA——实在Agent(智能体):自动化时代的新引擎

随着人工智能和机器学习技术的快速发展&#xff0c;智能Agent在 RPA领域扮演了革命性的角色。 Agent驱动的RPA不仅实现了传统规则导向自动化工具的功能升级&#xff0c;而且通过引入自主、智能决策与协作能力&#xff0c;为现代企业带来了更高程度的灵活性与智能化水平。随着数…

第1章 计算机系统概述

王道学习 1.1 操作系统的基本概念 1.1.1 操作系统的概念 1.1.2 操作系统的特征 操作系统是一种系统软件&#xff0c;但与其他系统软件和应用软件有很大的不同&#xff0c;它有自己的特殊性即基本特征。操作系统的基本特征包括并发、共享、虚拟和异步。这些概念对理解和掌握…

kail linux破解密码--- 详细过程(配合图文让你看了就会)

1.准备工作 1.vmware虚拟机 2.kali的系统 3.无线网卡一张(这个是必须的我买的是30多块) 4.这里为了实验&#xff0c;和直观的看到效果&#xff0c;用手机开了一个wifi然后使用kali进行破解 2.下载kali然后安装到虚拟机vmware 直接在官网下载 Get Kali | Kali Linux 我选…

WebXR实践——利用aframe框架浏览器展示全景图片

一、效果 话不多说&#xff0c;先上效果 二、代码 index.html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>360&deg; Image</title><meta name"description" content"360&deg; Imag…

【机器学习】深入解析线性回归模型

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

高精度铸铁平台制造工艺有多精细——河北北重机械

高精度铸铁平台制造工艺通常包括以下几个步骤&#xff1a; 材料准备&#xff1a;选择合适的铸铁材料&#xff0c;并确保其质量符合要求。常用的铸铁材料包括灰铸铁、球墨铸铁等。 模具制造&#xff1a;根据平台的设计要求&#xff0c;制造适用的模具。模具一般由砂型、金属模具…

【python】flask基于cookie和session来实现会话控制

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【开源-土拨鼠充电系统】鸿蒙 HarmonyOS 4.0 App+微信小程序+云平台

✨本人自己开发的开源项目&#xff1a;土拨鼠充电系统 ✨踩坑不易&#xff0c;还希望各位大佬支持一下&#xff0c;在Gitee或GitHub给我点个 Start ⭐⭐&#x1f44d;&#x1f44d; ✍Gitee开源项目地址&#x1f449;&#xff1a;https://gitee.com/cheinlu/groundhog-charging…

力扣Lc19--- 268. 丢失的数字(java版)-2024年3月20日

1.题目描述 2.知识点 &#xff08;1&#xff09;比如数组里面有n个数&#xff0c;然后计算这n个数的总和(用等差求和数列计算&#xff09;,然后减去数组的和&#xff0c;用总和减去数组和即为所得 &#xff08;2&#xff09;加强型 for 循环&#xff08;也称为 for-each 循环&…

单片机--数电(4)

触发器 数字电路中&#xff1a;分组合逻辑电路与时序逻辑电路两大类 组合逻辑电路的基本单元是门电路&#xff08;与或非等一些门电路&#xff09; 时序逻辑电路的基本单元是触发器 触发器与门电路的区别 门电路某一时刻的输出信号完全取决于该时刻的输入信号&#xff0c;…

销售数据分析怎么做?用好这5个数据分析方法与模型就足够了。

企业经营其实简单来说就是做买卖&#xff0c;有了买卖自然就产生了销售数据&#xff0c;那怎么能让这些销售数据产生价值呢&#xff1f;答案就是数据分析。通过对销售数据的分析&#xff0c;可以帮助企业及时洞察市场动向&#xff0c;发现企业销售过程中的问题&#xff0c;调整…

ResNet《Deep Residual Learning for Image Recognition》

ResNet论文学习 引言Deep Residual Learning 深度残差学习网络结构 总结代码复现 引言 深度网络自然地以端到端的多层方式集成低/中/高级特征和分类器&#xff0c;特征的“级别”可通过堆叠层的数量来丰富 随着网络层数加深&#xff0c;提取的特征越强&#xff0c;但是 网络…

表情识别数据集

表情视频数据集 在许多的研究中,研究者通常会把人脸表情识别区分为静态的人脸表情识别(static facial emotion recognition)和动态的人脸表情识别(dynamic facial emotion recognition)。前者希望通过单张图片辨别人的表情从而达到识别人情绪的目的,而后者希望感知视频/…

进程的概念 | PCB | Linux下的task_struct | 父子进程和子进程

在讲进程之前首先就是需要去回顾一下我们之前学的操作系统是干嘛的&#xff0c;首先操作系统是一个软件&#xff0c;它是对上提供一个良好高效&#xff0c;稳定的环境的&#xff0c;这是相对于用户来说的&#xff0c;对下是为了进行更好的软硬件管理的&#xff0c;所以操作系统…

TinyEMU源码分析之虚拟机初始化

TinyEMU源码分析之虚拟机初始化 1 初始化结构参数2 配置RAM地址空间3 初始化设备4 拷贝BIOS和Kernel5 手动写入5条指令6 体验第一条指令的执行 本文属于《 TinyEMU模拟器基础系列教程》之一&#xff0c;欢迎查看其它文章。 本文中使用的代码&#xff0c;均为伪代码&#xff0c…

vue2使用webSocket双向通讯

基于webSocket实现双向通信&#xff0c;使用webworker保持心跳。 由于浏览器的资源管理策略会暂停或限制某些资源的消耗&#xff0c;导致前端心跳包任务时效&#xff0c;后端接收不到webSocket心跳主动断开&#xff0c;因此需要使用webworker保持心跳 引入webworker npm insta…

关于安卓调用文件浏览器(一)打开并复制

背景 最近在做一个硬件产品&#xff0c;安卓应用开发。PM抽风&#xff0c;要求从app打开文件浏览器&#xff0c;跳转到指定目录&#xff0c;然后可以实现文件复制粘贴操作。 思考 从应用开发的角度看&#xff0c;从app打开系统文件浏览器并且选择文件&#xff0c;这是很常见…