堆(C语言实现)

1.堆的概念

如果有一个关键码的集合,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,孩子节点都不大于父节点的称为小堆,孩子节点都不大于其父节点的称为大堆

图示:

由此可以看出:任何数组都可以看做完全二叉树,但不一定是堆

2.堆的性质

1.堆中某个节点的值总是不大于或不小于其父节点的值
2.堆总是一棵完全二叉树
3.孩子节点和父亲节点下标的关系:

  • parent = (child - 1) / 2
  • leftchild = parent * 2 + 1
  • rightchild = parent * 2 + 2

3.堆的结构

堆总是一棵完全二叉树,堆是用数组来存储的

typedef int HPDataType;//数据类型
typedef struct Heap
{
	HPDataType* a;//数组
	int size;//数据个数
	int capacity;//容量
}HP;

4.接口实现

4.1初始化堆

将数组指针指向NULL,将数据个数和容量置零方便后面统计和判断容量,这里断言php是防止人为不小心传入了空指针

//初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;// 指空
	php->size = php->capacity = 0;// 置零
}

4.2销毁堆

释放掉数组,并将数组指针指空,将数据个数和容量置零即可

//销毁堆
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);//释放
	php->a = NULL;//指空
	php->size = php->capacity = 0;//置零
}

4.3打印堆内元素

遍历数组将其内的元素打印出来即可

//打印堆内元素
void HeapPrint(HP* php)
{
	assert(php);	
	//遍历打印
	for(int i = 0; i < php->size; ++i)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

4.4向上调整

将孩子节点的数据和其父结点的数据进行比较,若孩子节点的数据大于(小于)父节点的数据就交换并继续向上调整,直到根结点(数组中下标为0的元素)位置就停止,这里对父子节点的比较是根据堆是大堆还是小堆来决定的

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

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//孩子大于(小于)父亲,就交换并继续向上调整,反之则调整结束
		//if (a[child] > a[parent])
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

4.5向堆中插入数据

首先创建一个新节点,并判断数组是否需要扩容,然后直接向数组尾部插入一个数据,将该数据向上调整(保证结构继续是个堆)即可

//向堆中插入数据
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//判断扩容
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)//判断防止扩容失败
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	//将新的数据插入数组尾部
	php->a[php->size] = x;
	php->size++;//数据个数+1
	AdjustUp(php->a, php->size - 1);//从尾节点开始向上调整
}

4.6向下调整

将孩子节点的数据和其父结点的数据进行比较,若孩子节点的数据大于(小于)父节点的数据就交换并继续向小调整,直到最后一个节点(数组中的最后一个的元素)就停止,这里对父子节点的比较是根据堆是大堆还是小堆来决定的

//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;//默认是左孩子
	while (child < n)
	{
		//确认child指向大的(小的)那个孩子
		//if (a[child + 1] > a[child] && child + 1 < n)
		if (a[child + 1] < a[child] && child + 1 < n)
		{
			++child;//换成右孩子
		}
		//孩子大于(小于)父亲,就交换并继续向下调整,反之则调整结束
		//if (a[child] > a[parent])
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

4.7删除堆顶元素

将堆顶元素和堆尾数据元素交换并删除堆尾(此时堆尾数据也就是堆顶数据),再从根节点开始向下调整(保证它继续是个堆)即可,这里需要断言堆为空的情况,为空不能删

//删除堆顶元素
void HeapPop(HP* php)
{
	assert(php);
	//断言堆为空
	assert(php->size > 0);
	//交换堆顶元素和堆尾元素
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;//数据个数-1
	AdjustDown(php->a, php->size, 0);//从根节点开始向下调整
}

4.8查看堆顶元素

数组首元素就是堆顶元素,直接将其返回即可,这里需要断言堆为空的情况,为空无堆顶元素

//查看堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	//断言堆为空
	assert(php->size > 0);
	//返回数组首元素(堆顶元素)
	return php->a[0];
}

4.9统计堆内数据个数

结构体中的size就是有效数据的个数,直接将其返回即可

//统计堆内数据个数
int HeapSize(HP* php)
{
	assert(php);
	//返回有效数据个数
	return php->size;
}

4.10判断堆是否为空

如果堆内的有效数据个数为0,则堆为空,直接返回其布尔值即可

//判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);
	//有效数据个数为0则堆为空
	return php->size == 0;
}

4.11堆的构建

我们可以人为传入一个数组来建堆,首先需要开辟一个与传入的数组大小相同的空间,用来将数组中的内容复制到该空间中,然后从最后一个元素的父节点开始((size-1-1)/2)依次向前可以遍历到每颗子树的父节点,并对每颗子树向下调整

//堆的构建
void HeapCreate(HP* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//将数组复制到开辟的空间中
	memcpy(php->a, a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;

	//建堆算法
	//从最后一个元素的父节点开始依次向前可以遍历到每颗子树的父节点
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(php->a, n, i);
	}
}

图解:


堆的实现到这里就介绍结束了,期待大佬们的三连!你们的支持是我最大的动力!
文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茉莉蜜茶v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值