文章目录

  • 前言
  • 1. heap结构概述
  • 2. push_heap
  • 3. pop_heap
  • 4. sort_heap
  • 5. make_heap

前言

  • heap这种数据结构,允许用户以任何次序将任何数据放入该结构中,但是最后取出数据的时候一定是权值最高(或者最低)的元素。主要和实现有关,根据比较方法的不同,实现大根堆/小根堆的算法……

    下面小编主要是实现大根堆的算法

  • 并且如果一个类型需要进行使用堆来进行存储,那么这个类型一定是支持比较方法的

本文章就和大家来探讨堆的算法。例如:

  1. push_heap
  2. pop_heap
  3. sort_heap
  4. make_heap

1. heap结构概述

  • heap的底层就是使用的一个完全二叉树(逻辑上)。我们使用vector/array容器来作为heap的底层结构,再以顺序结构构建这颗完全二叉树(物理存储上)。

  • 大根堆:任意一个父节点的权值都大于等于孩子节点的权值。

  • 小根堆:任意一个父节点的权值都小于等于孩子节点的权值。

在这里插入图片描述

  • 构建的方式

    1. 我们将这颗二叉树的根设置为数组的0下标。(还有其它设置方法)

    2. 根节点找孩子节点

      当前节点的下标为i,左孩子的下标为2 * i + 1;右孩子的下标为:2 * i + 2。合理地找到一个根节点左右孩子节点。

    3. 孩子节点找根节点

    当前节点的下标为i,父亲节点的下标为(i - 1)/2

  • 现有如图的大根堆结构:

    构建的一个大根堆结构

2. push_heap

push_heap操作是在原有的堆基础之上,再在这个完全二叉树的结构中添加的新的元素,一般而言会有如下的几个步骤:

  1. 将新数据新增到物理结构的最后下标位置,但是逻辑结构上仍然是一个完全二叉树。此时,已经破坏了大根堆/小根堆的性质。

  2. 需要对当前节点的数据进行调整,使得整个完全二叉树满足大根堆/小跟堆的性质。

    此时我们用到的调整算法:Percolate Up(上溯)。向上调整。

如下图:

