【数据结构与算法】探索数组在堆数据结构中的妙用:从原理到实现

news2024/11/18 18:35:45

     

            💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

 

1b7335aca73b41609b7f05d1d366f476.gif​​

 

目录

一、引言

二、堆的基本概念

🍃堆的特性

🍃堆的分类

三、数组与堆的关联

🍃为什么选择数组

🍃数组与堆的映射关系(重要)

四、堆的结构定义

五、堆的接口实现

🍃初始化

🍃销毁

🍃向上调整算法

🍃入堆

🍃向下调整算法

🍃出堆

🍃取堆顶元素

🍃对堆判空

🍃获取堆的数据个数

六、C语言实现堆的代码示例

🍃Heap.h        //堆的头文件

🍃Heap.c        //堆的源文件

🍃test.c           //mian函数测试文件

🍃测试结果

七、性能分析

八、应用场景

九、总结


 

 

一、引言

堆是一种特殊的树形数据结构,其每个节点的值都大于或等于(大顶堆)或小于或等于(小顶堆)其子节点的值。在计算机科学中,堆常用于实现优先级队列、堆排序等算法。本文将探讨如何使用数组实现堆,并分析其原理、实现细节以及应用场景。

 

二、堆的基本概念

🍃堆的特性

  • 堆是一棵完全二叉树,通常使用数组进行存储。
  • 堆中任意节点的值都满足堆的性质,即大顶堆中父节点的值大于或等于其子节点的值,小顶堆中父节点的值小于或等于其子节点的值。

🍃堆的分类

  • 大顶堆:父节点的值大于或等于其子节点的值。
  • 小顶堆:父节点的值小于或等于其子节点的值。

 

三、数组与堆的关联

🍃为什么选择数组

  • 数组在内存中是连续存储的,可以高效地进行访问和修改。
  • 对于完全二叉树,可以使用数组进行简单的索引计算来访问任意节点。

 注意:我们只是把数组在逻辑上想象成了抽象的堆,其实它本质上就是数组a3b50d0af7e54d1eba1f0c6ce269e276.png

🍃数组与堆的映射关系(重要)

  • 若某节点在数组中的下标为i(i从0开始),则其左子节点(若存在)的下标为2i+1,右子节点(若存在)的下标为2i+2,其父节点(若存在)的下标为(i-1)/2
  • 堆的根节点在数组中的下标通常为0。

 

四、堆的结构定义

堆的结构定义与顺序表基本是一致的,这也更说明了堆的概念更多的是在逻辑上更加抽象

包括

 

  • 指向某种数据类型的指针(用来实现数组)
  • 数组的有效数据个数size
  • 数组的空间大小capacity
typedef int HPDataType;//数据类型重定义
typedef struct Heap//堆的结构定义
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

 

五、堆的接口实现

🍃初始化

  • 首先对形参接收的地址判空
  • 指针初始为NULL
  • size和capacity初始为0
void HeapInit(Heap* php)//初始化
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

 

🍃销毁

  • 对形参接收的地址判空
  • 释放为数组动态开辟的空间,并置为NULL
  • size和capacity修改为0
void HeapDestory(Heap* hp)//销毁
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->size = hp->capacity = 0;

}

 

🍃向上调整算法(重要)

  • (该函数在这里是为入堆准备的)
  • 接收两个参数,分别是数组或指针,以及对应需要调整的节点位置
  • 思想:从该位置向上调整,直到父子满足大小关系,或调整至根结点
void Adjustup(HPDataType* a, int child)//向上调整算法
{
	assert(a);//数组必须存在,否则解引用就会报错
	int parent = (child - 1) / 2;
	while (child > 0 && a[parent] > a[child])//这里以小堆调整为例
	{
		Swap(&a[parent], &a[child]);//交换数据必须传地址
		child = parent;
		parent= (child - 1) / 2;
	}
}

这里额外封装了一个交换函数,方便后面多次使用,并且想要通过形参改变实参的值,需要传址调用

void Swap(HPDataType* a, HPDataType* b)//交换函数
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

 

🍃入堆

  • 接收两个参数:数组或指针,以及要插入的数据
  • 对形参接收的地址判空
  • 判断数组有剩余空间(若不足,扩容)
  • 将新数据插入到数组最后一个有效数据的后面
  • 之后调用向上调整算法 重新调整为堆
