堆/二叉堆详解[C/C++]

前言

堆是计算机科学中-类特殊的数据结构的统称。实现有很多,例如:大顶堆,小顶堆,斐波那契堆,左偏堆,斜堆等等。从子结点个数上可以分为二汊堆,N叉堆等等。本文将介绍的是二叉堆

二叉堆的概念

1、引例

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们小时候,基本都玩过或见过叠罗汉的恶作剧(如上图)。叠罗汉运动是把人堆在一起,而且为了保证稳定性,体重大身高高的人一般在下面,体重轻身高矮的人一般在上面,我们的二叉堆结构也是按照某种规则把元素堆成一个塔形结构。

2、定义

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二叉堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆( 例如左上图所示) ;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆(例如右上图所示)。

3、性质

对于大(小)顶堆,它总是满足下列性质:
1)空树是一个大(小)顶堆;
2)大(小)顶堆中某个结点的关键字小(大)于等于其父结点的关键字;
3)大(小)顶堆是一棵完全二叉树。
4)根结点一定是大(小)顶堆中所有结点最大(小)者。

4、作用

二叉堆能够在O(1)的时间内,获得关键字最大(小)的元素。并且能够在O(logn)的时间内执行插入和删除。一般用来做优先队列的实现、堆排序算法等。

堆的存储结构

二叉堆是一颗完全二叉树,是一种树形结构,但是我们未必真的需要按照二叉树的存储方式去实现,类似于并查集用数组来模拟树形结构,我们的二叉堆类似的,把每个结点,按照层序映射到-一个顺序存储的数组中,然后利用每个结点在数组中的下标,来确定结点之间的关系。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结点的编号

根节点

在国内的数据结构教材中,有的把根节点编号为0,有的编号为1,这里我们选择编号为0。

孩子结点

根据上图示例结合我们二叉树的性质,不难发现

根节点和左右孩子编号关系为:lchild = parent * 2 + 1 rchild = parent * 2 +2

同样的parent = (lchild - 1) / 2 = (rchild - 1) / 2(利用C/C++除法向下取整)

存储结构

对于堆,显然我们需要一个容器来存储我们的数据,由于堆的建造过程中我们难以避免大小比较,我们不妨增加一个比较的仿函数作为模板参数

存储结构如下

template <class T, class Container = vector<T>, class Compare = less<T>>
//T为存储数据类型   Container为存储数据的容器,默认为vector   Compare为两关键字进行比较的仿函数,可由用户自定义传入
class Heap{//...
private:Container _con;Compare _cmp;
};

堆的构造

向下调整算法

我们给定这样一个情形,有一颗根节点不满足堆规则但是其他任意子树都满足二叉堆规则的完全二叉树,我们进行怎样的调整可以使它成为堆?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图示例大顶堆,根节点35小于80和70,但是其他子树都符合我们大顶堆的规则,我们该如何调整呢?

需要从孩子中选择一个节点来替代我们的根节点,为了尽可能保证大顶堆的特性,我们选择孩子节点中最大的80和35进行交换,但是交换完之后根节点是符合规则了,交换后的35又小于40和50,这时除了35所在子树其他子树都满足大顶堆的特性,所以我们继续对35的子树进行调整,再次与最大孩子节点交换,我们发现这时得到了一个大顶堆。

我们向下调整算法调整的初始节点满足除了初始节点和子节点不满足堆规则,其他节点都满足。而我们每次调整会修正当前位置,同时最多增加一个不满足规则的位置,也就是说最多调整高度次,时间复杂度为O(log(N + 1)) = O(logN)

代码如下:

    void AdjustDown(int parent){int child = parent * 2 + 1;int n = _con.size();while (child < n){if (child + 1 < n && _cmp(_con[child], _con[child + 1])){child++;}if (_cmp(_con[parent], _con[child])){swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}}

向上调整算法

会了向下调整算法,自然就会向上调整算法了

同样的,我们向上调整算法调整的初始节点满足除了初始节点和父节点不满足堆规则,其他节点都满足。而我们每次调整会修正当前位置,同时最多增加一个不满足规则的位置,最多调整高度次,时间复杂度为O(log(N + 1)) = O(logN)

这里只给出大根堆示例就不详细说明了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码如下:

    void AdjustUp(int child){int parent = (child - 1) / 2;while (parent >= 0){if (_cmp(_con[parent], _con[child])){swap(_con[parent], _con[child]);child = parent;parent = (parent - 1) / 2;}elsebreak;}}

堆的构建算法

基于AdjustDown的自底而上构建

我们实际中,大多数时候都不会像两种调整算法那么巧,只有一个位置不符合规则,那么对于任意元素集合,我们该如何来进行调整呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比如上图,就是一种很不符合我们堆规则的情形,而我们只会两种调整算法的情形处理方式,但是我们可以把一个堆看成多个堆的组合

我们以向下调整算法为例我们按照下标顺序,从第一个非叶子节点开始从后往前执行向下调整算法,会出现什么情况呢?

为了更好理解,这里使用动画演示

在这里插入图片描述

对于我们的开始节点,也就是第一个非叶子节点20它显然符合我们向下调整算法的情形,Adjustdown后20节点所在子树修正完毕,然后向前继续该操作,修正节点80,然后修正节点70最后到根节点15的时候我们发现15此时也符合我们Adjustdown的情形,再次Adjustdown后,我们得到了一颗大根堆

现在我们清楚了,我们的向下调整堆构建算法就是从自下而上不断的修正,直到出现只有根节点不符合堆的规则,再次Adjustdown后我们就得到了堆

时间复杂的的计算我们后面详细解释

代码如下:

void BuildHeap()
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(i);}
}
基于AdjustUp的自顶而下构建

