STL —heap算法源码刨析 make_heap、push_heap、pop_heap、sort_heap操作分析
STL —heap算法源码刨析
- heap算法概述
- push_heap 插入元素
- pop_heap 取出根节点元素
- sort_heap 按极值存放元素
- make_heap 将一段现有数据构造成heap
- 程序测试
heap算法概述
heap的内部是一个完全二叉树,将极值存放在根节点。这个里的极值可分为最大值、最小值。根据极值意义的不同可分为大堆和小堆。为了方便解释,以下的讲解内容都以大堆(max-heap)的例子讲解。
大顶堆:
根节点(堆顶元素)是所有节点中的最大值(父节点都大于左右子节点)。
小顶堆:
小顶堆中的根节点是所有节点中的最小值(父节点都小于左右子节点)。

如上图所示,A即为这个列表中的极值。在查找极值时极为方便。同时因为是一个完全二叉树,所以除了叶子节点外,其他的节点都存在。因此可以通过索引值,来找到父节点和左右子节点之间的关系。当父节点索引值为i时,左子节点索引值2i+1,右子节点索引值为2i+2。
具体原理可查看此博客:堆排序中 i 位置的节点的子节点位置为 2i+1, 2i+2, 父节点为 (i-1) / 2
push_heap 插入元素

数据插入示意图
将要插入的元素,放入容器的尾端。通过调用push_heap()函数,将尾端的元素放入到完全二叉树的合适位置。
//计算元素之间的距离
template<class ForwardIterator>
typename std::iterator_traits<ForwardIterator>::difference_type Distance(ForwardIterator first, ForwardIterator last)
{typename std::iterator_traits<ForwardIterator>::difference_type n = 0;while (first != last){++first;++n;}return n;
}//获取元素类型
template<class Iterator>
inline typename std::iterator_traits<Iterator>::value_type* value_Type(const Iterator&)
{return static_cast<std::iterator_traits<Iterator>::value_type *> (0);
}//获取表示距离的类型
template<class Iterator>
inline typename std::iterator_traits<Iterator>::difference_type* distance_type(const Iterator&)
{return static_cast<std::iterator_traits<Iterator>::difference_type *> (0);
}
template<class RandomAcessIterator, class Distance, class T>
void _push_heap(RandomAcessIterator first, Distance holeIndex, Distance topIndex, T value)
{Distance parent = (holeIndex - 1) / 2; //找到父节点while (holeIndex > topIndex && *(first + parent) < value) //当索引值不为根节点时,或者父节点小于插入空节点时{*(first + holeIndex) = *(first + parent);//父节点的值复制给空节点holeIndex = parent; //父节点索引值复制给空节点parent = (holeIndex-1) / 2; //查找此时空节点的父节点}*(first + holeIndex) = value; //将值复制到此时的空节点上
}template<class RandomAcessIterator, class Distance, class T>
inline void _push_heap_aux(RandomAcessIterator first, RandomAcessIterator last, Distance*, T*)
{int count = last - first;_push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
}template <class RandomAcessIterator>
inline void USD_push_heap(RandomAcessIterator first, RandomAcessIterator last)
{_push_heap_aux(first, last, distance_type(first), value_Type(first));
}
这是是一个插入操作,将数据写入到字符串的尾端。找到尾端所对应的父节点,进行对比。如果大于父节点,两个节点对调。找到根节点或者插入空节点小于父节点时,循环操作结束。此时插入的值被放入合适的位置。元素的插入,可参考示意图的操作。
pop_heap 取出根节点元素
调用 函数pop_heap(),会将根节点的值取出来,存在放在容器的尾端。同时会按照完全二叉树的规则重新移动剩余的元素,找出剩余元素中的极值,存放在根节点中,构建出一个新的完全二叉树。此时要取出的元素位于尾部,再通过容器自身的操作,取出尾部的元素。

