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 在科技飞速发展的当今时代,医药行业作为关乎人类生命健康的重要领域,正处于前所未有的变革浪潮之中。数智医疗服务的崛起,为医疗模式带来了全新的转变,开启了医疗服务的新时代。…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...