void HeapPush(Heap* hp, HPDataType x)//入堆
{
	assert(hp);//接收的堆地址必须是有效的
	if (hp->size == hp->capacity)//判断是否需要扩容
	{
		int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("Push perror\n");
			exit(1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size++] = x;//插入到尾部
	Adjustup(hp->a, hp->size - 1);//进行向上调整
}

 

🍃向下调整算法(重要)

  • 接收三个参数,数组或指针,以及parent对应要调整的位置,比向上调整算法额外多一个参数n(数组有效数据个数),用来判断是否调整到叶子结点
  • 思想:以小堆为例,child等于parent两个孩子中较小的孩子,从该位置开始比较和调整,直到满足堆的大小关系或者调整到叶子结点
void Adjustdown(HPDataType* a, int parent, int n)//向下调整算法
{
	assert(a);
	int child = parent * 2 + 1;//先假设左孩子小
	
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])//这里以小堆调整为例
			child++;//如果右孩子存在,且右孩子小,父节点与右孩子进行比较
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

 

🍃出堆

  • 接收一个参数:数组或指针,表示堆
  • 首先对形参接收的地址判空
  • 然后判断堆是否为空
  • 交换堆顶和堆尾数据,size--
  • 然后从堆顶开始进行向下调整
void HeapPop(Heap* hp)//出堆
{
	assert(hp);
	assert(hp->size);//判断堆不为空
	Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));
	hp->size--;//第一个数据与最后一个数据交换,然后删除最后一个
	Adjustdown(hp->a, 0, hp->size);
}

 

🍃取堆顶元素

  • 对形参判空,并且堆不能为空
  • 然后返回数组的第一个数据
HPDataType HeapTop(Heap* hp)// 取堆顶的数据
{
	assert(hp);
	assert(hp->size);
	return hp->a[0];
}

 

🍃对堆判空

  • 对形参判空
  • 然后返回size==0的结果
int HeapEmpty(Heap* hp)//堆的判空
{
	assert(hp);
	return hp->size == 0;
}

 

🍃获取堆的数据个数

  • 对形参判空
  • 然后返回size
int HeapSize(Heap* hp)//堆的数据个数
{
	assert(hp);
	return hp->size;
}

 

 

六、C语言实现堆的代码示例

🍃Heap.h        //堆的头文件

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>


typedef int HPDataType;//数据类型重定义
typedef struct Heap//堆的结构定义
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;

//堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* hp);

//向上调整算法
void Adjustup(HPDataType* a, int child);

//交换函数
void Swap(HPDataType* a, HPDataType* b);

// 堆的插入
void HeapPush(Heap* hp, HPDataType x);

// 向下调整算法
void Adjustdown(HPDataType* a, int parent, int n);

// 堆的删除
void HeapPop(Heap* hp);

// 取堆顶的数据
HPDataType HeapTop(Heap* hp);

// 堆的数据个数
int HeapSize(Heap* hp);

// 堆的判空
int HeapEmpty(Heap* hp);

 

🍃Heap.c        //堆的源文件

#include"Heap.h"

void HeapInit(Heap* php)//初始化
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

// 堆的销毁
void HeapDestory(Heap* hp)//销毁
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->size = hp->capacity = 0;

}

void Swap(HPDataType* a, HPDataType* b)//交换函数
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}


void Adjustup(HPDataType* a, int child)//向上调整算法
{
	assert(a);//数组必须存在,否则解引用就会报错
	int parent = (child - 1) / 2;
	while (child > 0 && a[parent] > a[child])//这里以小堆调整为例
	{
		Swap(&a[parent], &a[child]);//交换数据必须传地址
		child = parent;
		parent= (child - 1) / 2;
	}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)//入堆
{
	assert(hp);//接收的堆地址必须是有效的
	if (hp->size == hp->capacity)//判断是否需要扩容
	{
		int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("Push perror\n");
			exit(1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size++] = x;//插入到尾部
	Adjustup(hp->a, hp->size - 1);//进行向上调整
}

void Adjustdown(HPDataType* a, int parent, int n)//向下调整算法
{
	assert(a);
	int child = parent * 2 + 1;//先假设左孩子小
	
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])//这里以小堆调整为例
			child++;//如果右孩子存在,且右孩子小,父节点与右孩子进行比较
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}


void HeapPop(Heap* hp)//出堆
{
	assert(hp);
	assert(hp->size);//判断堆不为空
	Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));
	hp->size--;//第一个数据与最后一个数据交换,然后删除最后一个
	Adjustdown(hp->a, 0, hp->size);
}


HPDataType HeapTop(Heap* hp)// 取堆顶的数据
{
	assert(hp);
	assert(hp->size);
	return hp->a[0];
}


int HeapSize(Heap* hp)//堆的数据个数
{
	assert(hp);
	return hp->size;
}


int HeapEmpty(Heap* hp)//堆的判空
{
	assert(hp);
	return hp->size == 0;
}

 

🍃test.c           //mian函数测试文件

#include"Heap.h"