元素取出示意图
template<class RandomAcessIterator, class Distance, class T>
void _adjust_heap(RandomAcessIterator first, Distance holeIndex, Distance len, T value)
{Distance topIndex = holeIndex; //根节点Distance sencondChild = 2 * holeIndex + 2; //右子节点while (sencondChild < len){if (*(first + sencondChild) < *(first + sencondChild - 1)) //右子节点与左子节点进行比较{sencondChild--; //通过移动,找到左子节点}*(first + holeIndex) = *(first + sencondChild);//将较大的值复制给空值元素所在的位置holeIndex = sencondChild; //空值元素移动到较大值元素位置上sencondChild = 2 * sencondChild + 2; //再次查找对应的右子节点}if (sencondChild == len) //此时右子节点等于树的长度,那么右子树不存在,将左子树元素复制给空值元素{*(first + holeIndex) = *(first + sencondChild-1);holeIndex = sencondChild-1;}_push_heap(first, holeIndex, topIndex,value);//将原二叉树的尾部元素,插入到现在空值元素位置上
}template<class RandomAcessIterator,class T,class Distance>
inline void _pop_heap(RandomAcessIterator first, RandomAcessIterator last, RandomAcessIterator result,T value,Distance *)
{*result = *first;_adjust_heap(first, Distance(0), Distance(last - first), value);
}template<class RandomAcessIterator,class T>
inline void _pop_heap_aux(RandomAcessIterator first, RandomAcessIterator last,T*)
{_pop_heap(first, last-1, last - 1, T(*(last - 1)), distance_type(first));
}template<class RandomAcessIterator>
inline void USD_pop_heap(RandomAcessIterator first, RandomAcessIterator last)
{_pop_heap_aux(first,last,value_Type(first));
}
此算法中的核心逻辑调用_adjust_heap(),首先holeIndex = 0,此时空值元素代表的是根节点,找到根节点对应的右子节点,根节点的右子节点和左子节点进行相比。空值元素与相比后较大值元素进行对换,此时较大值元素就作为了根节点,同时空值元素,移动到了较大值的位置。重复上述操作在继续查找控制元素的右子节点,与它的左子节点进行对比。只到所查值的右子节点索引值大于树的长长度。则停止查找。查找结束后,将原完全二叉树的尾节点,复制到空值所在的位置。操作流程,可参考此函数的示意图。
sort_heap 按极值存放元素
将元素按照极值,从尾到前开始存放元素





