数据结构进阶之堆

2024-04-13 21:20
文章标签 进阶 数据结构 之堆

本文主要是介绍数据结构进阶之堆,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天我们学习的是数据结构里面的,大家先看看我们今天要学习的内容

一、堆概念及认识

在学习堆之前我们得先明白完全二叉树是什么样子,因为堆是依据完全二叉树的结构来实现的,所以在这里我先告诉大家完全二叉树的是什么,如下图:

完全二叉树就是从根节点开始依次往下延伸展开,其他层都是满节点,只有最后一层不同,最后一层可满可不满,但是最后一层必须是从左往右,这就是完全二叉树,也就是堆的逻辑结构。

堆的概念及认识:堆是一种数据结构,分为大堆和小堆,数据从上开始往下排序,上一层的数据如果大于下一层的每个数据,那么它就是大堆,反之就是小堆。

二、堆的结构及操作原理

堆的逻辑结构就是完全二叉树的结构,但是我们要实现自己的堆就需要了解堆的物理结构,它是如何实现对数据的储存的,在这里实现堆我们用数组来实现堆,大家可能会一脸懵,所以接下来我向大家解释为什么用数组来实现。

先看下图来理解:

如果把上层看作父节点,下层看作子节点,再看看它们的的数组下标大家会不会发现父子节点之间存在某种关系,这也是其他大佬发现的规律,在这里我直接告诉大家,用 parent 作为父节点的下标,child 作为子节点的下标,就会有下面的下标规律

child = 2 * parent + 1 ,parent = (child - 1)/  2 。

因为有上面的规律,所以我们实现自己的堆才选择用数组来储存,上面的规律也是堆实现的底层逻辑。

三、堆的实现

在这里我们以实现小堆为例,首先我得先告诉大家小堆的储存结构,大家先看下图:

如果是小堆,那么上一层的数据必须小于下一层的数据,并且最高层的数据是最小的,这就是小堆。那么接下来我们就来实现一个自己的堆。

首先需要创建一个结构体用来储存堆的数组和堆的大小和堆的数据个数,如下:

typedef int HPDataType;
typedef struct Heap
{HPDataType* a;// 堆的物理结构时数组,逻辑结构是满二叉树或完全二叉树int size;int capacity;
}HP;

接下来我们先写出最简单的堆的初始化和销毁和一个交换函数,如下:

//堆的初始化
void HPInit(HP* hp)
{assert(hp);hp->a = NULL;hp->capacity = hp->size = 0;
}//堆的销毁
void HPDestroy(HP* hp)
{assert(hp);free(hp->a);hp->capacity = hp->size = 0;
}//实现交换数据
void Swap(HPDataType* hp1, HPDataType* hp2)
{HPDataType tem = *hp1;*hp1 = *hp2;*hp2 = tem;
}

最难的部分就是堆数据放入时的向上调整,因为是小堆,所以上一层的数据必须小于下一层的每个数据,所以每次放进一个数据就要向上进行调整,用来保证小堆的结构,那如何来实现数据的想上调整呢,请看下面:

根据上面的讲解我们开始实现向上调整,请结合代码中的注释加以理解:

//堆数据的向上调整 时间复杂度:O(logN)
void AdjustUp(HP* hp, int child)
{assert(hp);assert(hp->size > 0);// 截至条件是 当最后一次的子节点小于或者等于0的时候 说明上层没有数据 也就不用再向上调整了while (child > 0){// 实现数据的交换// 根据父子的关系规律来找到父节点int parent = (child - 1) / 2;if (hp->a[parent] > hp->a[child]){//交换Swap(&(hp->a[parent]), &(hp->a[child]));child = parent;}else{// 如果已经符合小堆 就直接退出break;}}
}

有了上面的向上调整,我们就可以开始实现数据的入队列了,操作如下:

