堆的介绍、堆的向上、 向下调整法与基本功能实现

news/2024/5/20 9:54:36 标签: 数据结构,

💓博主csdn个人主页:小小unicorn
⏩专栏分类:数据结构
🚚代码仓库:小小unicorn的代码仓库🚚
🌹🌹🌹关注我带你学习编程知识

  • 二叉树的顺序结构
  • 的概念:
  • 的性质:
  • 的结构
  • 的向下调整法:
  • 时间复杂度:
  • 的向上调整法:
  • 的实现:
    • 初始化
    • 销毁
    • 打印
    • 的插入:
    • 的删除:
    • 获取顶的数据:
    • 获取的数据个数:
    • 的判空:

二叉树的顺序结构

在树与二叉树中,我们知道:

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。

现实中我们通常把(一种二叉树)使用顺序结构的数组来存储需要注意的是这里的和操作系统虚拟进程地址空间中的是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

的概念:

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

:将根结点最小的叫做小,也叫最小或小根

:将根结点最大的叫做大,也叫最大或大根

的性质:

1.中某个结点的值总是不大于或不小于其父结点的值。

2.总是一棵完全二叉树。

的结构

在这里插入图片描述

的向下调整法:

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小
在这里插入图片描述
但是:向下调整算法有一个前提:左右子树必须是一个,才能调整。

1.若想将其调整为小,那么根结点的左右子树必须都为小
2.若想将其调整为大,那么根结点的左右子树必须都为大

在这里插入图片描述

向下调整算法的基本思想(以建小为例):
 1.从根结点处开始,选出左右孩子中值较小的孩子。
 2.让小的孩子与其父亲进行比较。

 若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。

 若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小了。

代码如下:

//交换函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*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++;//较小的孩子改为右孩子
		}
		if (a[child] < a[parent])//左右孩子中较小孩子的值比父结点还小
		{
			//将父结点与较小的子结点交换
			Swap(&a[child], &a[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);
	}

时间复杂度:

因为是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
在这里插入图片描述
利用错位相减法进行计算:
在这里插入图片描述
因此:的时间复杂度为O(N)

总结:
的向下调整算法的时间复杂度:T(n)=O(logN)。
的时间复杂度:T(n)=O(N)。

的向上调整法:

当我们在一个的末尾插入一个数据后,需要对进行调整,使其仍然是一个,这时需要用到的向上调整算法。
在这里插入图片描述
向上调整算法的基本思想(以建小为例):
 1.将目标结点与其父结点比较。
 2.若目标结点的值比其父结点的值小,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是小了。

在这里插入图片描述
代码如下:

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

//的向上调整(小
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)//调整到根结点的位置截止
	{
		if (a[child] < a[parent])//孩子结点的值小于父结点的值
		{
			//将父结点与孩子结点交换
			Swap(&a[child], &a[parent]);
			//继续向上进行调整
			child = parent;
			parent = (child - 1) / 2;
		}
		else//已成
		{
			break;
		}
	}
}

的实现:

初始化

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

typedef int HPDataType;//中存储数据的类型

typedef struct Heap
{
	HPDataType* a;//用于存储数据的数组
	int size;//记录中已有元素个数
	int capacity;//记录的容量
}HP;

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

//初始化
void HeapInit(HP* php, HPDataType* a, int n)
{
	assert(php);

	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType)*n);//申请一个结构
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	php->a = tmp;
	memcpy(php->a, a, sizeof(HPDataType)*n);//拷贝数据到
	php->size = n;
	php->capacity = n;
	int i = 0;
	//建
	for (i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}

销毁

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

//销毁
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);//释放动态开辟的数组
	php->a = NULL;//及时置空
	php->size = 0;//元素个数置0
	php->capacity = 0;//容量置0
}

打印

打印中的数据,这里用了两种打印格式。

第一种打印格式是按照的物理结构进行打印,即打印为一排连续的数字。

第二种打印格式是按照的逻辑结构进行打印,即打印成树形结构。

