堆详解(C语言实现)

文章目录

  • 写在前面
  • 1. 堆的概念和性质
    • 1.1 堆的概念
    • 1.2 堆的性质
  • 2 堆的实现
    • 2.1 堆结构的定义
    • 2.2 堆的初始化
    • 2.3 堆的插入
      • 2.3.1 向上调整算法
      • 2.3.2 堆的插入元素过程
    • 2.4 堆的删除
      • 2.4.1 向下调整算法
      • 2.4.2 堆的删除元素过程
    • 2.5 获取堆顶元素
    • 2.6 获取堆元素个数
    • 2.7 判断堆是否为空
    • 2.8 堆的销毁

写在前面

本片文章使用C语言实现了一种非线性的数据结构——堆(小根堆)。

1. 堆的概念和性质

1.1 堆的概念

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

画个图理解一下:
在这里插入图片描述

1.2 堆的性质

  1. 堆中某个节点的值总是不大于或不小于其父节点的值;
  2. 堆总是一棵完全二叉树。
    在这里插入图片描述

2 堆的实现

由于堆的逻辑结构是一颗完全二叉树,所以在实际存储时,通常使用数组来表示这颗完全二叉树。

数组表示法的好处:

  1. 由于完全二叉树的性质,数组表示法可以将元素按照层级顺序依次存储,不会有额外的空间浪费,使得存储更为紧凑。
  2. 数组的索引关系可以方便地表示父节点和子节点之间的关系。对于数组中的第 i 个元素,其左子节点2i+1 的位置,右子节点2i+2 的位置,而父节点在 (i−1) / 2的位置。

画个图理解一下:
在这里插入图片描述

这种数组表示法使得堆的操作更加高效,因为可以通过索引直接访问父子节点,而无需使用指针。

2.1 堆结构的定义

从上面的分析可以知道堆结构的定义通常包括一个动态开辟的数组,用来存储数据。为了方便扩容,增加一个用于记录堆中元素个数以及一个用于记录堆容量的变量。
下面是堆结构的定义:

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* nums;//动态开辟的数组
	int size;//堆中元素个数
	int capacity;//堆容量
}Heap;

2.2 堆的初始化

在初始时,堆是一个空堆,因此将堆的元素数组指针 nums 初始化为 NULL,表示还没有分配存储空间。此时 capacity 为 0,表示堆的容量为 0,还没有存储任何元素。size 也为 0,表示当前堆中的元素个数为 0。

这种初始化状态是堆数据结构的起始状态,之后可以根据需要动态分配存储空间,并通过插入元素来逐渐构建堆的结构。
代码如下:

void HeapInit(Heap* php)
{
	assert(php);//检查参数有效性
	php->nums = NULL;//初始化为 NULL,表示还没有分配存储空间
	php->size = php->capacity = 0;
}

2.3 堆的插入

2.3.1 向上调整算法

在实现堆的插入之前需要介绍一下向上调整算法。这是因为向上调整算法是在堆的插入操作中使用的关键步骤。这算法确保在插入新元素后,仍然保持堆的性质
以下是向上调整算法的一般步骤:

  1. 从插入的节点开始,向上比较与父节点的大小。
  2. 如果插入节点的值大于(小于)其父节点的值,交换这两个节点。
  3. 重复步骤 2 直到满足堆的性质或达到堆的根节点。

如向该小根堆:{ 15,18,19,25,28,34,65,49,27,37 }插入一个10,并用向上调整算法使该小根堆继续保持小根堆的性质:

画个图理解一下:
在这里插入图片描述
代码如下:

//交换两个数
void Swap(HPDataType* x1, HPDataType* x2)
{
	HPDataType tmp = *x1;
	*x1 = *x2;
	*x2 = tmp;
}
//向上调整算法
void AdjustUp(int* nums, int child)
{
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		//1.小堆 向上调整,孩子小于父亲就交换
		//2.如果满足堆的性质,则不需要调整
		//若实现大根堆,这里nums[child] < nums[parent]的 < 换成 >
		if (nums[child] < nums[parent])
		{
			Swap(&nums[child], &nums[parent]);
		}
		else
		{
			break;
		}
		child = parent;
	}
}

需要注意的是:堆的向上调整算法的前提是,插入位置之前的元素已经构成了一个有效的堆。这确保了插入新元素后,通过向上调整以后,整个堆仍然是一个有效的小顶堆。

2.3.2 堆的插入元素过程

