排序算法之堆排序

排序算法之堆排序

简介

之前讲过简单选择排序,它是通过 n n n次遍历,每次选出最小值,从而完成排序,因为没有保存每次遍历的结果,所以在每次遍历时都要遍历剩余的所有元素,因此操作次数较多,那我们可不可以在每次选择最小元素的同时,根据比较结果对其他元素进行调整,使其最小值始终处于某个位置,这样每次选择最小值时就不用进行多余的操作,从而提高了效率。下面我们就介绍这样一种排序算法——堆排序

原理

堆排序是采用堆结构对元素集合进行排序。我们之前讲过堆得概念,堆是指具有以下性质的完全二叉树:每个节点的值都大于(小于)或等于其左右孩子节点的值,我们称之为大顶堆(小顶堆)。如下图,左边是大顶堆,右边是小顶堆。
在这里插入图片描述
由堆得定义可知,其根节点的值一定是所有节点中最大或最小的值。根据这个性质,我们可以将根节点值取出并删除,将取出的根节点值按序放入一个数组中,然后将剩余的节点重新按堆序性排序,重复此操作,直至所有节点都被排序完成。但这样存在一个问题,那就是需要一个额外的数组空间,这将使空间复杂度增加。为了解决这一问题我们可以使用堆中的空间,我们每次 d e l e t e delete delete时,堆的空间减少一位,而这一位刚好可以用来存放删除的那个元素。这样就可以很好地解决这一问题。如下图所示,删除最大值97之后,将97放入删除前尾元素的位置,然后再将剩余元素重新构建成堆。
在这里插入图片描述
之后的操作完全相同。整个过程只涉及到两步,首先对所有元素构建堆,然后按上述过程排序。下面进行代码实现,因为构建的过程只涉及到下滤。其原理见二叉堆。这里就不在详细讲解。

代码实现

void percDown(SqList* L, int index, int size)
{
	if (index >= size)
		return;
	L->r[0] = L->r[index];
	for (int i = index * 2; i <= size; i *= 2)
	{
		//选取左右孩子最大值
		if (i + 1 <= size)
			if (L->r[i + 1] > L->r[i])
				i++;
		if (L->r[0] >= L->r[i])
			break;
		L->r[index] = L->r[i];
		index = i; //继续下滤
	}
	L->r[index] = L->r[0]; //插入
}

void heapSort(SqList* L)
{
	if (!L)
		return;
	for (int i = L->length / 2; i > 0; i--) //将L中的r数组构建成最大堆
		percDown(L, i, L->length);

	for (int j = L->length; j > 1; j--) //排序
	{
		swap(L, j, 1);
		percDown(L, 1, j - 1);
	}
}

完整代码见https://github.com/kfcyh/sort/tree/master/heapsort

性能分析

堆排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),并且经验指出,堆排序算法的性能及其稳定,无论最好、最坏还是平均时间复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn),这些性能要远远好过冒泡、简单选择和直接插入的 O ( n 2 ) O(n^2) O(n2)。但初始时构建堆所需的比较次数较多,不适合排序个数较少的情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值