//求结点数为n的二叉树的深度
int depth(int n)
{
	assert(n >= 0);

	if (n>0)
	{
		int m = 2;
		int hight = 1;
		while (m < n + 1)
		{
			m *= 2;
			hight++;
		}
		return hight;
	}
	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 h = depth(php->size);
	int N = (int)pow(2, h) - 1;//与该二叉树深度相同的满二叉树的结点总数
	int space = N - 1;//记录每一行前面的空格数
	int row = 1;//当前打印的行数
	int pos = 0;//待打印数据的下标
	while (1)
	{
		//打印前面的空格
		int i = 0;
		for (i = 0; i < space; i++)
		{
			printf(" ");
		}
		//打印数据和间距
		int count = (int)pow(2, row - 1);//每一行的数字个数
		while (count--)//打印一行
		{
			printf("%02d", php->a[pos++]);//打印数据
			if (pos >= php->size)//数据打印完毕
			{
				printf("\n");
				return;
			}
			int distance = (space + 1) * 2;//两个数之间的空格数
			while (distance--)//打印两个数之间的空格
			{
				printf(" ");
			}
		}
		printf("\n");
		row++;
		space = space / 2 - 1;
	}
}

的插入:

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

//的插入
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("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

的删除:

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

原因:我们若是直接删除顶的数据,那么原后面数据的父子关系就全部打乱了,需要全体重新建,时间复杂度为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);//向下调整
}

获取顶的数据:

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

//获取顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	return php->a[0];//返回顶数据
}

获取的数据个数:

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

//获取中数据个数
int HeapSize(HP* php)
{
	assert(php);

	return php->size;//返回中数据个数
}

的判空:

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

//的判空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;//判断中数据是否为0
}


http://www.niftyadmin.cn/n/5060179.html

相关文章

Airtest1.2.7新增断言API介绍

1. 前言 1.2.7版本的Airtest中&#xff0c;一个很重要的功能是 新增了非常丰富的断言API &#xff0c;今天我们就来详细看一下Airtest都给我们提供了哪些断言语句。 2. 旧版Airtest提供的断言语句 先回顾下&#xff0c;旧版Airtest一直以来&#xff0c;都只给我们提供了2种断言…

在Qt中,怎么获取到在mainwindow.ui文件中添加的控件

2023年9月30日&#xff0c;周六晚上 假设我在mainwindow.ui中添加了一个名为textEdit的QTextEdit对象 在mainwindow.cpp中&#xff0c;可以通过ui对象来获取到这个控件

YOLOv8改进算法之添加CA注意力机制

1. CA注意力机制 CA&#xff08;Coordinate Attention&#xff09;注意力机制是一种用于加强深度学习模型对输入数据的空间结构理解的注意力机制。CA 注意力机制的核心思想是引入坐标信息&#xff0c;以便模型可以更好地理解不同位置之间的关系。如下图&#xff1a; 1. 输入特…

ChatGPT终于可以进行网络搜索 内容不再限于2021年9月前

微软和谷歌已经让旗下聊天机器人进行网上搜索&#xff0c;并提供原始材料的链接&#xff0c;以提高信息共享的可信度和范围。但是&#xff0c;ChatGPT迄今为止只接受了有时间限制的训练数据&#xff0c;这些数据仅限于从互联网上收集的2021年9月之前的信息。在周三的一系列推文…

敏捷开发的实施要素和实现敏捷的实际改进

敏捷开发的实施要素如下&#xff1a; 个体和交互&#xff1a;胜过过程和工具。可以工作的软件&#xff1a;胜过面面俱到的文档。客户合作&#xff1a;胜过合同谈判。响应变化&#xff1a;胜过遵循计划。 敏捷开发过程是一个增量的、迭代的过程&#xff0c;责任人、开发人员和…

idea debug 重启弹窗提示窗口询问是否关闭运行着的服务器

目录 方法121版本的IDEA idea重新启动服务器时会有一个提示窗口询问是否关闭运行着的服务器&#xff0c;&#xff0c;这个窗口不小心点了不再提示.重新打开弹窗方法 方法1 idea编辑器由于勾选了不再提示选项导致的弹窗无法继续弹出&#xff1a;解决方案 1.打开项目没提示&…

【RocketMQ】基本使用:Java操作RocketMQ(rocketmq-client)

【RocketMQ】基本使用&#xff1a;Java操作RocketMQ&#xff08;rocketmq-client&#xff09; 1.引入依赖 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId><version>4.3.2</version>…

信息系统项目管理师 12题

1.管理团队所获得的主要收益体现在&#xff08;&#xff09;。 ①指导团队选择和职责分配 ②管理冲突 ③解决问题 ④改进团队协作 ⑤影响团队行为 ⑥评估团队成员绩效 A.①③⑤⑥ B.②③④⑥ C.①③④⑥ D.②③⑤⑥ 【答案】D。管理团队。本过程的主要收益&#xff1a;影响…