template<class RandomAcessIterator>
void USD_sort_heap(RandomAcessIterator first, RandomAcessIterator last)
{while (last - first>1){USD_pop_heap(first,last--);}
}
通过使用pop_heap()操作,将结果存放在尾部,同时缩短last的索引值。这样每pop_heap一次,就会构造一个新的二叉树,同时尾部会存放上一个二叉树的根节点。
make_heap 将一段现有数据构造成heap
容器中存放一段数据,通过make操作后,会更改容器中元素的位置。从而满足 堆的要求。
template<class RandomAcessIterator,class T,class Distance>
void _make_heap(RandomAcessIterator first, RandomAcessIterator last, T*, Distance*)
{if (last - first<2){return;}Distance len = last - first;Distance parent = (len - 2) / 2; //最后一个父节点while (true){_adjust_heap(first, parent, len, *(first+ parent));if (parent == 0){return;}parent--;}
}template<class RandomAcessIterator>
inline void USD_make_heap(RandomAcessIterator first, RandomAcessIterator last)
{_make_heap( first,last, value_Type(first), distance_type(first));
}
此算法的思路,首先找到最后一个父节点。通过调用_adjust_heap()函数,来调整此时的父节点和左右两个子节点之间的关系。来满足heap的条件。当此节点和其子节点调整结束后,通过移动parent索引值,找到下一个父节点再重复上一个操作。当查找到根节点即索引值为0时,heap构建完成。
程序测试
//heap.h
#pragma once
#include <algorithm>template<class ForwardIterator>
typename std::iterator_traits<ForwardIterator>::difference_type Distance(ForwardIterator first, ForwardIterator last)
{typename std::iterator_traits<ForwardIterator>::difference_type n = 0;while (first != last){++first;++n;}return n;
}template<class Iterator>
inline typename std::iterator_traits<Iterator>::value_type* value_Type(const Iterator&)
{return static_cast<std::iterator_traits<Iterator>::value_type *> (0);
}template<class Iterator>
inline typename std::iterator_traits<Iterator>::difference_type* distance_type(const Iterator&)
{return static_cast<std::iterator_traits<Iterator>::difference_type *> (0);
}template<class RandomAcessIterator, class Distance, class T>
void _push_heap(RandomAcessIterator first, Distance holeIndex, Distance topIndex, T value)
{Distance parent = (holeIndex - 1) / 2;while (holeIndex > topIndex && *(first + parent) < value){*(first + holeIndex) = *(first + parent);holeIndex = parent;parent = (holeIndex-1) / 2;}*(first + holeIndex) = value;
}template<class RandomAcessIterator, class Distance, class T>
inline void _push_heap_aux(RandomAcessIterator first, RandomAcessIterator last, Distance*, T*)
{int count = last - first;_push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
}template <class RandomAcessIterator>
inline void USD_push_heap(RandomAcessIterator first, RandomAcessIterator last)
{_push_heap_aux(first, last, distance_type(first), value_Type(first));
}template<class RandomAcessIterator, class Distance, class T>
void _adjust_heap(RandomAcessIterator first, Distance holeIndex, Distance len, T value)
{Distance topIndex = holeIndex;Distance sencondChild = 2 * holeIndex + 2;while (sencondChild < len){if (*(first + sencondChild) < *(first + sencondChild - 1)){sencondChild--;}*(first + holeIndex) = *(first + sencondChild);holeIndex = sencondChild;sencondChild = 2 * sencondChild + 2;}if (sencondChild == len){*(first + holeIndex) = *(first + sencondChild-1);holeIndex = sencondChild-1;}_push_heap(first, holeIndex, topIndex,value);
}template<class RandomAcessIterator,class T,class Distance>
inline void _pop_heap(RandomAcessIterator first, RandomAcessIterator last, RandomAcessIterator result,T value,Distance *)
{*result = *first;_adjust_heap(first, Distance(0), Distance(last - first), value);
}template<class RandomAcessIterator,class T>
inline void _pop_heap_aux(RandomAcessIterator first, RandomAcessIterator last,T*)
{_pop_heap(first, last-1, last - 1, T(*(last - 1)), distance_type(first));
}template<class RandomAcessIterator>
inline void USD_pop_heap(RandomAcessIterator first, RandomAcessIterator last)
{_pop_heap_aux(first,last,value_Type(first));
}template<class RandomAcessIterator>
void USD_sort_heap(RandomAcessIterator first, RandomAcessIterator last)
{while (last - first>1){USD_pop_heap(first,last--);}
}template<class RandomAcessIterator,class T,class Distance>
void _make_heap(RandomAcessIterator first, RandomAcessIterator last, T*, Distance*)
{if (last - first<2){return;}Distance len = last - first;Distance parent = (len - 2) / 2; //最后一个父节点while (true){_adjust_heap(first, parent, len, *(first+ parent));if (parent == 0){return;}parent--;}
}template<class RandomAcessIterator>
inline void USD_make_heap(RandomAcessIterator first, RandomAcessIterator last)
{_make_heap( first,last, value_Type(first), distance_type(first));
}
#include "heap.h"
#include <vector>int main()
{int a[5] = {1,2,3,4,5};std::vector<int> vecTest(a,a+5);USD_make_heap(vecTest.begin(), vecTest.end()); //5,4,3,1,2 vecTest.push_back(7);USD_push_heap(vecTest.begin(), vecTest.end()); //7,4,5,1,2,3USD_pop_heap(vecTest.begin(), vecTest.end());//5,4,3,1,2,7vecTest.pop_back();//5,4,3,1,2USD_sort_heap(vecTest.begin(), vecTest.end());//1,2,3,4,5return 0;
}
为了方便,上述实现都采用了“<”作为比较操作符。这块比较操作,可封装成仿函数,来满足用户自定义的比较操作。注意:写仿函数时,注意返回的bool值。从而决定构建的是max-heap或者min-heap。
如果想了解,算法封装仿函数操作可参考:STL算法详细解剖——单纯数据处理函数
此文章,列举了相同算法,封装仿函数与不封装仿函数两种形式。未了解过仿函数的读者,可做初步的了解。
相关文章:
STL —heap算法源码刨析 make_heap、push_heap、pop_heap、sort_heap操作分析
STL —heap算法源码刨析 heap算法概述push_heap 插入元素pop_heap 取出根节点元素sort_heap 按极值存放元素make_heap 将一段现有数据构造成heap程序测试 heap算法概述 heap的内部是一个完全二叉树,将极值存放在根节点。这个里的极值可分为最大值、最小值。根据极值…...
走进低代码表单开发(一):可视化表单数据源设计
在前文,我们已对勤研低代码平台的报表功能做了详细介绍。接下来,让我们深入探究低代码开发中最为常用的表单设计功能。一个完整的应用是由众多表单组合而成的,所以高效的表单设计在开发过程中起着至关重要的作用。让我们一同了解勤研低代码开…...
简单好用的OCR API
现如今,越来越多的科技产品可以帮助我们改善和提高相应的工作效率。OCR技术的出现,提高了人们的工作效率,其应用领域及其广泛。就拿应用了OCR技术的翔云文档识别服务来说,只需上传文档图片便可自动识别并返回文档中相应的内容。翔…...
c++的拷贝构造函数和赋值函数
拷贝构造函数和赋值函数 什么是拷贝构造 是一种特殊构造函数,如果没有显式的实现,编译器就会自动生成。 class 类名 { public:// 拷贝构造类名(const 类名& that){} }; 什么时候会调用拷贝构造 当使用一个类对象给另一个新的类对象初始化时&…...
什么自动猫砂盆才适合旅游党?4个选购技巧统统告诉你!
有没有能让我们防夹3天不在家都不用担心猫咪铲屎问题的方法?当然有了!自动猫砂盆就是最好的选择,要知道,有个好用合适的自动猫砂盆在家的话,根本不用担心生虫发臭的问题出现,因为自动猫砂盆能及时感应到猫咪…...
算法知识点————双指针【删除重复元素】【反转链表】
删除重复元素 题目://给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数 思路:…...
建造者模式builder
此篇为学习笔记,原文链接 https://refactoringguru.cn/design-patterns/builder 能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象...
IEC103设备数据 转 IEC61850项目案例
目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 配置VFBOX网关采集103设备数是 2 5 用IEC61850协议转发数据 4 6 网关使用多个逻辑设备和逻辑节点的方法 6 7 IEC103协议说明 8 8 案例总结 9 1 案例说明 设置网关采集IEC103设备数据把采集的数据转成IEC61850协议转发…...
438.找到字符串中所有字母异位词
题目 链接:leetcode链接 思路分析(滑动窗口) 很容易想到,这个题目要求我们在字符串s中找到一个定长的窗口让窗口里面出现异位词。 OK,先思考一下怎么快速判断两个字符串是否是异位词? 比较简单的方法是…...
Microsoft SC-100: Microsoft 网络安全架构师
SC-100认证介绍 Microsoft SC-100: Microsoft 网络安全架构师是微软网络安全方向的相关证书, 作为 Microsoft 网络安全架构师,你要将网络安全策略转化为保护组织的资产、业务和运营的功能。 你要设计、指导实现和维护遵循零信任原则和最佳做法的安全性解…...
代码随想录训练营day42|188.买卖股票的最佳时机IV,309.最佳买卖股票时机含冷冻期,714.买卖股票的最佳时机含手续费
188.买卖股票的最佳时机IV 变成了最多可以买卖k只股票 class Solution { public:int maxProfit(int k, vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(2*k1,0));for(int i1;i<2*k1;i2){dp[0][i]-prices[0];}//初始…...
解决Pynput不能在Ubuntu22.04上正常使用问题
pynput 是一个 Python 库,它提供了一套简单的接口来控制和监控输入设备,包括鼠标和键盘。这个库允许开发者编写跨平台的代码来处理输入事件,无需担心底层操作系统的差异。 下面是pynput的测试程序: from pynput.keyboard import …...
IPV4端口数据有哪些?
关于IPv4的端口数据包括端口号,协议类型,端口状态,扫描时间以及开放时间,这些是常见的实时IPv4端口扫描和分析数据。 IPv4端口号 Ipv4端口号是一个16位的无符号整数,范围从0到65535,这些端口号用于区分运…...
【爱加密_云平台-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
Open CASCADE学习|按圆离散旋转体
旋转体是一个非常重要的概念,它涉及到三维空间中由二维曲线绕某一轴线旋转形成的立体形状。这种旋转体的形成过程,实际上是一个连续变化的动态过程,但在数学和几何学中,我们往往通过静态的方式来描述和研究它。 旋转体的基本特性…...
无人矿车使用ZMQ消息代理进行跨机互联进行消息收发
目录 背景 前景知识 1.启动一个代理服务器 2.发布者 3.订阅者 实际应用 1.模拟智驾收来自交互箱131的按钮json数据 2.模拟交互箱131收来自智驾137的cjson数据 背景 在调试防爆无轨无人矿车时,出现消息代理不能很好转发,于是在公司通过代理的方式…...
医疗机构关于DIP/DRG信息化建设
推进DIP/DRG支付方式改革是一项系统性工程,牵一发而动全身。作为河北省DIP试点医院,河北医科大学第二医院将信息化与创新性管理理念融合,用好支付工具做好精细化管理,积极应对改革。 ■ 改革背景 国家医疗保障局制定的《DRG/DIP支…...
100个候选人,没一个能讲明白什么是自动化框架?
什么是自动化测试框架 01 什么是框架 框架是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法。它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间…...
数据结构与算法1: 链表
题目名称: 重排链表 链接: . - 力扣(LeetCode) 介绍:本题的目标是将链表进行重新组合,如下图。 如果按照标准的解法,我们需要实现三步 1. 链表中点的获取 2. 链表的反转 3. 链…...
【专题】2024年8月医药行业报告合集汇总PDF分享(附原数据表)
原文链接:https://tecdat.cn/?p37621 在科技飞速发展的当今时代,医药行业作为关乎人类生命健康的重要领域,正处于前所未有的变革浪潮之中。数智医疗服务的崛起,为医疗模式带来了全新的转变,开启了医疗服务的新时代。…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