push_heap操作的实现逻辑

  • 调整的结束当前节点权值小于等于父节点权值或者已经更新到根节点
	void push_heap(int val){_heap.push_back(val); //添加新元素AdjustUp(_heap.size() - 1);}void AdjustUp(size_t child){int index = (int)child; //拿到最后一个位置的下标while (index > 0) //当前调整的节点位置大于0,就继续调整{int parent = (index - 1) / 2; //找到父节点位置if (_heap[parent] < _heap[index]) //父亲节点的key < 当前节点位置的key{//交换两者的权值std::swap(_heap[parent], _heap[index]);}else // >={break;}//调整当前位置的下标index = parent;}}

3. pop_heap

一般来说,pop的操作是取走整个heap的权值最大/最小的节点。根据前面的经验,那么就是需要将根节点的数据取走。根节点在vector中的下标为0。我们该如何取取走该根节点呢?

  • 为了满足堆的完全二叉树的性质,所以pop节点一定是最后一个位置的。那么我们能想到的方案就是:

    1. 交换根节点权值和最后一个节点的权值,执行pop_back。此时整个heap结构仍然保持完全二叉树结构。但是不满足大根堆/小根堆性质。

    2. 我们需要对根节点开始调整,使其满足堆的性质。

      从根节点开始,执行 Percolate Down(下溯)。向下调整。

如下图:

pop_heap算法演示

  • 调整的结束当前节点权值小于左右孩子最大节点值或者没有左右孩子
void pop_heap()
{size_t index = _heap.size() - 1;std::swap(_heap[0], _heap[index]); //交换权值_heap.pop_back(); //删除最后一个元素AdjustDown(0, _heap.size() - 1); //传入根节点的下标和当前heap的有效长度
}void AdjustDown(int index, int size)
{//在完全二叉树中,左孩子存在,但是右孩子不一定存在int child = index * 2 + 1; //假设左孩子大while (child < size) //选取的孩子不能越界;越界了说明孩子不存在{if (child + 1 < size && _heap[child] < _heap[child + 1]){child = child + 1; //更新左右孩子中值较大的}if (_heap[index] < _heap[child]) //父亲比孩子小{std::swap(_heap[index], _heap[child]); //交换权值//继续向下调整}else // >={break;}//更新index和childindex = child;child = 2 * index + 1;}
}

后面的sort_heap会解释,为什么向下调整还需要一个size的参数

4. sort_heap

  • 堆排序的思想

    利用堆的性质:每次都能获得当前heap中键值最大的元素。结合pop_heap算法我们可以可以持续对堆中的元素做pop_heap操作。(注意:这里的pop_heap不会将原有的数据删除,而是有效数据的位置减少,在pop_heap中的size参数,就是体现有效数据的地方)这样我们每次都能将键值最大/最小的元素安置在容器的末尾,从而完成升序或者降序……

    注意使用完堆排序之后,这个heap就不再是一个heap了

void sort_heap()
{int size = (int)_heap.size(); //得到当前大小while (size > 1) //剩余元素超过两个{// 模拟pop_backint index = size - 1;std::swap(_heap[0], _heap[index]);--size; //有效长度-1//向下调整AdjustDown(0, size); //向下调整有效长度}
}

5. make_heap

  • 建堆一般是根据一个已有的容器/迭代器中的数据,将这些数据构建出一个大根堆/小根堆的算法。很容易我们可以想到一种方法:

    方案一依次将迭代器区间中的元素push_heap到一个新的堆中

但是这样的建堆方式是利用了向上调整的算法。这个算法要求,除了最后一个位置之外的所有位置,都满足堆结构。后面会进行时间复杂度的分析。

  • 如果你比较敏锐,你会发现我们向下调整的算法,要求:当前节点的左右子树必须满足堆结构。

    方案二:我们可以局部看待任何一个二叉树,分为左子树 根 右子树。为了实现向下调整的算法。我们似乎的处理方式是:先将左右子树建堆,最后根向下调整。以任何一个。这样的建堆方式如下图:

在这里插入图片描述
我们采用迭代的方式进行建堆,即:找到没有成堆的最小树开始向下调整

void make_heap()
{int index = (int)_heap.size() - 1, size = (int)_heap.size(); //堆数据的大小int parent = (index - 1) / 2; //找到父亲节点while (parent >= 0) //实际上调整的节点是父亲节点{AdjustDown(parent, size);--parent; //遍历下一个父亲节点}
}
  • 时间复杂度分析
    分析向上建堆和向下建堆的时间复杂度

完。

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

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

相关文章

MCU 软件断点调试注意事项!!!

——为什么调试器会在运行中改我的Flash程序&#xff1f;调试单片机时&#xff0c;很多人都有这样的疑问&#xff1a;明明我在调试前刷进去的固件是好的&#xff0c;为什么加了一个断点之后&#xff0c;调试器居然去改了 Flash&#xff1f; 如果我拔掉调试器&#xff0c;这个固…

启发式合并 + 莫队 恋恋的心跳大冒险

题目来源&#xff1a;2025 Wuhan University of Technology Programming Contest 比赛链接&#xff1a;Dashboard - 2025 Wuhan University of Technology Programming Contest - Codeforces 题目大意&#xff1a; Solution&#xff1a; 首先肯定要预处理出以每个节点为起点…

JCTools 无锁并发队列基础:ConcurrentCircularArrayQueue

ConcurrentCircularArrayQueue ConcurrentCircularArrayQueue 是一个抽象类&#xff0c;它为基于数组的并发循环队列提供了基础功能。从其命名可以看出几个关键特性&#xff1a;​​Concurrent​​&#xff1a;常指无锁并发。​​Circular Array​​&#xff1a;内部使用循环数…

力扣(LeetCode) ——622. 设计循环队列(C语言)

题目&#xff1a;622. 设计循环队列示例1&#xff1a; MyCircularQueue circularQueue new MyCircularQueue(3); // 设置长度为 3 circularQueue.enQueue(1); // 返回 true circularQueue.enQueue(2); // 返回 true circularQueue.enQueue(3); // 返回 true circularQueue.…

在JVM跑JavaScript脚本 | Oracle GraalJS 简介与实践

这是2024年初的 GraalVM 系列博文&#xff0c;当时写了大纲&#xff0c;知道一年半后的现在才得以完成发布&#x1f604; 1、概述 实话说&#xff0c;标题的场景为小众需求&#xff0c;日常开发基本用不到&#xff0c;我是最近在做一个低代码轮子玩具 app-meta 需要实现 FaaS&…

基于 EC 数据与大模型技术实现天气预报:从数据到上线的全栈方法

1. 先校准“EC 数据”与“AI 预报”的语境 EC 数据家族(常用) IFS/HRES:确定性全球模式,水平分辨率约 9 km,常用预报范围 10 天; IFS/ENS:51 成员集合预报,提供 15 天概率信息; ERA5:再分析数据,小时级、0.25,可追溯至 1940 年,用作训练/评测黄金基准。 AI 预报…

迭代器模式及优化

迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;用于提供一种统一的方式遍历聚合对象&#xff08;如集合、容器&#xff09;中的元素&#xff0c;而无需暴露对象的内部实现细节。它将遍历逻辑与聚合对象分离&#xff0c;使得遍历操作可以…

纯Qt手撕gb28181协议/gb28181协议服务端/gb28181协议设备端/gb28181设备模拟器/gb28181虚拟监控设备

一、前言说明 搞完onvif设备模拟器&#xff0c;总想着把28181设备模拟也实现&#xff0c;因为之前已经花了大力气把28181平台软件端实现了&#xff0c;为了实现这个组件&#xff0c;头发掉了一大把&#xff0c;专门把国标文档看了好几遍&#xff0c;逐行阅读&#xff0c;针对需…

【渗透实战】无下载器环境(curl/wget)下玩转 Metasploit 自动利用

1. 背景与问题场景 在渗透测试或漏洞利用中&#xff0c;Metasploit&#xff08;MSF&#xff09;是业界最常用的框架之一。 其许多 RCE&#xff08;远程代码执行&#xff09;模块在落地 payload&#xff08;如 Meterpreter 或反弹 shell&#xff09;时&#xff0c;采用了 CMD St…

jd-hotkey探测热点key

对任意突发性的无法预先感知的热点数据&#xff0c;包括并不限于热点数据&#xff08;如突发大量请求同一个商品&#xff09;、热用户&#xff08;如恶意爬虫刷子&#xff09;、热接口&#xff08;突发海量请求同一个接口&#xff09;等&#xff0c;进行毫秒级精准探测到。然后…

C#WPF实战出真汁07--【系统设置】--菜品类型设置

1、菜品设置介绍 菜品设置跟餐桌设置的功能目的是相同的&#xff0c;包括了新增&#xff0c;删除&#xff0c;编辑&#xff0c;分页&#xff0c;查询&#xff0c;重置&#xff0c;全选&#xff0c;全消&#xff0c;列表功能&#xff0c;实现流程也是布局设计&#xff0c;后台逻…

aave v3 存款与借款利息的计算方式

本文只涉及到利率计算的数学原理&#xff0c;不作源码解析:存款首先我们假设小明在aave里面存了10000usdt&#xff0c;存的时候年化收益率是5%,那么半年后其存款的利息是多少呢?常规的计算方式如下:利息10000*5%*(存款的时长/一年的时长)这么做有什么问题呢&#xff1f;假设现…

Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析

&#x1f4cb; 目录 项目概述 技术架构深度解析 核心功能模块详解 代码实现分析 使用场景与实战案例 性能优化与最佳实践 扩展开发指南 总结与展望 项目概述 什么是Windows-MCP.Net&#xff1f; Windows MCP.Net是一个基于.NET 10.0开发的Windows桌面自动化MCP&…

Boost.Asio学习(7):Boost.Beast实现简易http服务器

namespace beast boost::beast;beast::flat_buffer是一个用于 Boost.Asio 和 Boost.Beast 网络读写的缓冲区实现。专为 一次性顺序读取 / 消费 场景设计&#xff0c;比 std::string 或 std::vector 高效&#xff0c;因为它是扁平内存结构&#xff08;contiguous memory&#x…

深入解析JVM内存区域划分:从理论到实践

Java虚拟机&#xff08;JVM&#xff09;是Java程序运行的核心环境&#xff0c;它负责管理内存分配、垃圾回收、字节码执行等关键任务。理解JVM的内存区域划分&#xff0c;对于优化Java应用性能、排查内存问题&#xff08;如OutOfMemoryError、StackOverflowError&#xff09;至…

滑窗|贪心|✅滚动数组

lc17.08pair按身高升序、相同时体重降序排序结果是找体重序列的最长递增子序列长度核心&#xff1a;转化为二维最长递增子序列问题求解vector<int> dp;for (auto& p : hw) {int w p.second;auto it lower_bound(dp.begin(), dp.end(), w);if (it dp.end()) {dp.pu…

深入理解数据库架构:从原理到实践的完整指南

一、数据库存储架构的多维度分类体系 1.1 基于数据组织方式的存储架构分类 数据库的存储架构从根本上决定了其性能特征、适用场景和扩展能力。理解不同的数据组织方式是选择合适数据库技术的基础&#xff0c;这种分类不仅反映了技术实现的差异&#xff0c;更体现了对不同业务需…

体彩排列三第2025218期号码分析

大家好&#xff0c;本人蔡楚门来此平台分享一下本期得经验和思路&#xff0c;希望能够给大家带来好的运气和灵感&#xff01;体彩排列三第2025218期号码分析&#xff0c;大小号码数字分析&#xff0c;上期开出全小号码最多&#xff0c;最近两期的开奖号码全部都是全小号码最多&…

java设计模式之迪米特法则介绍与说明

一、核心概念与目标 基本定义 迪米特法则的核心思想是&#xff1a;一个对象应该对其他对象尽可能少地了解&#xff0c;仅与直接关联的对象&#xff08;即“朋友”&#xff09;通信&#xff0c;避免与“陌生人”产生直接交互。 直接朋友&#xff1a;包括当前对象的成员变量、方法…

2024-2025华为ICT大赛中国区 实践赛昇腾AI赛道(高职组)全国总决赛 理论部分真题+解析

Part 1 昇腾AI全栈系统模块(共6题)&#xff1a;1、许多计算芯片可以设计作为人工智能的计算芯片&#xff0c;但不同的芯片计算性能不同&#xff0c;昇腾计算芯片是一种()芯片。(单选题)A.CPU B.GPU C. NPU D.TPU正确答案&#xff1a;C解析&#xff1a;A项CPU中央处理器的架…