会了基于AdjustDown的自底而上构建,自然会基于AdjustUp的自顶而下构建辣。

这里直接给出代码:

void BuildHeap()
{
for (int i = 0; i <= (n - 1 - 1) / 2; i++){AdjustUp(i);}
}
两种构建算法的时间复杂度分析

前面没有给出两种构建的时间复杂度分析,是为了专门在这里说明,从而进行两种方法的选择。

对于基于AdjustDown的自底而上构建

这里给出计算方法

总调整次数为每一层节点数量乘该层每个节点调整次数求和

假设高度为h,对于第i层,有2^i次方个节点,需要调整(h - i)次,根据高中数列求和的知识,不难完成F(h)的计算
F ( h ) = 2 h − 1 × 0 + 2 h − 2 × 1 + ⋯ + 2 0 × ( h − 1 ) ① 2 ∗ F ( h ) = 2 h × 0 + 2 h − 2 × 1 + ⋯ + 2 0 × ( h − 1 ) ② F ( h ) = 2 h − 1 + 2 h − 2 + ⋯ + 2 0 − h + 2 ② − ① = 2 h − 1 − h − 1 = N − log ⁡ 2 ( N + 1 ) ≈ N \begin{equation} \begin{split} F(h)&=2^{h-1}\times 0+2^{h-2}\times 1+\dots +2^{0} \times(h - 1) ①\\ 2*F(h)&=2^{h}\times 0+2^{h-2}\times 1+\dots +2^{0} \times(h - 1) ②\\ F(h)&=2^{h-1}+2^{h-2}+\dots +2^{0}-h+2②-①\\ &=2^{h-1}-h-1\\ &=N-\log_{2}{(N+1)} \\ &\approx N \end{split} \end{equation} F(h)2F(h)F(h)=2h1×0+2h2×1++20×(h1)=2h×0+2h2×1++20×(h1)=2h1+2h2++20h+2②=2h1h1=Nlog2(N+1)N

对于基于AdjustUp的自顶而下构建

F ( h ) = 2 1 − 1 × 0 + 2 2 − 1 × 1 + ⋯ + 2 h − 1 × ( h − 1 ) ① 2 ∗ F ( h ) = 2 1 × 0 + 2 2 × 1 + ⋯ + 2 h × ( h − 1 ) ② F ( h ) = 2 h × ( h − 1 ) − 2 h + 1 ② − ① = 2 h × ( h − 2 ) − h + 1 = ( N + 1 ) × ( log ⁡ 2 N − 1 ) − log ⁡ 2 ( N + 1 ) + 1 = N × log ⁡ 2 N − N ≈ N × log ⁡ 2 N \begin{equation} \begin{split} F(h)&=2^{1-1}\times 0+2^{2-1}\times 1+\dots +2^{h-1} \times(h-1) ①\\ 2*F(h)&=2^{1}\times 0+2^{2}\times 1+\dots +2^{h} \times(h-1) ②\\ F(h)&=2^{h}\times(h-1)-2^{h}+1②-①\\ &=2^{h}\times(h-2)-h+1\\ &=(N+1)\times(\log_{2}{N} - 1)-\log_{2}{(N+1)}+1 \\ &=N\times\log_{2}{N}-N\\ &\approx N\times\log_{2}{N} \end{split} \end{equation} F(h)2F(h)F(h)=211×0+221×1++2h1×(h1)=21×0+22×1++2h×(h1)=2h×(h1)2h+1②=2h×(h2)h+1=(N+1)×(log2N1)log2(N+1)+1=N×log2NNN×log2N

通过对比发现,自底而上构建为O(N),而自顶而下构建为O(NlogN),所以我们一般选择自底而上构建

堆的常用接口