void test1()
{
	Heap hp;
	HeapInit(&hp);//初始化
	if (HeapEmpty(&hp))
		printf("堆空\n");
	else
		printf("堆非空\n");
	int arr[] = { 5,7,8,11,2,9,15,13,21};
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)//数据入堆
	{
		HeapPush(&hp, arr[i]);
	}
	if (HeapEmpty(&hp))
		printf("堆空\n");
	else
		printf("堆非空\n");
	printf("堆的数据个数:%d\n", HeapSize(&hp));
	while (hp.size)//每次打印堆顶元素,并出堆
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("堆的数据个数:%d\n", HeapSize(&hp));
	HeapDestory(&hp);
}

int main()
{
	test1();
	return 0;
}

 

🍃测试结果

e213aeec74e34f4d9547d104984c3344.png

 

七、性能分析

  • 堆的插入和删除操作的时间复杂度均为O(log n),这使得堆在处理大规模数据时具有较高的效率。
  • 与其他数据结构(如链表)相比,数组在实现堆时具有更好的空间利用率和访问速度。

 

八、应用场景

  • 优先队列:堆可以高效地实现优先队列,支持按照元素的优先级进行插入和删除操作。
  • 堆排序:堆排序是一种基于堆的排序算法,具有O(nlogn)的时间复杂度。
  • 数据流中的TopK问题:在处理数据流时,可以使用堆来快速找到前K大或前K小的元素。

九、总结

本文详细介绍了数组在堆数据结构中的妙用,并通过具体的代码示例和性能分析展示了其高效性和灵活性。通过深入学习堆的概念和实现方法,我们可以更好地理解其原理和应用场景,并在实际编程中灵活运用堆数据结构来解决各种问题。

 

如果看完本篇文章对您有所帮助,麻烦三连支持一下

 

 

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

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

相关文章

Python Tkinter:开发一款文件加密解密小工具

在这个信息泄露风险日益增加的时代&#xff0c;使用文件加密工具对于保护个人隐私和企业机密至关重要。 本文介绍了一款小工具——encryptDecrypt&#xff0c;它不仅提供了一个易于使用的图形界面&#xff0c;简化了加密和解密过程&#xff0c;还确保了数据的安全性&#xff0c…

国产压缩包工具——JlmPackCore SDK说明(一)

一、什么是JlmPackCore SDK &#xff08;1&#xff09;自主可控 JlmPackCore是一套基于我国自主知识产权的核心算法发明专利——杰林码&#xff08;详系请参考《杰林码原理及应用》一书&#xff0c;也可以参考后续发表的相关论文&#xff09;&#xff0c;其中一篇会议论文&…

完全入门C语言

1.C语言 C语言是一门通用的计算机编程语言&#xff0c;广泛应用于底层开发。其设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。有良好的跨平台的特性。 同时C语言还是面向过程的编程语言&#xff0c;…

汇编基础语法

指令格式 1、立即数 一个常数&#xff0c;该常数必须对应8位位图&#xff0c;即一个8位的常数通过循环右移偶数位得到该数&#xff0c;该数位合法立即数 在指令中表示方法&#xff1a;#数字&#xff0c;例如&#xff1a;#100 快速判定是否是合法立即数&#xff1a; 首先将这…

一款可以编辑SVG矢量图形的方法(以桑基图为例)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、svg矢量图片在线绘制网站二、保存SVG格式图片二、修改SVG图片中的字体大小一、svg矢量图片在线绘制网站 提示:有些图不想用代码出图时,可以采用这个网站,无需注册,无VIP: 网站地址https…

FFmpeg视频处理工具安装使用

一、前言 FFmpeg是流行的开源视频处理工具&#xff0c;用于转码、合并、编辑等。以下是安装和使用方法&#xff1a; 二、步骤 1.下载 1.1 ffmpeg下载 官网下载地址 wget https://www.ffmpeg.org/releases/ffmpeg-6.1.1.tar.xz1.2 nasm下载 https://www.nasm.us/pub/nasm/…

桥感应加热主电路拓扑结构及控制原

1 桥感应加热主电路拓扑结构及控制原理 1.1 主电路拓扑 本文所述中频感应加热电源采用交—直—交的变频原理&#xff0c;三相50Hz的正弦交流输入电压经过整流滤波为540V平滑直流电压&#xff0c;再经逆变器将直流电压变成不同频率的交流电压供负载使用。本文采用半桥串联谐振…

【echarts】如何关闭dataZoom-silder 组件中数据阴影(缩略图、数据走势图)