在上面向上调整算法的介绍中,我们发现向堆中插入元素时,只需把新元素追加到堆的末尾,然后通过向上调整算法,使整个堆仍然是一个有效的堆。
堆的插入元素过程通常包括以下步骤:

  1. 将新元素追加到堆的末尾。在堆的实现过程中,我们是使用数组来表示堆的,而在数组表示的堆中,堆的末尾就是数组的最后一个位置,因此新元素被添加到数组的最后一个位置。
  2. 进行向上调整。 新元素与其父节点进行比较,如果新元素的值小于其父节点的值,它们交换位置。这个过程一直重复,直到满足堆的性质,即新插入的元素不再小于其父节点或者到达堆顶。

分析:
3. 当堆为空时,插入第一个元素时,它天然满足堆的性质,因此,无需进行向上调整。
4. 当堆不为空时,插入元素时就有可能会进行向上调整。这是因为插入的新元素被追加到堆的末尾,与其父节点进行比较。如果新元素的值小于其父节点的值,就会交换它们的位置。这个过程重复进行,直到新插入的元素不再小于其父节点或者到达堆顶。

画个图理解一下:
在这里插入图片描述

代码如下:

void HeapPush(Heap* php, HPDataType x)
{
	assert(php);//检查参数有效性
	//检查是否需要扩容
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->nums, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("HeapPush->realloc");
			exit(-1);
		}
		php->nums = tmp;
		php->capacity = newcapacity;
	}
	//插入新元素
	php->nums[php->size++] = x;
	//向上调整
	AdjustUp(php->nums, php->size - 1);
}

2.4 堆的删除

2.4.1 向下调整算法

在实现堆的删除操作之前需要介绍一下向下调整算法。这是因为向下调整算法是在堆的删除操作中使用的关键步骤。向下调整算法通常在删除堆顶元素后使用,确保新的堆顶元素能够找到适当的位置以维持堆的有序性。

确保向下调整算法有效的前提:

堆的左右子树都满足堆的性质。这是因为向下调整算法的目的是将当前节点与其左右孩子中较小(或较大,具体取决于是小根堆还是大根堆)的值交换,以恢复堆的有序性。
如果堆的左右子树不满足堆的性质,那么通过向下调整算法进行交换可能无法保证整个堆的有序性。因此,在使用向下调整算法时,必须确保该节点的左右子树都是堆。

向下调整算法具体步骤如下:

  1. 找到较小的孩子: 如果存在左右孩子,找到值较小的孩子。如果当前节点有左孩子(位置为 2 * parent + 1),如果有右孩子(位置为 2 * parent + 2)。

  2. 比较与较小孩子的值: 将当前节点与较小孩子的值比较。

  3. 交换或结束: 如果当前节点的值大于较小孩子的值,则交换它们,并继续向下调整。如果当前节点的值小于等于较小孩子的值,则调整结束。

  4. 循环: 重复执行上述步骤,直到当前节点不再有孩子或当前节点的值小于等于孩子的值。

画个图理解一下:
在这里插入图片描述

代码如下:

//交换两个数
void Swap(HPDataType* x1, HPDataType* x2)
{
	HPDataType tmp = *x1;
	*x1 = *x2;
	*x2 = tmp;
}
void AdjustDown(int* nums, int n, int parent)
{
	// 左孩子的索引
	int child = parent * 2 + 1;
	// 循环直到没有左孩子
	while (child < n)
	{
		// 如果右孩子存在且比左孩子小,选择右孩子
		//若实现大根堆,这里nums[child + 1] < nums[child]的 < 换成 >
		if (child + 1 < n && nums[child + 1] < nums[child])
		{
			++child;
		}
		// 如果孩子比父亲小,交换它们的值
		//若实现大根堆,这里nums[child] < nums[parent]的 < 换成 >
		if (nums[child] < nums[parent])
		{
			// 孩子比父亲大,堆的有序性已经恢复,退出循环
			Swap(&nums[child], &nums[parent]);
		}
		else
		{
			break;
		}
		// 更新父亲和孩子的索引
		parent = child;
		child = parent * 2 + 1;
	}
}

2.4.2 堆的删除元素过程

堆的删除元素过程通常包括以下步骤:

  1. 将堆顶元素与最后一个元素交换:为了方便删除堆顶元素,将堆顶元素与最后一个元素交换位置。
  2. 删除堆顶元素:将堆顶元素删除,此时堆的大小减一。
  3. 向下调整堆:由于删除了堆顶元素,需要通过向下调整算法,保持堆的有序性。向下调整的目的是将交换后的堆顶元素放置到正确的位置,使得堆仍然满足堆的性质。

