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 在科技飞速发展的当今时代,医药行业作为关乎人类生命健康的重要领域,正处于前所未有的变革浪潮之中。数智医疗服务的崛起,为医疗模式带来了全新的转变,开启了医疗服务的新时代。…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