实际上,我们如果不以序列初始化堆的话,是用不到堆的构建算法的,因为每次只涉及堆的一个元素的增删查改,下面介绍我们堆的常用接口的实现

堆的插入push

对于在堆里面新增元素,我们尾插,然后此时对新元素向上调整即可

    void push(const T &x){_con.push_back(x);AdjustUp(_con.size() - 1);}

堆顶元素的删除pop

对于删除堆顶元素,我们把堆顶元素和序列是容器中最后一个元素交换然后尾删,但是此时堆顶可能非法,所以还要对堆顶向下调整

    void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}

获取堆顶元素top

直接返回堆顶即可

    T top() const{return _con[0];}

堆是否为空empty

调用序列是容器的接口即可

    bool empty() const{return _con.empty();}

获取堆的大小size

同样调用序列式容器的接口

    bool size() const{return _con.size();}

堆的代码实现

template <class T, class Container = vector<T>, class Compare = less<T>>
class Heap
{
public:explicit Heap(const Compare &cmp) : _con(), _cmp(cmp) {}Heap() : _con() {}void push(const T &x){_con.push_back(x);AdjustUp(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}T top() const{return _con[0];}bool empty() const{return _con.empty();}bool size() const{return _con.size();}private:void AdjustDown(int parent){int child = parent * 2 + 1;int n = _con.size();while (child < n){if (child + 1 < n && _cmp(_con[child], _con[child + 1])){child++;}if (_cmp(_con[parent], _con[child])){swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}}void AdjustUp(int child){int parent = (child - 1) / 2;while (parent >= 0){if (_cmp(_con[parent], _con[child])){swap(_con[parent], _con[child]);child = parent;parent = (parent - 1) / 2;}elsebreak;}}private:Container _con;Compare _cmp;
};

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

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

相关文章

left join时筛选条件对查询结果的

-- 创建表 CREATE TABLE table1 (id int(11) NOT NULL AUTO_INCREMENT,card_num varchar(60) DEFAULT NULL,customer_id varchar(60) DEFAULT NULL,PRIMARY KEY (id) ) ENGINE InnoDBAUTO_INCREMENT 12DEFAULT CHARSET utf8mb4 COMMENT 测试表1;-- 创建表 CREAT…

[OpenCV-dlib]人脸识别功能拓展-通过随机要求头部动作实现活体检测

引言 在现代计算机视觉中&#xff0c;面部检测和姿势识别是一个重要的领域&#xff0c;它在各种应用中发挥着关键作用&#xff0c;包括人脸解锁、表情识别、虚拟现实等。本文将深入探讨一个使用Python编写的应用程序&#xff0c;该应用程序结合了多个库和技术&#xff0c;用于…

44springboot摄影跟拍预定管理系统

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

basic_sr介绍

文章目录 pytorch基础知识和basicSR中用到的语法1.Sampler类与4种采样方式2.python dict的get方法使用3.prefetch_dataloader.py4. pytorch 并行和分布式训练4.1 选择要使用的cuda4.2 DataParallel使用方法常规使用方法保存和载入 4.3 DistributedDataParallel 5.wangdb 入门5.…

详解js数组操作——filter()方法

引言 在JavaScript中&#xff0c;我们经常需要对数组进行筛选&#xff0c;以便根据特定的条件获取所需的元素。而JavaScript的filter()方法就是一个非常有用的工具&#xff0c;它可以帮助我们轻松地筛选数组中的元素。本文将介绍如何使用filter()方法&#xff0c;以及一些实用…

react+antd+Table实现表格初始化勾选某条数据,分页切换保留上一页勾选的数据

加上rowKey这个属性 <Table rowKey{record > record.id} // 加上rowKey这个属性rowSelection{rowSelection}columns{columns}dataSource{tableList}pagination{paginationProps} />

众佰诚:抖音小店的体验分什么时候更新

随着移动互联网的发展&#xff0c;越来越多的电商平台开始涌现&#xff0c;其中抖音小店作为一种新型的电商模式&#xff0c;受到了许多用户的欢迎。然而&#xff0c;对于抖音小店的体验分更新时间&#xff0c;很多用户并不是很清楚。本文将对此进行详细的解答。 首先&#xff…

SimpleCG图像操作基础

上一篇我们介绍了程序的交互功能&#xff0c;就可以编写一些简单的游戏了&#xff0c;例如贪吃蛇、扫雷、俄罗斯方块、五子棋等&#xff0c;都可以使用图形函数直接绘制&#xff0c;在后续文章中将逐一展示。不过编写画面丰富游戏离不开图像&#xff0c;所以本篇我们介绍一下基…

智能合同和TikTok:揭示加密技术的前景

在当今数字化时代&#xff0c;智能合同和加密技术都成为了技术和商业世界中的热门话题。它们代表了一个崭新的未来&#xff0c;有着潜在的巨大影响。 然而&#xff0c;你或许从未想过将这两者联系在一起&#xff0c;直到今天。本文将探讨智能合同和TikTok之间的联系&#xff0…

代码随想录算法训练营Day56|动态规划14

代码随想录算法训练营Day56|动态规划14 文章目录 代码随想录算法训练营Day56|动态规划14一、1143.最长公共子序列二、 1035.不相交的线三、53. 最大子序和 动态规划 一、1143.最长公共子序列 class Solution {public int longestCommonSubsequence(String text1, String text2…

sql聚合函数嵌套问题 aggregate function cannot contain aggregate parameters

在需求的应用场景&#xff0c;需要对create_time字段求最小值并求和&#xff0c;刚开始理所当然写成像下面这样&#xff1a; SUM(COALESCE (CASE WHEN MIN(crl.create_time) BETWEEN date_add(date_sub(current_date(), 1), -1 * (open_case_day_num % 6)) AND current_date()…

辉视IP对讲与SIP视频对讲:革新的通信技术与应用领域的开启

辉视IP对讲与辉视SIP视频对讲系统&#xff0c;不仅在技术上实现了一次革新&#xff0c;更在应用领域上开启了新的篇章。它们不仅仅是一种通信工具&#xff0c;更是一种集成了先进技术和多种功能的高效解决方案&#xff0c;为各领域提供了一种安全、便捷、高效的通信体验。 辉视…

5. 函数式接口

5.1 概述 只有一个抽象方法的接口我们称之为函数接口。 JDK的函数式接口都加上了 FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法&#xff0c;都是函数式接口。 在Java中&#xff0c;抽象方法是一种没有方法体&#xff08;实现代码&a…

【AOP系列】6.缓存处理

在Java中&#xff0c;我们可以使用Spring AOP&#xff08;面向切面编程&#xff09;和自定义注解来做缓存处理。以下是一个简单的示例&#xff1a; 首先&#xff0c;我们创建一个自定义注解&#xff0c;用于标记需要进行缓存处理的方法&#xff1a; import java.lang.annotat…

联想G50笔记本直接使用F键功能(F1~F12)需要在BIOS设置关闭热键功能可以这样操作!

如果开启启用热键模式按F1就会出现FnF1的效果&#xff0c;不喜欢此方式按键的用户可以进入BIOS设置界面停用热键模式即可。 停用热键模式方法如下&#xff1a; 1、重新启动笔记本电脑&#xff0c;当笔记本电脑屏幕出现Lenovo标识的时候&#xff0c;立即按FnF2进入BIOS设置界面…

表单规定输入域的选项列表(html5新元素)

datalist datalist 元素规定输入域的选项列表。 datalist属性规定 form 或 input 域应该拥有自动完成功能。当用户在自动完成域中开始输入时&#xff0c;浏览器应该在该域中显示填写的选项&#xff1a; 使用 input元素的列表属性与datalist元素绑定. 还有一定的搜索能力&…

CVE-2020-9483 apache skywalking SQL注入漏洞

漏洞概述 当使用H2 / MySQL / TiDB作为Apache SkyWalking存储时&#xff0c;通过GraphQL协议查询元数据时&#xff0c;存在SQL注入漏洞&#xff0c;该漏洞允许访问未指定的数据。 Apache SkyWalking 6.0.0到6.6.0、7.0.0 H2 / MySQL / TiDB存储实现不使用适当的方法来设置SQL参…

GPIO基本原理

名词解释 高低电平&#xff1a;GPIO引脚电平范围&#xff1a;0V~3.3V&#xff08;部分引脚可容忍5V&#xff09;数据0就是0V&#xff0c;代表低电平&#xff1b;数据1就是3.3V&#xff0c;代表高电平&#xff1b; STM32是32位的单片机&#xff0c;所以内部寄存器也都是32位的…

FilterRegistrationBean能不能排除指定url

文章目录 什么是FilterRegistrationBean举个栗子但是如果我想要排除某些uri方法总结FilterRegistrationBean只能设置指定的url进行过滤,而不能指定排除uri,只能使用OncePerRequestFilter的shouldNotFilter方法,排除uri 什么是FilterRegistrationBean FilterRegistrationBean是…

用于细胞定位的指数距离变换图--Exponential Distance Transform Maps for Cell Localization

论文&#xff1a;Exponential Distance Transform Maps for Cell Localization Paper Link&#xff1a; Exponential Distance Transform Maps for Cell Localization Code&#xff08;有EDT Map的生成方式&#xff09;&#xff1a; https://github.com/Boli-trainee/MHFAN 核…