排序3: 堆排序

上期回顾:我们讲了希尔排序的实现方式。

这一期,我们来剖析一下堆排序的底层思路以及代码实现。


目录

堆排序的基本思想

向下调整

选数

总结


堆排序的基本思想

关于堆排序,我们首先考虑的当然是建堆了。

堆,是二叉树的一种。

关于建堆,在我们学习堆的时候,一定学过两个建堆的方式:

        1、向上建堆法。

就是子节点与父节点比较然后逐渐向上的一种建堆方式,最终,我们选择的最小的数或者最大的数会出现在堆顶。

例如:当我们要将下面的数据调整为堆结构的时候,会有以下调整。 

 

         2、向下建堆法

父节点与两个最大或最小的子节点比较然后逐渐向下传递的一种建堆方式最终,我们选择的最小的数或者最大的数会出现在堆底。

那么,哪种方式建堆的效率是最高的呢?

最终我们得到: 向下建堆的效率为O(N)。

利用同样的方法,我们可以算得向上建堆的效率为O(N*logN)。最终我们选择用向下建堆的方法。

接下来我们就要选数了。我们以从小到大排序为例,我们建堆时建大堆,然后每次把尾部的数和堆顶的最大的数字交换,那么我们就把最大的数字放到了它改在的地方。然后把堆顶的数字向下调整,最终堆顶的数字就是第二大的数字了,然后我们把他与倒数第二个数字交换……直到最后第二个数字排好,我们的堆排序就完成了。

向下调整

我们知道,在堆中,下标为 n 的父节点,他的两个孩子节点的下标分别为:2n+1 和 2n+2 。

所以向下调整的步骤大致分为以下几步:

        1、左孩子和右孩子比较,如果右孩子比左孩子大,记录右孩子的下标。

        2、父节点与记录子节点比较,如果子节点更大,则父与子交换值。

        3、加上循环。

  

代码实现:

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(int* a, int n, int parent)
{
	int minChild = parent * 2 + 1;
	while (minChild < n)
	{
		// 找出大的那个孩子
		if (minChild + 1 < n && a[minChild + 1] > a[minChild])
		{
			minChild++;
		}

		//把比双亲节点大的孩子换上去
		if (a[minChild] > a[parent])
		{
			Swap(&a[minChild], &a[parent]);
			parent = minChild;
			minChild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

注: n是整个堆元素个数,循环要让孩子节点的下标小于n以免越界访问。如果有右孩子,需要判断右孩子是否越界。

选数

我们首先让堆顶和最后一个元素交换,然后交换后的堆顶元素向下调整。然后第二次堆顶与倒数第二个元素交换……

代码: 

void HeapSort(int* a, int n)
{
	//建大堆
	for (int i = (n - 1 -1 ) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	//选数
	int i = 1;
	while (i < n)
	{
		Swap(&a[0], &a[n - i]);
		AdjustDown(a, n - i, 0);
		++i;
	}
}

注:通过 n-i ,我们可以逐渐缩小范围,最终排完第二个元素的时候停下。

总结

时间复杂度:O(N*logN) 。

在前文我们计算得到向下建堆的效率是O(N),选数的时候循环是O(logN)的。

空间复杂度:O(1)。

稳定性:不稳定。

堆是二叉树的一种,利用二叉树的调整法建成的堆只是具有相对有序的,向下调整的时候还是可能破坏原先的排序,所以是不稳定的。

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青衫哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值