画个图理解一下:
在这里插入图片描述

代码如下:

void HeapPop(Heap* php)
{
	assert(php);//检查参数有效性
	assert(!HeapEmpty(php));//检查堆是否为空

	Swap(&php->nums[0], &php->nums[php->size - 1]);

	php->size--;

	AdjustDown(php->nums, php->size - 1, 0);
}

2.5 获取堆顶元素

获取堆顶元素步骤:

  1. 堆不为空时,才能取堆顶数据。
  2. 获取堆顶元素的操作是直接访问堆数组的第一个元素,因为在堆的表示中,堆顶元素总是存储在数组的第一个位置。

代码如下:

HPDataType HeapTop(Heap* php)
{
	assert(php);//检查参数有效性
	assert(!HeapEmpty(php));//检查堆是否为空

	return php->nums[0];
}

2.6 获取堆元素个数

获取堆元素个数的操作可以直接返回堆的成员变量 size,该变量记录了堆中当前元素的个数。
代码如下:

int HeapSize(Heap* php)
{
	assert(php);//检查参数有效性
	return php->size;
}

2.7 判断堆是否为空

判断堆是否为空的操作可以通过检查堆的成员变量 size 是否为零来实现。如果 size 为零,表示堆中没有元素,即堆为空;否则,堆非空。
代码如下:

bool HeapEmpty(Heap* php)
{
	assert(php);//检查参数有效性
	return php->size == 0;
}

2.8 堆的销毁

在销毁堆的操作中,我们首先使用 free 函数释放堆中的元素数组所占用的内存,然后将数组指针设置为 NULL,并将堆的大小和容量都设置为0,表示堆已被销毁。这样的操作确保了在释放内存的同时将堆的状态清空,防止野指针和内存泄漏的问题。
代码如下:

void HeapDestroy(Heap* php)
{
	assert(php);//检查参数有效性
	free(php->nums);

	php->nums = NULL;
	php->size = php->capacity = 0;
}

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。
创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言——打印出所有的“水仙花数”

所谓水仙花数,是指一个3位数,其各位数字立方和等于该数本身。水仙花数是指一个三位数&#xff0c;它的每个位上的数字的立方和等于它本身。例如&#xff0c;153是一个水仙花数&#xff0c;因为1^3 5^3 3^3 153。 #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>…

【刷题笔记】分糖果||数组||暴力通过||符合思维方式||多案例分析

分发糖果 文章目录 分发糖果1 题目描述2 题目分析2.1 寻找波峰波谷2.2 从波底往波峰攀爬&#xff01;2.2 计算糖果 3 代码附录1 1 题目描述 https://leetcode.cn/problems/candy/ n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求&…

kafka的详细安装部署

简介&#xff1a; Kafka是一个分布式流处理平台&#xff0c;主要用于处理高吞吐量的实时数据流。Kafka最初由LinkedIn公司开发&#xff0c;现在由Apache Software Foundation维护和开发。 Kafka的核心是一个分布式发布-订阅消息系统&#xff0c;它可以处理大量的消息流&#…

开始使用Spring Boot Admin吧-使用Nacos注册SBA

什么是 Spring Boot Admin&#xff08;SBA&#xff09;? Spring Boot Admin 是 codecentric 公司开发的一款开源社区项目&#xff0c;目标是让用户更方便的管理以及监控 Spring Boot 应用。 应用可以通过我们的Spring Boot Admin客户端&#xff08;通过HTTP的方式&#xff0…

浙江启用无人机巡山护林模式,火灾扑救效率高

为了保护天然的森林资源&#xff0c;浙江当地林业部门引入了一种创新技术&#xff1a;林业无人机。这些天空中的守护者正在重新定义森林防火和护林工作的方式。 当下正值天气干燥的季节&#xff0c;这些无人机开始了它们的首次大规模任务。它们在指定的林区内自主巡逻&#xff…

C++二分查找或并集查找:交换得到字典序最小的数组

作者推荐 利用广度优先或模拟解决米诺骨牌 本文涉及的基础知识点 二分查找算法合集 题目 给你一个下标从 0 开始的 正整数 数组 nums 和一个 正整数 limit 。 在一次操作中&#xff0c;你可以选择任意两个下标 i 和 j&#xff0c;如果 满足 |nums[i] - nums[j]| < limi…

Ubuntu20.04部署TVM流程及编译优化模型示例