//堆的放入数据
void HPPush(HP* hp, HPDataType x)
{assert(hp);if (hp->capacity == hp->size){// 堆内存的开辟int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;HPDataType* tem = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);if (tem == NULL){perror("realloc");exit(1);}hp->a = tem;hp->capacity = newcapacity;}// 堆数据的放入hp->a[hp->size] = x;hp->size++;// 每次放入一个数据都需要进行向上调整来保证小堆的完整性AdjustUp(hp, hp->size - 1);
}

接下来我们继续实现堆的其他简单操作,如下:

//获取堆顶数据
HPDataType HPTop(HP* hp)
{assert(hp);return hp->a[0];
}//堆中的数据个数
int HPSize(HP* hp)
{assert(hp);return hp->size - 1;
}//堆的判空
bool HPEmpty(HP* hp)
{assert(hp);return hp->size == 0;
}

 接下来还有一个难点就是堆顶数据的删除,首先我们得了解一下堆顶数据是如何进行删除的,我来告诉大家,堆顶数据的删除不是真的删除,而是把堆顶的数据和堆的最后一位数据进行交换,并且对堆顶的数据再进行向下调整,来保证小堆的结构。上面我有提到了向下调整,这其实和向上调整一样,请大家看原理图:

那为什么要和两个子节点中较小的进行比较呢,因为要符合小堆,必须最高层数据是最小值,所以要和两个子节点中较小的进行比较,实现如下请结合注释一起理解:

//堆数据的向下调整 时间复杂度:logN
void AdjustDown(HP* hp, int parent)
{assert(hp);// 用父子节点之间的规律来找到子节点int child = 2 * parent + 1;// 退出条件为 最后一次的子节点超出堆的大小就不用在进行向下调整 直接退出循环while (child < hp->size){// 要判断两个子节点中的最小值 首先这个父节点下面要有两个子节点才可以 如果没有就直接和子节点比较饥渴if (child + 1 < hp->size){if (hp->a[child] > hp->a[child + 1]){// 假设法选出两个儿子中的最小值child = child + 1;}}if (hp->a[parent] > hp->a[child]){// 交换两个值Swap(&(hp->a[child]), &(hp->a[parent]));parent = child;child = 2 * parent + 1;}else{// 如果没有交换 说明满足小堆的结构 直接退出即可break;}}}

接下来堆顶数据的删除就可以结合堆顶数据的删除原则(如堆顶数据删除的原理图)就可以写出来了,如下:

//堆顶数据的删除
void HPPop(HP* hp)
{assert(hp);Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));hp->size--;AdjustDown(hp, 0);
}

这就是堆的实现,大家还有什么不会的或者没理解的,可以评论区留言或者私信我,我来帮大家解答。

好了,今天的内容就到这,下期再见!!!

这篇关于数据结构进阶之堆的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/901243

相关文章

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、

Python进阶之列表推导式的10个核心技巧

《Python进阶之列表推导式的10个核心技巧》在Python编程中,列表推导式(ListComprehension)是提升代码效率的瑞士军刀,本文将通过真实场景案例,揭示列表推导式的进阶用法,希望对... 目录一、基础语法重构:理解推导式的底层逻辑二、嵌套循环:破解多维数据处理难题三、条件表达式:实现分支

redis数据结构之String详解

《redis数据结构之String详解》Redis以String为基础类型,因C字符串效率低、非二进制安全等问题,采用SDS动态字符串实现高效存储,通过RedisObject封装,支持多种编码方式(如... 目录一、为什么Redis选String作为基础类型?二、SDS底层数据结构三、RedisObject

基于Python编写自动化邮件发送程序(进阶版)

《基于Python编写自动化邮件发送程序(进阶版)》在数字化时代,自动化邮件发送功能已成为企业和个人提升工作效率的重要工具,本文将使用Python编写一个简单的自动化邮件发送程序,希望对大家有所帮助... 目录理解SMTP协议基础配置开发环境构建邮件发送函数核心逻辑实现完整发送流程添加附件支持功能实现htm

基于Python实现进阶版PDF合并/拆分工具

《基于Python实现进阶版PDF合并/拆分工具》在数字化时代,PDF文件已成为日常工作和学习中不可或缺的一部分,本文将详细介绍一款简单易用的PDF工具,帮助用户轻松完成PDF文件的合并与拆分操作... 目录工具概述环境准备界面说明合并PDF文件拆分PDF文件高级技巧常见问题完整源代码总结在数字化时代,PD

javaSE类和对象进阶用法举例详解

《javaSE类和对象进阶用法举例详解》JavaSE的面向对象编程是软件开发中的基石,它通过类和对象的概念,实现了代码的模块化、可复用性和灵活性,:本文主要介绍javaSE类和对象进阶用法的相关资... 目录前言一、封装1.访问限定符2.包2.1包的概念2.2导入包2.3自定义包2.4常见的包二、stati

C语言进阶(预处理命令详解)

《C语言进阶(预处理命令详解)》文章讲解了宏定义规范、头文件包含方式及条件编译应用,强调带参宏需加括号避免计算错误,头文件应声明函数原型以便主函数调用,条件编译通过宏定义控制代码编译,适用于测试与模块... 目录1.宏定义1.1不带参宏1.2带参宏2.头文件的包含2.1头文件中的内容2.2工程结构3.条件编

从入门到进阶讲解Python自动化Playwright实战指南

《从入门到进阶讲解Python自动化Playwright实战指南》Playwright是针对Python语言的纯自动化工具,它可以通过单个API自动执行Chromium,Firefox和WebKit... 目录Playwright 简介核心优势安装步骤观点与案例结合Playwright 核心功能从零开始学习

深度解析Python装饰器常见用法与进阶技巧

《深度解析Python装饰器常见用法与进阶技巧》Python装饰器(Decorator)是提升代码可读性与复用性的强大工具,本文将深入解析Python装饰器的原理,常见用法,进阶技巧与最佳实践,希望可... 目录装饰器的基本原理函数装饰器的常见用法带参数的装饰器类装饰器与方法装饰器装饰器的嵌套与组合进阶技巧

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据