堆/二叉堆详解[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.rhkb.cn/news/161559.html

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

相关文章

04 MIT线性代数-矩阵的LU分解 Factorization into A=LU

目的: 从矩阵的角度理解高斯消元法, 完成LU分解得到ALU 1.矩阵乘积的逆矩阵 Inverse of a product 2.矩阵乘积的转置 Transpose of a product 3.转置矩阵的逆矩阵 Inverse of a transpose 4.矩阵的LU分解 U为上三角阵(Upper triangular matrix), L为下三角阵(Lower triangular…

【C++ 学习 ㉘】- 详解 C++11 的列表初始化

目录 一、C11 简介 二、列表初始化 2.1 - 统一初始化 2.2 - 列表初始化的使用细节 2.2.1 - 聚合类型的定义 2.2.2 - 注意事项 2.3 - initializer_list 2.3.1 - 基本使用 2.3.2 - 源码剖析 一、C11 简介 1998 年&#xff0c;C 标准委员会发布了第一版 C 标准&#xff0…

大数据Hadoop之——部署hadoop+hive+Mysql环境(window11)

一、安装JDK8 【温馨提示】对应后面安装的hadoop和hive版本&#xff0c;这里使用jdk8&#xff0c;这里不要用其他jdk了&#xff0c;可能会出现一些其他问题。 1&#xff09;JDK下载地址 Java Downloads | Oracle 按正常下载是需要先登录的&#xff0c;这里提供一个不用登录下载…

VMware虚拟机安装Linux系统的介绍

许多新手连 Windows 的安装都不太熟悉&#xff0c;更别提 Linux 的安装了&#xff1b;即使安装成功了&#xff0c;也有可能破坏现有的 Windows 系统&#xff0c;比如导致硬盘数据丢失、Windows 无法开机等。所以一直以来&#xff0c;安装 Linux 系统都是初学者的噩梦。 然而&a…

Zookeeper【Curator客户端Java版】从0到1——万字学习笔记

目录 初识Zookeeper Zookeeper作用 维护配置信息 分布式锁服务 集群管理 生产分布式唯一ID Zookeeper的设计目标 Zookeeper 工作机制 数据模型 ZooKeeper 命令操作 服务端常用命令 客户端常用命令 ZooKeeper JavaAPI操作 Curator 介绍 Curator API 常用操作 导入依赖 建立连接 …

PTE-精听学习(一)

目录 SST SST每一题都是单独计时 MMA 切换题目的时候&#xff0c;总是会迷茫 deduct 出现关键词之后&#xff0c;才开始精听 没有人管你 &#xff0c;绝对是要为后方留出更多的时间 &#xff0c;选多一个错的&#xff0c;要倒扣分 特征 1.paraphrase 2.循序出现 …

JDK 21的新特性总结和分析

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Windows服务器安装php+mysql环境的经验分享

php mysql环境 下载IIS Php Mysql环境集成包,集成包下载地址: 1、Windows Server 2008 一键安装Web环境包 x64 适用64位操作系统服务器:下载地址:链接: https://pan.baidu.com/s/1MMOOLGll4D7Eb5tBrdTQZw 提取码: btnx 2、Windows Server 2008 一键安装Web环境包 32 适…

视频推拉流/直播点播平台EasyDSS分享的链接提示“无信号”,该如何解决?

视频直播点播平台EasyDSS可支持用户自行上传视频文件&#xff0c;也可将上传的点播文件作为虚拟直播进行播放。平台能支持多屏播放&#xff0c;可兼容Windows、Android、iOS、Mac等操作系统&#xff0c;还能支持CDN转推&#xff0c;具备较强的可拓展性与灵活性。 为给用户提供更…

AcWing算法提高课-5.6.2青蛙的约会

宣传一下 算法提高课整理 CSDN个人主页&#xff1a;更好的阅读体验 原题链接 题目描述 两只青蛙在网上相识了&#xff0c;它们聊得很开心&#xff0c;于是觉得很有必要见一面。 它们很高兴地发现它们住在同一条纬度线上&#xff0c;于是它们约定各自朝西跳&#xff0c;直到…

数据库设计与前端框架

数据库设计与前端框架 学习目标&#xff1a; 理解多租户的数据库设计方案 熟练使用PowerDesigner构建数据库模型理解前端工程的基本架构和执行流程 完成前端工程企业模块开发 多租户SaaS平台的数据库方案 多租户是什么 多租户技术&#xff08;Multi-TenancyTechnology&a…

PCI设备与UIO驱动

随着网络的高速发展,对网络的性能要求也越来越高,DPDK框架是目前的一种加速网络IO的解决方案之一,也是最为流行的一套方案。DPDK通过bypass内核协议栈与内核驱动,将驱动的工作从内核态移至用户态,并利用polling mode的线程工作模式加速网络I/O使得网络IO性能出现大幅度的增…

xml文件报错 ORA-00907: 缺失右括号

原来的sql 更改之后 加一个select * from &#xff08;&#xff09;

一文理解登录鉴权(Cookie、Session、Jwt、CAS、SSO)

1 前言 登录鉴权是任何一个网站都无法绕开的部分&#xff0c;当系统要正式上线前都会要求接入统一登陆系统&#xff0c;一方面能够让网站只允许合法的用户访问&#xff0c;另一方面&#xff0c;当用户在网站上进行操作时也需要识别操作的用户&#xff0c;用作后期的操作审计。…

嵌入式开发学习之STM32F407点亮LED及J-Link下载(二)

嵌入式开发学习之STM32F407点亮LED及J-Link下载&#xff08;二&#xff09; 开发涉及工具控制端口配置端口的设定与确认端口配置方法实现点亮LED程序下载与仿真 有工程实例&#xff0c;链接在最底部。 开发涉及工具 开发环境&#xff08;IDE&#xff09;&#xff1a;IAR-ARM8…

力扣每日一题46:全排列

题目描述&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; …

Tuxera NTFS2024最新永久版下载和安装

要使用Tuxera NTFS for Mac&#xff0c;你需要先下载和安装Tuxera NTFS for Mac驱动器&#xff0c;然后按照以下步骤操作&#xff1a; 1、下载和安装Tuxera NTFS for Mac 免费下载Tuxera NTFS for Mac驱动器的最新版本。下载完成后&#xff0c;双击DMG文件并按照提示安装即可…

云计算:shell脚本

shell脚本&#xff0c;会极大减少重复性工作&#xff0c;缩短很大时间。 脚本每个人都可以不一样&#xff0c;只要实现就可以。 注意&#xff1a;要多思考&#xff0c;把思路锻炼好。以后就可以写各种程序。 shell语言 学完shell之后&#xff0c;对Linux理解更深刻&#xff…

IDEA 修改插件安装位置

不说假话&#xff0c;一定要看到最后&#xff0c;不然你以为我为什么要自己总结&#xff01;&#xff01;&#xff01; IDEA 修改插件安装位置 前言步骤 前言 IDEA 默认的配置文件均安装在C盘&#xff0c;使用时间长会生成很多文件&#xff0c;这些文件会占用挤兑C盘空间&…

如何使用前端模块化开发?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…