前言&#xff1a;记录自己安装TVM的流程&#xff0c;以及一个简单的利用TVM编译模型并执行的示例。 1&#xff0c;官网下载TVM源码 git clone --recursive https://github.com/apache/tvmgit submodule init git submodule update顺便完成准备工作&#xff0c;比如升级cmake版本…

Vue3中调用外部iframe链接方法

业务场景&#xff0c;点击某个按钮需要跳转到外部iframe的地址&#xff0c;但是需要在本项目内显示。以前项目中写过调用外部链接的功能&#xff0c;是有菜单的&#xff0c;但是这次是按钮&#xff0c;所以不能直接把地址配到菜单里。 实现方法&#xff1a;在本地路由文件里写个…

解决git与huggingface项目下载速度慢或者失败的问题

git clone 项目报错 比如使用git clone 下载项目&#xff1a; git clone https://github.com/ChuRuaNh0/FastSam_Awsome_TensorRT.git有时候会报以下错误&#xff1a; fatal: unable to access ‘https://github.com/xxx.git/’: Failed to connect to github.com port 443 …

Unity打出的安卓包切换后台再恢复前台,卡顿许久问题记录

连接AndroidStudio发现当切换后台时提示&#xff1a;D/Unity: Multi-casting "[IP] 192.168.31.231 [Port] 55000 [Flags] 19 [Guid] 1268732307 [EditorId] 264356214 [Version] 1048832 [Id] AndroidPlayer(11,Xiaomi_M2012K11AC192.168.31.231) [Debug] 0 [PackageName…

MATLAB实战 | 不同形式的三维曲面图

通常&#xff0c;MATLAB中绘制三维曲面图&#xff0c;先要生成网格数据&#xff0c;再调用mesh函数和surf函数绘制三维曲面。若曲面用含两个自变量的参数方程定义&#xff0c;则还可以调用fmesh函数和fsurf函数绘图。若曲面用隐函数定义&#xff0c;则可以调用fimplicit3函数绘…

医学影像PACS源码:PACS系统的基础知识(DICOM、HL7、SWF)

1、PACS PACS是Picture Archiving and Communication Systems首字母缩写&#xff0c;全称为影像储存和传输系统&#xff0c;涉及放射医学、计算机技术、通讯技术及数字图像技术等&#xff0c;是医院信息系统的重要组成部分&#xff0c;是将数字医疗设备(如X线、CT、MRI、超声、…

(C++)字符串相乘

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 题目链接如下&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名…

C# WPF上位机开发(第一个应用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 万事开头难&#xff0c;很多事情都是难在第一步。走出了这第一步&#xff0c;回过头看以前走的每一步&#xff0c;发现其实也不难。用c# wpf编写界…

nodejs+vue+mysql皮具行李箱包包网上商城购物网站

本系统可分为两个大的模块&#xff0c;即前台用户模块和后台管理员模块&#xff0c;前台用户模块用户可以进行浏览查询皮具的各种信息&#xff0c;添加购物车&#xff0c;下订单等各种操作。后台管理员模块管理员可以进行皮具的处理&#xff0c;还有处理订单&#xff0c;皮具分…

【C/PTA —— 12.指针1(课内实践)】

C/PTA —— 12.指针1&#xff08;课内实践&#xff09; 6-1 交换两个整数的值6-2 利用指针找最大值6-3 字符串的连接6-4 移动字母 6-1 交换两个整数的值 void fun(int* a, int* b) {int* tmp *a;*a *b;*b tmp; }6-2 利用指针找最大值 void findmax(int* px, int* py, int* p…

easyexcel指定sheet页动态给行列加背景色

easyexcel&#xff0c;有多个sheet页&#xff0c;某些sheet页的行、列动态需要加背景色 import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.m…

任意多个磁盘时的kickstart配置方法

最近工作遇到一个需求&#xff1a;当机器中存在任意多个磁盘时&#xff0c;kickstart配置文件应该如何编写&#xff1f; 我查询了一些资料&#xff0c;得到的结果大多是针对特定数量的磁盘的配置&#xff08;比如&#xff0c;2个&#xff0c;3个&#xff09;。 那么假如因为某些…

opencv-医学图像预处理

医学图像预处理通常需要针对特定任务和数据集的特点进行定制。以下是一些常见的医学图像预处理步骤&#xff0c;可以使用OpenCV以及其他相关库来实现&#xff1a; 导入相关的库 import cv2 import matplotlib.pyplot as plt1. 读取图像 image cv2.imread(r"C:\Users\m…