堆的概念、堆的向下调整算法、堆的向上调整算法、堆的基本功能实现

目录

堆的介绍

堆的概念

堆的性质

堆的结构

堆的向下调整算法

基本思想(以建小堆为例)

 代码

堆的向上调整算法

基本思想(以建小堆为例)

代码

 堆功能的实现

堆的初始化 HeapInit

销毁堆 HeapDestroy

打印堆 HeapPrint

堆的插入 HeapPush

堆的删除 HeapPop

获取堆顶的数据 HeapTop

获取堆的数据个数 HeapSize

堆的判空 HeapEmpty


堆的介绍

堆的概念

        堆:如果有一个关键码的集合K={k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足ki<=k2i+1且ki<=k2i+2(或满足ki>=k2i+1且ki>=k2i+2),其中i=0,1,2,…,则称该集合为堆。
        小堆:将根结点最小的堆叫做小堆,也叫最小堆或小根堆。
        大堆:将根结点最大的堆叫做大堆,也叫最大堆或大根堆。

堆的性质

        堆中某个结点的值总是不大于或不小于其父结点的值。
        堆总是一棵完全二叉树。

堆的结构

堆的向下调整算法

        现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。

但是,使用向下调整算法需要满足一个前提
        若想将其调整为小堆,那么根结点的左右子树必须都为小堆。
        若想将其调整为大堆,那么根结点的左右子树必须都为大堆。 

基本思想(以建小堆为例)

        1.从根结点处开始,选出左右孩子中值较小的孩子。
        2.让小的孩子与其父亲进行比较。
        若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
        若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

 代码

// 定义一个交换函数,用于交换两个整数变量的值
void Swap(int* x, int* y)
{// 创建一个临时变量存储x指向的值int tmp = *x;// 将y指向的值赋给x指向的位置*x = *y;// 将临时变量tmp的值(原x的值)赋给y指向的位置*y = tmp;
}// 定义一个堆的向下调整函数(针对小顶堆)
void AdjustDown(int* a, int n, int parent)
{// 初始化child为当前节点的左孩子的下标,假设左孩子初始时值较小int child = 2 * parent + 1;// 当孩子节点下标小于数组长度时,循环执行以下操作while (child < n){// 如果右孩子存在,并且右孩子的值小于左孩子的值if (child + 1 < n && a[child + 1] < a[child]){// 更新child为较小孩子的下标(即右孩子)child++;}// 如果孩子(左右中较小的一个)的值小于其父节点的值if (a[child] < a[parent]){// 使用交换函数交换父节点和较小的孩子节点的值Swap(&a[child], &a[parent]);// 更新parent为刚交换后较小孩子的下标,准备检查新的子树是否满足堆性质parent = child;// 重新计算下一个待检查的孩子节点的下标child = 2 * parent + 1;}else // 如果当前节点以及其子节点均满足堆性质,则结束调整{break;}}
}

        使用堆的向下调整算法,最坏的情况下(即一直需要交换结点),需要循环的次数为:h - 1次(h为树的高度)。而h = log2(N+1)(N为树的总结点数)。所以堆的向下调整算法的时间复杂度为:O(logN) 。 

        上面说到,使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆才行,那么如何才能将一个任意树调整为堆呢?
        答案很简单,我们只需要从倒数第一个非叶子结点开始,从后往前,按下标,依次作为根去向下调整即可。

// 建堆过程
for (int i = (n - 1 - 1) / 2; i >= 0; i--) 
{// 从最后一个非叶子节点开始,依次对每个节点调用向下调整函数// 这样做可以确保整个堆结构满足小顶堆的性质AdjustDown(php->a, php->size, i);
}

 在这段代码中:

  • `n` 表示堆中的元素个数。
  • 通过 `(n - 1 - 1) / 2` 计算得到最后一个非叶子节点的索引。
  • 遍历从最后一个非叶子节点到根节点(索引为0),依次对每个节点执行向下调整操作,使得每个节点及其子树构成一个小顶堆。
  • `php->a` 是指向堆数组的指针,`php->size` 是堆的大小(元素个数)。
  • 调用 `AdjustDown` 函数逐个对每个父节点进行调整,以保证堆的特性。当遍历完成后,整个数组便构成了一个小顶堆。

堆的向上调整算法

        当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。

基本思想(以建小堆为例)

        1.将目标结点与其父结点比较。

        2.若目标结点的值比其父结点的值小,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是小堆了。

代码

// 定义一个交换函数,用于交换两个自定义类型 HPDataType 指针所指向的数据
void Swap(HPDataType* x, HPDataType* y)
{// 创建一个临时变量存储x指向的数据HPDataType tmp = *x;// 将y指向的数据赋给x指向的位置*x = *y;// 将临时变量tmp的数据(原x的数据)赋给y指向的位置*y = tmp;
}// 定义一个堆的向上调整函数(针对小顶堆)
void AdjustUp(HPDataType* a, int child)
{// 根据孩子节点的下标计算其父节点的下标int parent = (child - 1) / 2;// 当孩子节点的下标大于0(即未到达根节点)时,循环执行以下操作while (child > 0){// 如果孩子节点的值小于其父节点的值if (a[child] < a[parent]){// 使用交换函数交换孩子节点和父节点的数据Swap(&a[child], &a[parent]);// 更新孩子节点为刚刚交换后的父节点,以便继续向上调整child = parent;// 重新计算新的父节点下标parent = (child - 1) / 2;}else // 如果孩子节点与其父节点的值关系已经满足堆的性质,则停止调整{break;}}
}

在上述代码中:

  • HPDataType 是用户自定义的数据类型,这里假设它支持 < 运算符用于比较大小。
  • AdjustUp 函数主要用于将新插入或更新后的元素调整到正确位置,以保持小顶堆的性质。从下标为 child 的节点开始,如果其值小于父节点,则两者交换位置,直到无法继续交换(即已达到根节点或者不再违反堆的性质)。

 堆功能的实现

堆的初始化 HeapInit

        首先,必须创建一个堆类型,该类型中需包含堆的基本信息:存储数据的数组、堆中元素的个数以及当前堆的最大容量。

// 数据类型定义
typedef int HPDataType; // 定义堆中存储数据的类型为整型// 结构体定义
typedef struct Heap
{// 堆中存储数据的数组,动态分配内存来存储堆中的元素HPDataType* a;// 记录堆中已有元素的个数int size;// 记录堆的最大容量,即数组a可容纳的元素数量int capacity;
} HP; 

        创建完堆类型后,我们还需要一个初始化函数,对刚创建的堆进行初始化,注意在初始化期间要将传入数据建堆。

// 定义一个初始化堆的函数
void HeapInit(HP* php, HPDataType* a, int n)
{// 断言检查传入的堆结构指针是否有效assert(php);// 动态申请一块足够容纳n个HPDataType类型数据的内存空间HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType)*n);// 检查内存申请是否成功if (tmp == NULL){printf("动态内存分配失败!\n");exit(-1); // 若分配失败,程序终止运行}// 将新申请的内存空间赋值给堆结构的数组成员php->a = tmp;// 使用memcpy函数将输入数组a中的数据复制到堆结构的数组中memcpy(php->a, a, sizeof(HPDataType)*n);// 设置堆的已有元素个数为输入的nphp->size = n;// 同时设置堆的容量也为nphp->capacity = n;// 初始化堆:从最后一个非叶子节点开始,依次对每个节点进行向下调整操作int i = 0;for (i = (php->size - 1 - 1) / 2; i >= 0; i--){AdjustDown(php->a, php->size, i);}
}

        此函数的作用是初始化一个堆结构,并根据传入的数据构建一个小顶堆。首先动态分配内存以存储堆中的数据,然后将输入数组a中的数据复制到堆结构的数组中。接着设置堆的大小和容量,并从最后一个非叶子节点开始,使用 AdjustDown 函数对每一个节点进行调整,最终使整个数据结构满足小顶堆的性质。 

销毁堆 HeapDestroy

        为了避免内存泄漏,使用完动态开辟的内存空间后都要及时释放该空间,所以,一个用于释放内存空间的函数是必不可少的。

// 定义一个销毁堆的函数
void HeapDestroy(HP* php)
{// 断言检查传入的堆结构指针是否有效assert(php);// 释放之前动态分配给堆结构数组的空间free(php->a);// 置堆结构的数组指针为空,防止野指针问题php->a = NULL;// 将堆中元素个数清零php->size = 0;// 将堆的容量也清零php->capacity = 0;
}

        此函数用于销毁堆结构,释放堆占用的内存资源并将相关状态信息重置为初始状态,以便后续可能的再次初始化或避免产生内存泄漏等问题。 

打印堆 HeapPrint

        打印堆中的数据,这里用了两种打印格式。第一种打印格式是按照堆的物理结构进行打印,即打印为一排连续的数字。第二种打印格式是按照堆的逻辑结构进行打印,即打印成树形结构。

// 计算具有n个节点的完全二叉树的深度
int depth(int n)
{assert(n >= 0);if (n > 0){int m = 2;int height = 1;while (m < n + 1){m *= 2;height++;}return height;}else{return 0;}
}// 打印堆
void HeapPrint(HP* php)
{assert(php);// 按照数组形式打印堆内容int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");// 按照树形结构打印堆内容int tree_depth = depth(php->size);int total_nodes = pow(2, tree_depth) - 1; // 获取对应深度的满二叉树的节点总数int current_spaces = total_nodes - 1; // 记录每一行前面的空格数int current_row = 1; // 当前行数int index = 0; // 待打印数据的下标while (true){// 打印前面的空格for (int i = 0; i < current_spaces; i++){printf(" ");}// 打印数据和间距int nodes_in_row = pow(2, current_row - 1); // 每一行的数字个数while (nodes_in_row--){printf("%02d", php->a[index++]); // 打印数据if (index >= php->size) // 如果所有数据都已打印,则结束打印{printf("\n");return;}int spaces_between_numbers = (current_spaces + 1) * 2; // 两个数之间的空格数for (int j = 0; j < spaces_between_numbers; j++) // 打印两个数之间的空格{printf(" ");}}printf("\n"); // 换行current_row++; // 下一行current_spaces = current_spaces / 2 - 1; // 更新当前行的空格数}
}

这段代码实现了两个功能:

  • depth 函数用于计算具有n个节点的完全二叉树的深度。

  • HeapPrint 函数用于打印堆的内容。首先按数组顺序打印堆的所有元素,然后按照树形结构打印堆,使其看起来像一棵二叉树。通过计算堆对应的完全二叉树的深度,确定每一层节点的数量及相应空格数,从而实现树形结构的打印。

堆的插入 HeapPush

        数据插入时是插入到数组的末尾,即树形结构的最后一层的最后一个结点,所以插入数据后我们需要运用堆的向上调整算法对堆进行调整,使其在插入数据后仍然保持堆的结构。

// 插入元素到堆中
void HeapPush(HP* php, HPDataType x)
{// 断言检查堆结构指针有效性assert(php);// 判断堆是否已满,如果满了则尝试扩大容量if (php->size == php->capacity){// 申请两倍于当前容量的新内存空间HPDataType* tmp = (HPDataType*)realloc(php->a, 2 * php->capacity * sizeof(HPDataType));// 检查内存分配是否成功if (tmp == NULL){printf("动态内存扩充失败!\n");exit(-1); // 分配失败则退出程序}// 成功分配内存后,更新堆结构的数组指针和容量值php->a = tmp;php->capacity *= 2;}// 将新元素添加至堆数组末尾php->a[php->size] = x;// 堆大小加一php->size++;// 调整堆结构,确保新插入元素后的堆仍满足堆属性AdjustUp(php->a, php->size - 1); // 从新增节点开始向上调整
}

        在这个函数中,我们首先检查堆是否已满,若满则通过realloc函数扩大堆容量。接着将新元素添加至堆数组的末尾,并增加堆的大小计数。最后调用`AdjustUp`函数对新插入的元素进行上浮调整,确保堆仍然满足小顶堆的性质。

堆的删除 HeapPop

        堆的删除,删除的是堆顶的元素,但是这个删除过程可并不是直接删除堆顶的数据,而是先将堆顶的数据与最后一个结点的位置交换,然后再删除最后一个结点,再对堆进行一次向下调整。

        原因:我们若是直接删除堆顶的数据,那么原堆后面数据的父子关系就全部打乱了,需要全体重新建堆,时间复杂度为O(N)。

        若是用上述方法,那么只需要对堆进行一次向下调整即可,因为此时根结点的左右子树都是小堆,我们只需要在根结点处进行一次向下调整即可,时间复杂度为 O(log(N))。

// 删除堆顶元素
void HeapPop(HP* php)
{// 断言检查堆结构指针有效性assert(php);// 断言检查堆是否为空,如果不是空堆才能进行删除操作assert(!HeapEmpty(php));// 将堆顶元素(即数组的第一个元素)与堆的最后一个元素交换Swap(&php->a[0], &php->a[php->size - 1]);// 删除堆的最后一个元素,即将堆大小减一php->size--;// 由于堆顶元素可能不再满足堆的性质,因此需要对新的堆顶元素进行向下调整操作,以恢复堆的有序性AdjustDown(php->a, php->size, 0);
}

        此函数首先确认堆不为空,然后通过交换堆顶元素和堆尾元素的位置,实际上将堆尾元素移到了堆顶。接着减少堆的大小表示删除了堆顶元素,最后调用 `AdjustDown` 函数从新的堆顶元素开始向下调整堆,确保剩余元素重新形成符合堆性质的结构。

获取堆顶的数据 HeapTop

        获取堆顶的数据,即返回数组下标为0的数据。

// 获取堆顶元素的值
HPDataType HeapTop(HP* php)
{// 断言检查堆结构指针有效性assert(php);// 断言检查堆是否为空,非空堆才能获取堆顶数据assert(!HeapEmpty(php));// 返回堆顶元素的值,即堆数组的第一个元素return php->a[0];
}

        此函数用于安全地获取堆顶元素的值,在确保堆非空的情况下直接返回堆顶元素(小顶堆中最小的元素)。若堆为空,则会触发断言错误,提示堆为空无法获取堆顶元素。 

获取堆的数据个数 HeapSize

        获取堆的数据个数,即返回堆结构体中的size变量。

// 获取堆中元素个数
int HeapSize(HP* php)
{// 断言检查堆结构指针有效性assert(php);// 返回堆中当前存储的数据个数return php->size;
}
此函数用于获取堆中实际包含的元素数量,只需直接返回堆结构体中的 `size` 成员变量即可。在调用此函数前,需确保堆结构指针有效。

堆的判空 HeapEmpty

        堆的判空,即判断堆结构体中的size变量是否为0。

// 判断堆是否为空
bool HeapEmpty(HP* php)
{// 断言检查堆结构指针有效性assert(php);// 如果堆中数据个数为0,则认为堆为空return php->size == 0;
}
此函数用于检查堆中是否有数据,通过查看堆结构体中的 `size` 成员变量是否为0来判断。当堆中没有元素时,函数返回 true,表示堆为空;否则返回 false,表示堆中有至少一个元素。在调用此函数前,需确保堆结构指针有效。

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

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

相关文章

洛谷 P1021 邮票面值设计

原题链接&#xff1a;[NOIP1999 提高组] 邮票面值设计 - 洛谷 目录 题目描述 解题思路&#xff1a; 代码实现&#xff1a; 题后总结&#xff1a; 题目描述 给定一个信封&#xff0c;最多只允许粘贴 N 张邮票&#xff0c;计算在给定 K&#xff08;NK≤15&#xff09;种邮票…

RAG的进化之路:从单兵作战到多智协作

原文&#xff1a;https://arxiv.org/pdf/2404.15155.pdf 近年来,随着大规模预训练语言模型的蓬勃发展,基于检索的知识问答技术越来越受到学术界和工业界的青睐。其中最具代表性的当属RAG方法。RAG通过将外部知识库集成到语言模型中,对输入的问题进行深入理解、推理,并生成相应的…

【C语言】联合体详解

目录 1.联合体的声明 2.联合体的特点 3.相同成员的结构体和联合体对比 4.联合体大小的计算 1.联合体的声明 像结构体一样&#xff0c;联合体也是由一个或者多个成员构成&#xff0c;这些成员可以不同的类型。但是编译器只为最大的成员分配足够的内存空间。 联合体的特点是所…

骑砍2霸主MOD开发(6)-使用C#-Harmony修改本体游戏逻辑

一.C#-Harmony反射及动态注入 利用C#运行时环境的反射原理,实现对已加载DLL,未加载DLL中代码替换和前置后置插桩. C#依赖库下载地址:霸王•吕布 / CSharpHarmonyLib GitCodehttps://gitcode.net/qq_35829452/csharpharmonylib 根据实际运行.Net环境选择对应版本的0Harmony.dll…

C++(Qt)软件调试---crashpad捕获崩溃(19)

C(Qt)软件调试—crashpad捕获崩溃&#xff08;19&#xff09; 文章目录 C(Qt)软件调试---crashpad捕获崩溃&#xff08;19&#xff09;1、概述2、资源地址3、配置环境4、解决报错5、测试代码6、测试结果7、Qt中使用crashpad 更多精彩内容&#x1f449;个人内容分类汇总 &#x…

矩阵按列相乘运算的并行化实现方法

这两天一直在琢磨如下矩阵计算问题。 已知dm矩阵X和hq矩阵Y&#xff0c;求如下矩阵&#xff1a; 其中X(:,i), Y(:,j)分别表示矩阵X, Y的第i列和第j列&#xff0c;易知Z为dh矩阵。 如果直接串行计算矩阵Z&#xff0c;两个循环共有mq&#xff0c;则会很慢&#xff0c;能不能并行化…

枚举(enum)/共用体(union)/结构体(struct)---详解

前言 C语言包含内置类型和自定义类型。 其实C语言中有内置类型&#xff0c;包含&#xff1a;char,short,int,long,long long,float,double,long double ,这些是C语言本身支持的现成的类型。 但仅仅只有内置类型是远远不够的&#xff0c;在描述一个复杂对象是无法使用内置类型来…

区块链安全应用------压力测试

测试要求&#xff1a; 1. 对以下AccountManager智能合约进行压测(基础要求set函数测试&#xff0c;balanceOf涵为20分加分项)2. 在本地链进行测试&#xff0c;需要监控本地进程的资源使用情况。每个进程的multiOutput属性为Avg3. 需要将每一个更改的配置文件截图&#xff0c;和…

政安晨:【Keras机器学习示例演绎】(十五)—— 用于图像分类的 CutMix 数据增强技术

目录 简介 设置 加载 CIFAR-10 数据集 定义超参数 定义图像预处理函数 将数据转换为 TensorFlow 数据集对象 定义 CutMix 数据增强功能 可视化应用 CutMix 扩增后的新数据集 定义 ResNet-20 模型 使用经 CutMix 扩展的数据集训练模型 使用原始非增强数据集训练模型 …

vscode 配置verilog环境

一、常用的设置 1、语言设置 安装如下插件&#xff0c;然后在config 2、编码格式设置 解决中文注释乱码问题。vivado 默认是这个格式&#xff0c;这里也设置一样。 ctrl shift p 打开设置项 3、插件信任区设 打开一个verilog 文件&#xff0c;显示是纯本文&#xff0c;没…

Xbar控制图的定义和应用

1、定义 Xbar控制图&#xff08;X-Bar Chart&#xff09;是一种统计图表&#xff0c;用于展示数据分布情况。它通过绘制一系列数据点在均值线&#xff08;通常为X轴&#xff09;周围的分布情况来显示数据的波动性。这种图表可以用来监控生产过程、质量管理、金融分析等多个领域…

【MHA】MySQL高可用MHA介绍1-功能,架构,优势,案例

目录 一 MHA 介绍 1 MHA功能 自动化主服务器监控和故障转移 交互式&#xff08;手动启动的&#xff09;主故障转移 非交互式主故障转移 在线切换主机 2 主服务器故障转移的难点 二 MHA架构 1 MHA组件 2 自定义扩展&#xff08;脚本&#xff09; 三 MHA优势 1 MHA可以…

【Godot4自学手册】第三十八节给游戏添加音效

今天&#xff0c;我的主要任务就是给游戏添加音效。在添加音效前&#xff0c;我们需要了解一个东西&#xff1a;音频总线。这个东西或许有些枯燥&#xff0c;如果你只为添加一个音效没必要了解太多&#xff0c;但如果你以后将要经常与音频播放打交道&#xff0c;还是要了解一下…

政安晨:【深度学习神经网络基础】(十三)—— 卷积神经网络

目录 概述 LeNet-5 卷积层 最大池层 稠密层 针对MNIST数据集的卷积神经网络 总之 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎…

ReentrantLock 源码解析

ReentrantLock 源码解析 文章目录 ReentrantLock 源码解析前言一、字段分析二、内部类分析1、Sync2、FairSync3、NonfairSync 三、方法分析1、构造方法2、其他方法 总结 前言 ReentrantLock 实现了 Lock 接口&#xff0c;内部基于 AQS 实现。所以想要弄懂 ReentrantLock &#…

vue 实现左侧导航栏,右侧锚点定位滚动到指定位置(超简单方法)

项目截图&#xff1a; 实现方法&#xff1a; 点击左侧菜单根据元素id定位到可视内容区域。 浏览器原生提供了一种方法scrollIntoView 。 通过scrollIntoView方法可以把元素滚动到可视区域内。 behavior: "smooth"是指定滚动方式为平滑效果。 具体代码如下&#xf…

使用 PhpMyAdmin 安装 LAMP 服务器

使用 PhpMyAdmin 安装 LAMP 服务器非常简单。按照下面所示的步骤&#xff0c;我们将拥有一个完全可运行的 LAMP 服务器&#xff08;Linux、Apache、MySQL/MariaDB 和 PHP&#xff09;。 什么是 LAMP 服务器&#xff1f; LAMP 代表 Linux、Apache、MySQL 和 PHP。它们共同提供…

智能化安全防护:AI防火墙的原理与应用

随着人工智能技术的迅猛发展&#xff0c;其在各个领域的应用也日益广泛。作为引领数字化转型的重要力量&#xff0c;AI技术为我们的生活和工作带来了前所未有的便利与效率。在通信领域&#xff0c;人工智能的应用同样展现出了巨大的潜力和价值&#xff0c;特别是在网络安全防护…

HTTP/1.1,HTTP/2.0和HTTP/3.0 各版本协议的详解(2024-04-24)

1、HTTP介绍 HTTP 协议有多个版本&#xff0c;目前广泛使用的是 HTTP/1.1 和 HTTP/2&#xff0c;以及正在逐步推广的 HTTP/3。 HTTP/1.1&#xff1a;支持持久连接&#xff0c;允许多个请求/响应通过同一个 TCP 连接传输&#xff0c;减少了建立和关闭连接的消耗。 HTTP/2&#…

基于PaddlePaddle平台训练物体分类——猫狗分类

学习目标&#xff1a; 在百度的PaddlePaddle平台训练自己需要的模型&#xff0c;以训练一个猫狗分类模型为例 PaddlePaddle平台&#xff1a; 飞桨&#xff08;PaddlePaddle&#xff09;是百度开发的深度学习平台&#xff0c;具有动静统一框架、端到端开发套件等特性&#xf…