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


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

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

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

相关文章

为什么Hashtable不允许插入nuIl键和null值?

1、典型回答 浅层次的来回答这个问题的答案是&#xff0c;JDK 源码不支持 Hashtable 插入 value 值为 null&#xff0c;如以下 JDK 源码所示&#xff1a; 也就是 JDK 源码规定了&#xff0c;如果你给 Hashtable 插入 value 值为 null 就会抛出空指针异常。 并且看上面的 JDK …

如何搭建DolphinScheduler服务并结合内网穿透公网远程任务调度

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

Stable Diffusion WebUI 生成参数:宽度/高度/生成批次/每批数量/提示词相关性/随机种子

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 大家好&#xff0c;我是水滴~~ 本文将继续了解 Stable Diffusion WebUI 的生成参数&#xff0c;主要内容有&#xff1a;宽度、高度、生成批次、每批数量、提示词相关性、随机种子。希望能对你…

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 并训练自己的数据集

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 概述 大模型微调(finetuning)以适应特定任务是一个复杂且计算密集型的过程。本文训练测试主要是基于主流的的微调方法:LoRA、Adapter、Prefix-tuning、P-tuning和Prompt-tuning,并对…

STM32驱动CC1101时的正确配置和一些遇到的坑

项目背景 由于项目需要用到CC1101-Q1,所以买了CC1101的模块和驱动底板,插电脑上一发一收。串口调试助手查看接收端的数据,测试正常。 然后想用STM32来驱动其中的一块CC1101模块作为发送端,和另一个买来的接收端测试通信。找到一份STM32驱动CC1101Demo代码,分为软件模拟S…

Certum代码签名证书

代码签名证书是一种数字证书&#xff0c;用于验证软件开发者身份和保证软件完整性的技术。它的作用在于确保软件在传输和安装过程中没有被篡改或损坏&#xff0c;并且确认软件来自可信的开发者。通过代码签名证书&#xff0c;用户可以更放心地下载和安装软件&#xff0c;降低安…

【Linux】文件描述符 - fd

文章目录 1. open 接口介绍1.1 代码演示1.2 open 函数返回值 2. 文件描述符 fd2.1 0 / 1 / 22.2 文件描述符的分配规则 3. 重定向3.1 dup2 系统调用函数 4. FILE 与 缓冲区 1. open 接口介绍 使用 man open 指令查看手册&#xff1a; #include <sys/types.h> #include …

AI系统性学习06—开源中文语言大模型

1、ChatGLM ChatGLM-6B的github地址&#xff1a;https://github.com/THUDM/ChatGLM-6B ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级…

透视未来工厂:山海鲸可视化打造数字孪生新篇章

在信息化浪潮的推动下&#xff0c;数字孪生工厂项目正成为工业制造领域的新宠。作为一名山海鲸可视化的资深用户&#xff0c;我深感其强大的数据可视化能力和数字孪生技术在工厂管理中的应用价值&#xff0c;同时我们公司之前也和山海鲸可视化合作制作了一个智慧工厂项目&#…

OceanBase生产环境安装部署的最优实践

关于生产环境&#xff0c;为了尽量确保性能和稳定性&#xff0c;我们比较建议采用标准化的配置进行部署&#xff0c;例如接下来会提到的服务初始化、日志管理和数据分盘等关键步骤。而在非生产环境中&#xff0c;如果条件满足&#xff0c;同样建议遵循规范部署的原则。 前期准备…

项目实战-开发工具入门/基本框架搭建/项目初始化/引入组件库

上周更新完了之前vue3的shopping项目&#xff0c;接下来&#xff0c;将会开启一个新的项目&#xff0c;效果是类似于移动端的一个伙伴匹配项目&#xff0c;今天这篇文章从需求分析到架构设计再到项目初始化&#xff0c;基本框架搭建几个部分来为大家详细介绍。 从这个项目开始…

YOLOV5 改进:替换backbone为Swin Transformer

1、前言 本文会将YOLOV5 backbone更换成Swin Transformer 具体为什么这样实现参考上文:YOLOV5 改进:替换backbone(MobileNet为例)-CSDN博客 这里只贴加入的代码 训练结果如下: 2、common文件更改 在common文件中加入下面代码: 这里是swin transformer的实现,参考:…

VMware部署银河麒麟遇到的问题记录

1. 解决VMware Workstation安装VMware Tools显示灰色的办法 1.关闭虚拟机; 2.在虚拟机设置分别设置CD/DVD、CD/DVD2和软盘为自动检测三个步骤; 3.再重启虚拟机,灰色字即点亮。 2.Linux安装vmTool

Pytest接口自动化测试框架搭建模板

auto_api_test 开发环境: Pycharm 开发语言&版本: python3.7.8 测试框架: Pytest、测试报告: Allure 项目源码Git地址 项目目录结构 api – 模仿PO模式, 抽象出页面类, 页面类内包含页面所包含所有接口, 并封装成方法可供其他模块直接调用config – 配置文件目录data…

打造高效自动化渗透测试系统:关键步骤与实践

随着当前网络安全威胁的不断扩展与升级&#xff0c;开展渗透测试工作已经成为广大企业组织主动识别安全漏洞与潜在风险的关键过程。然而&#xff0c;传统的人工渗透测试模式对测试人员的专业能力和经验水平有很高的要求&#xff0c;企业需要投入较大的时间和资源才能完成。在此…

品牌纷纷推出“穷鬼套餐”的原因竟然有这些?

被大众戏称为“千元店”的山姆也开穷鬼套餐了&#xff1f;&#xff1f;该套餐包括1.59公斤售价21.9元的鸡蛋、7个装售价23.9元的原味贝果以及16片装售价59.8元的瑞士卷。 穷鬼套餐最开始是麦当劳和肯德基的推出的概念&#xff0c;由麦当劳推出的11随心配、肯德基超值午餐等。为…

python(django)之产品后台管理功能实现

1、添加新项目 在命令行输入以下代码 python manage.py startapp prroduct 2、添加路径和代码结构 在新项目目录下admin.py中加入以代码 from .models import Product class ProductAdmin(admin.ModelAdmin):list_display [product_name, product_desc,producter,created_…

CC-DefineTag:一个简单好用的标签组件,支持自动换行、自适应高度,且可设置行数、标签文字颜色等属性

CC-DefineTag&#xff1a;一个简单好用的标签组件&#xff0c;支持自动换行、自适应高度&#xff0c;且可设置行数、标签文字颜色等属性 摘要&#xff1a; 在前端开发中&#xff0c;标签组件是常见的UI组件之一&#xff0c;用于显示一组相关的标签。然而&#xff0c;传统的标签…

Postman Runner 使用指南

什么是 Postman Runner&#xff1f; 而 Postman Runner 是 Postman 中的一个模块&#xff0c;它提供了一种批量运行 API 请求的方式&#xff0c;这些请求可以是已经保存的历史请求、集合中的请求或者手动添加的请求。在批量运行 API 请求的过程中&#xff0c;Postman Runner 可…

4.1 用源文件写汇编代码

汇编语言 1. 源程序 1.1 伪指令 汇编指令是有对应的机器码的指令&#xff0c;可以被编译为机器指令&#xff0c;最终为CPU所执行伪指令没有对应的机器指令&#xff0c;最终不被CPU所执行伪指令是由编译器来执行的指令&#xff0c;编译器根据伪指令来进行相关的编译工作 1.2…