堆排序---从堆的概念开始介绍

本文详细介绍了堆(大堆和小堆)在完全二叉树中的表示及其数学关系,重点讲解了向下调整算法用于维护堆的性质,以及堆排序的原理,包括建堆过程和优化的时间复杂度。
摘要由CSDN通过智能技术生成

堆都是完全二叉树。

堆分为大(根)堆和小(根)堆。

大堆每个父亲的值都大于等于孩子的值,小堆每个父亲的值都小于等于孩子的值

因此堆可以用来选数,堆顶(根)就是最大或最小值。

叶子节点可以看作大堆或小堆


完全二叉树中的数学关系

如果完全二叉树中父亲的编号为i:

左孩子的编号为2i+1;

右孩子是2i+2。

如果左孩子的编号是i:

父亲是(i-1)/2;

右孩子同理。


堆的数组表示

堆常用数组表示,数组的下标为堆的编号-1,由此可以利用完全二叉树的数学关系,通过一个节点找到它的父亲、左右儿子,来遍历整个堆。


向下调整算法

以小堆为例。

当一个完全二叉树的两个子树是小堆,而根不满足小堆时,可以使用此算法。

将原堆的根与两个子堆的根中较小(大)的比较,若大于则交换,交换后再与其两个子堆的根中较小(大)的比较,大于交换,循环往复,直到其小于两个子堆的根,或者到达叶子节点。

调整后变为小(大)堆:

void AdjustDown(HeapType* a, int n, int root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

建堆

从倒数的第一个非叶子节点i(也就是最后一个结点的父节点)作为根开始向下调整,再自减i,循环往复。最后一个混乱的完全二叉树变成了堆。

建堆的时间复杂度为O(n)。

同时我们发现堆的根为所有元素中的最小(大)值。


堆排序

由建堆启发,我们设想是否可以建堆,去除根元素,循环往复,对一系列元素进行排序。其实这种排序方法是一种堆排序,但时间复杂度为O(n^{2}),有没有什么办法优化呢?

由于堆是由数组表示的,当完成对一个完全二叉树建堆后,把根元素与数组的最后一个元素交换,再把数组长度减小1,再建堆,循环往复。这种方法的时间效率大大提高,为O(n{log_{2}}^{n})

以如下的完全二叉树为例:

建小堆:

得到最小值1,与尾值9交换,然后减小数组长度这样,9为根,其两个子树仍然是小堆,符合向下调整算法,循环往复,实现数值的从大到小排序。

所以得到总结,升序建大堆,降序建小堆。


void AdjustDown(HeapType* a, int n, int root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(HeapType* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值