echarts开启 “滑动条型数据区域缩放组件&#xff08;dataZoomInside&#xff09;”后&#xff0c;默认会显示数据的走势图。 但有时候我们并不需要。 如何关闭呢&#xff1f; 官方有提供一个属性&#xff1a;showDataShadow https://echarts.apache.org/zh/option.html#da…

无线物联网题集

测试一 未来信息产业的发展在由信息网络向 全面感知和 智能应用两个方向拓展、延伸和突破。 各国均把 物联网作为未来信息化战略的重要内容,融合各种信息技术,突破互联网的限制,将物体接入信息网络。 计算机的出现,开始了第四次工业革命,开始了人机物的高度融合&#xff08;&…

【Unity】 HTFramework框架(五十二)使用 HybridCLR 热更新

更新日期&#xff1a;2024年7月1日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 HybridCLR 热更新一、启用宏定义二、导入HybridCLR三、设置热更新程序集四、资源、代码热更 HybridCLR 热更新 HybridCLR是一个特性完整、零成本、高性能…

探索 ONLYOFFICE 8.1:云端协作的新纪元

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

如果对方没做幂等!记一次生产订单重复的反思

最近公司公司的旧系统中发现了一个bug。业务部门反馈&#xff0c;尽管用户只支付了一年的服务费用&#xff0c;系统却将有效期增加了两年。 原因分析&#xff1a; 到底是什么原因呢&#xff1f; 经过日志分析&#xff0c;发现消息队列&#xff08;MQ&#xff09;向第三方服务发…

EDI是什么?与ERP有何关系

EDI的发展过程 电子数据交换&#xff08;Electronic Data Interchange&#xff0c;EDI&#xff09;是一种通过电子方式传输商业文件的技术。EDI的历史可以追溯到20世纪60年代&#xff0c;当时企业开始使用计算机进行数据处理。最早的EDI系统是为解决大型企业间的信息交换问题而…

【刷题汇总--数字统计、两个数组的交集、点击消除(栈)】

C日常刷题积累 今日刷题汇总 - day0011、数字统计1.1、题目1.2、思路1.3、程序实现 2、两个数组的交集2.1、题目2.2、思路2.3、程序实现 3、点击消除(栈)3.1、题目3.2、思路3.3、程序实现 今日刷题汇总 - day001 1、数字统计 1.1、题目 请统计某个给定范围[L, R]的所有整数中…

智能制造企业CRM系统推荐清单(2024版)

当前&#xff0c;CRM市场呈现出“国际龙头优势逐渐下降&#xff0c;国产CRM奋起直追”的格局。智能制造企业在选型CRM时&#xff0c;如何选择合适的系统是一个需要重视的课题。 在我们之前的文章《一文读懂CRM&#xff0c;2023年30家CRM系统对比&#xff08;近年最全&#xf…

如何在AWS上使用免费的服务器

要在AWS上免费使用的服务器&#xff0c;你可以按照以下步骤操作&#xff1a; &#xff08;1&#xff09;注册AWS账户&#xff1a; 访问AWS官方网站&#xff08;https://aws.amazon.com/cn/&#xff09;&#xff0c;点击右上角的“完成注册”&#xff0c;按照页面提示填写相关…

经典小游戏(一)C实现——三子棋

switch(input){case 1:printf("三子棋\n");//这里先测试是否会执行成功break;case 0:printf("退出游戏\n");break;default :printf("选择错误&#xff0c;请重新选择!\n");break;}}while(input);//直到输入的结果为假&#xff0c;循环才会结束} …

基于FreeRTOS+STM32CubeMX+LCD1602+MCP23S09(SPI接口)的I/O扩展器Proteus仿真

一、仿真原理图: 二、运行效果: 三、STM32CubeMX配置: 1)、GPIO配置: 2)、SPI配置: 四、部分软件: 1)、主函数: /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : mai…

游戏AI的创造思路-技术基础-关于艾宾浩斯遗忘曲线的迷思

对于艾宾浩斯遗忘曲线和函数&#xff0c;我一直都有小小的迷思&#xff0c;总想实验下用艾宾浩斯函数来替换sigmoid函数作为激活函数&#xff0c;打造更接近人类的AI算法&#xff0c;这篇文章旨在讨论下 目录 3.10. 艾宾浩斯曲线 3.10.1. 定义 3.10.1.1. 曲线计算公式 3.10…

想用AI高端算力训练模型?试试英智BayStone平台

随着生成式人工智能的迅猛增长&#xff0c;各大公司纷纷推出强大的 AI产品以提升自身核心竞争力&#xff0c;对于依赖基础模型进行推理训练&#xff0c;同时需要高级基础设施的人工智能初创企业&#xff0c;急需使用高端智算算力来加速模型训练与产品研发创新。 算力是否充足&…