数据结构:堆的创建和使用

上一期我们学习了树和二叉树的定义,其中我们了解到了两种特殊的二叉树:满二叉树和完全二叉树。

今天我们还要学习一种新的结构:堆

那这种结构和二叉树有什么联系呢???

通过观察我们可以发现,完全二叉树和满二叉树可以看作是一个连续的结构,所以我们可以使用顺序存储,但是这种结构还不算是堆结构,那么堆又和这种结构有什么联系呢???

 下面是两种堆的结构:一种是大堆,一种是小堆

那么什么是大堆和小堆呢?

顾名思义,小堆就是根节点都比孩子结点小的

大堆就是根节点都比孩子结点大的

下面就是两种堆的结构:

  那么我们学习堆有什么意义呢?下面是堆的几种运用场景:

后面我们会有所了解。

 

这里我们先来了解一下堆排序

通过对比我们可以发现,堆排序相比冒泡排序的时间复杂度是远远要好的

下面我们就正式进入堆的学习中。

 我们先来看堆的定义:

因为堆是顺序结构,所以我们定义的方式和顺序表是一致的。

 在实现堆中我们需要使用两种算法,一种是向上调整算法,一种是向下调整算法。

 (假设在小堆中)在向上调整算法中,我们插入50,我们需要将它的值与根节点的值进行比较,

如果比根节点小我们就进行交换。直到我们的孩子结点走到最顶端,也就是下标为0的位置。

 那么我们要怎么寻找每次的孩子结点和父结点的下标呢?

通过观察我们可以发现孩子结点和自己的父结点有以下规律:

 所以每一次结束比较后我们都要将父结点赋值给孩子节点,并让父结点走向当前孩子结点的父结点。所以下面是代码的实现

 所以在堆中我们每次插入数据都需要调整值的顺序。但是,我们思考后会发现,我们的向上和向下调整算法都只能调整父结点和孩子结点的关系,而不能改变兄弟结点的关系

所以我们的堆插入数据的函数如下

 下面我们来写“删除堆顶元素”函数

我们将堆顶元素认为是下标为0所在位置的元素,有一些人就想当然将后面的数据覆盖上去,但是,这一种思路是不对的,我们仔细思考一下,我们这样子做,我们的结点间的父子关系就全都不对了,也就是我们的堆就不是堆了,顺序已经被打乱了

那么我们应该怎么做呢???

这里我们的另一种向调下整算法就起到了重要的作用。

删除时,首先我们将首元素和末尾元素的值交换,之后我们只要将size--就可以删除尾部元素,也就是原来的堆顶的元素,之后我们再将堆顶的元素向下调整。

那么向下调整的算法是怎样书写的呢???

向下调整中,我们定义父结点,之后通过上面的父结点和子节点的关系,我们可以找到左子节点,

左子节点和右子节点下标相差1,我们通过比较左子节点和右子节点的值,我们选出最小的,若此时的父结点的值大于最小的子节点,我们就进行交换,之后一直走循环,直到父结点走到动态数组的最后面

下面是向下调整算法的书写 

 下面是删除堆顶元素的函数:

 其中HPEmpty函数是判断堆是否为空的函数

 到这里我们就可以解决topk问题,比如我们要取出一千万数的前十名,我们就可以返回堆顶元素,之后pop9次就可以了。

这里我们要了解topk问题的具有现实的意义,比如我们在点外卖时,我们要选取销量前十的我们就可以使用topk解决问题

下面是全部代码

void HPInit(HP* php)//初始化堆堆空间
{
	php->size = 0;
	php->a = NULL;
	php->capcity = 0;
}
void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}//交换函数
void AdjustUp(int* a, int Child)
{
	int child = Child;
	int parent = (child - 1) / 2;
	while(child > 0)//直到孩子结点走到下标为0的位置
	{
		if (a[child] < a[parent])
		{
			swap((a) + child, (a) + parent);//发现子节点的值比父节点小就进行交换
			child = parent;//将父结点赋值给孩子结点
			parent = (child - 1) / 2;//走向下一个父结点
		}
		else
		{
			break;//若孩子结点大就退出循环,即不需要交换
		}
	}
	return;
}
void HPPush(HP* php, HPDatatype x)//插入数据
{
	
	if(php->size == php->capcity)
   {
	 int newcapcity = php->capcity == 0 ? 4 :php->capcity * 2;//看空间是否足够
	 HPDatatype* a = (HPDatatype*)realloc(php->a, sizeof(HPDatatype) * newcapcity);
	 if (a == NULL)
	 {
		 perror("malloc_fail");
		 return;
	 }
	 php->a = a;
	 php->capcity = newcapcity;
   }
	php->a[php->size] = x;
	php->size++;
	//开辟新的空间并存放数据
	AdjustUp(php->a,php->size - 1);//通过向上调整顺序形成小堆
}
bool HPEmpty(HP* php)
{
	return php->size == 0;
}
void AdjustDown(int* a,int n ,int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while(child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])//选出较小的子节点
		{
			child += 1;
		}
		if (a[parent] > a[child])//父节点都比两个子节点小就返回
		{
			swap(a + parent, a + child);//与较小的子节点交换位置
			//往下再寻找
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//若父结点的值小于最小子节点的值我们就退出循环,即这个堆还是小堆
		}
		
	}
}
void HPPop(HP* php)//删除堆顶元素
{
	assert(php);
	assert(!HPEmpty(php));
	swap(php->a, php->a + php->size - 1);//交换顶部元素和底部元素
	php->size--;
	AdjustDown(php->a,php->size,0);//向下调整
}
int HPTop(HP* php)//返回堆顶的元素
{
	return php->a[0];
}
void HPDestroy(HP* php)//销毁堆空间
{
	assert(php);
	free(php->a);
	php->a = NULL;
}

以上就是本次的全部内容,谢谢大家观看!!!

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Sugar_goat

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

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

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

打赏作者

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

抵扣说明:

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

余额充值