堆的实现及堆排序(不看你会后悔的。。。哼

前言

堆是一个完全二叉树,什么是完全二叉树呢,就是它每个结点的度最多只能有两个,且从左到右,依次满度。什么意思呢
在这里插入图片描述
这是一个完全二叉树
在这里插入图片描述
下面这个不是,因为它左子树没有满,它右边就有子树了。这种是二叉树,不是完全二叉树。完全二叉树采用顺序表存储的方式(也就是数组),因为完全二叉树的这种左边依次排满才有右边的结构特点,方便用数组进行连续的存储。堆是逻辑上的结构,物理上的实际存储的结构是数组。

堆的实现

1.造一个顺序表(用顺序表来实现堆)

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

2.初始化堆

为堆的顺序表结构初始化,其中包括为数组开辟n个空间,将数组的容量置为n.

// 堆的构建
void HeapCreate(Heap* hp, HPDataType** a, int n)
{
	assert(hp);
	*a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	hp->_capacity = n;
}

3.建堆

往开辟了n个空间的数组中放入数组并进行向上调整。

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	assert(hp->_a != NULL);
	hp->_a[hp->_size] = x;
	hp->_size++;
	AdjustUp(hp->_a, hp->_size - 1);
}

4.向上调整函数AdjustUp介绍

堆分为大根堆和小根堆。根结点小于子结点的叫做小根堆,根结点大于子结点的叫做大根堆。上面我们往数组中push了n个数据,但是它的排序是乱的,不是我们要的堆。例如
在这里插入图片描述

这个完全二叉树既不是大堆也不是小堆,它的数值是乱的,所以我们需要进行向上调整,这里以小根堆为例子。
在这里插入图片描述
从数组的最后一个元素开始,找到该结点的父结点,比较大小,这里调整成小根堆,所以如果父结点大于子结点就交换。然后,继续向上调整。
在这里插入图片描述
直到调整到child>=0(即根结点)。数组中的每一个数在push进数组的时候,都采用这种方式调整,等全部数据插入数组之后,就可以得到一个小根堆。这里有一个重要的公式:
左孩子的下标:child = parent2+1;
右孩子的下标:child = parent
2+2;
父结点的下标:parent = (child-1)/2;

这里的child可以是左孩子也可以是右孩子,因为左右孩子就差1.除2取整都是一样的。
代码实现:

//向上调整函数
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//小根堆
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

5.打印堆(数组)

经过上面的操作,我们已经建好了一个小堆。这时候可以打印出来看一下。

//打印
void HeapPrintf(Heap* hp)
{
	assert(hp);
	for (int i = 0; i < hp->_size; i++)
	{
		printf("%d ", hp->_a[i]);
	}
}

在这里插入图片描述
在这里插入图片描述
此时已经得到了一个小堆。大堆同样的原理,将大小于符号交换一下就可以了。

6.堆的删除

堆的删除是将根数据与尾数据交换,然后删除根数据,再对堆进行向下调整。向下调整的条件是左右子树都必须是小堆/大堆。在上面的步骤中,我们已经建立了一个小堆。
过程如下:

1.交换

在这里插入图片描述

2.删除

在这里插入图片描述

3.向下调整:从根结点开始,选择较小的一个孩子结点交换,

在这里插入图片描述
在这里插入图片描述
直到child = n-1;

堆的删除代码

// 堆的删除
void HeapPop(Heap* hp)
{
	//将根与最后一个叶子结点交换,再删除叶子结点,再从上到下去调整//依旧以小根堆为例
	assert(hp);
	swap(&hp->_a[0], &hp->_a[hp->_size-1]);
	hp->_size--;
	AdjustDown(hp->_a, 0,hp->_size);
}

7.向下调整函数

从父结点(在删除的这个函数中是根结点,也就是下标为0)开始调节。然后更新child和parent,直到child等于n-1;

代码如下:

//向下调整函数
void AdjustDown(HPDataType* a,int parent,int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//默认左孩子比右孩子小,如果不是就交换
		
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child = child + 1;
		}

		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);//交换
			parent = child;
			child = parent * 2 + 1;//向下更新
		}
		else
		{
			break;
		}

	}

}

经过上述的操作,在删除一个数之后,重新向下调整,建立新的小堆。这种删除方式可能在大家看来吃力不讨好,不如直接删除尾部元素,它也还是堆的结构(我也是这样想的)。但其实,这种操作的另一个用法,也是一个主要的用法并不是这个,而是排序
如何用上述这种方式排序呢?
虽然我们建了大小堆,但是导出来的数据并不是有序的。我们能确保的只能是根元素是最大或者最小的。所有我们这样操作:
1.获取顶部元素,也就是下标为0的数组元素。
2.将顶部元素与尾元素交换并删除交换后的尾元素,然后调用向下调整函数,进行向下调整。调整后的数组又是一个顶部元素最小/最大的堆。
3.重复上述操作,我们就可以实现排序(虽然撇脚。。。但也能用,下一章将好用的堆排序方式)

8.获取顶部元素

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

9.获取堆中的数据个数

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

10.堆的判空

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

总结

首先是堆的建立:
1.创一个顺序表并初始化
2.往顺序表(数组)中push数据并向上调整建堆
其次是堆的排序:
1.获取顶部元素
2.将顶部元素与最后一个叶子结点(也就是尾元素交换)
3.进行向下调整
4.重复上述操作,就可以实现堆排序。
注意:向下调整函数的前提是左右子树必须是大/小堆。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值