堆排序【C语言】

文章介绍了堆排序的原理,强调了数组作为堆的存储结构和向下调整的优势。通过比较向上调整和向下调整的时间复杂度,说明了向下调整的效率更高。堆排序可以通过构建大堆或小堆实现升序或降序排列,并且总的时间复杂度为O(NlogN)。
摘要由CSDN通过智能技术生成

前言

笔者这里讲的是数组的堆排序,具体代码笔者会放在文章末尾,需要的自取,文章就不对操作的代码进行具体讲解了,主要内容目录可知。
要实现数组的堆排序,首先需要读者实现或者了解堆的结构及其操作,堆的存储结构采用顺序结构,实现堆排序只需要实现堆的向下调整和堆的删除操作即可(实际上只需要堆的向下调整操作也可),当然,其他操作也很重要,因为掌握其他操作方便我们理解为什么这里不选择其他操作来实现。

堆其实就是树

堆其实就是一棵完全二叉树,对于一个堆,如果我们按采用顺序存储结构的完全二叉树来解读的话,结果如图:
在这里插入图片描述
所以堆可以看作是一棵完全二叉树。

代排序数组即为堆

待排序数组的存储结构也是采用顺序结构,故只需要改变一下解释的规则就可以将代排序的数组看作是堆,只不过这个堆既不是大堆,也不是小堆,我们就暂且称之为“不规则堆”吧,那么为什么要将代排序数组看作是堆呢?因为这样一来就不用拷贝代排序数组来创建一个堆了,既节省了空间,有节省了一定的时间,这里的节省空间好理解,但是为什么能节省时间呢?二者不是都要进行堆的调整吗?注意啦!拷贝不是需要时间吗?

“不规则堆”的调整—向上调整与向下调整的时间复杂度的比较

对于“不规则堆”调整方式也是有讲究的,向下调整是优于向上调整的,下面我们就来算算二者的时间复杂度:

在这里插入图片描述

补充:可能有些小伙伴对最后h到N的转化没有反应过来,这里稍微点拨一下:h = log₂(N+1) --> 2^h = N+1

由以上两图可见:明显向下调整建堆的复杂度是优于向上调整建堆的。

“规则堆”的删除即排序

堆的删除不是删除堆底元素,而是将堆顶元素与堆底交换顺序然后再进行一次向下调整,在进行一次删除操作后,堆顶元素就移步到堆底了,而次大或是次小于堆顶元素的元素就成了新堆顶了(这里的次大与次小是根据堆是大堆还是小堆来判断的),如果我们继续进行删除操作直至堆为空,那么将会得到一个有序的数组,因为每次对堆进行删除,堆内最值都会移步到堆底,然后堆的范围减少一,堆顶元素就会在待排序数组的后部,且区别于堆的空间,如图:在这里插入图片描述
由图可知后续就会得到一个升序的数组了,那如果我想要一个降序的数组该怎么办呢?

如何调整堆排序的升序与降序

对于大堆,删除一次,堆内的最大值就会排在最后,如果删除到堆为空,那么就会得到一个升序的数组,相反如果构造的是小堆,那么删除到堆为空就能得到一个降序的数组了,故决定堆排序的结果为降序还是升序取决于构建的堆的类型,使用大堆进行堆排序得到的是升序的数据,使用小堆得到的则为降序的数据。

堆排序的时间复杂度

有老铁可能会疑惑道:“前面不是已经算过时间复杂度了吗?”注意啦!前面算的是建堆的时间复杂度,并没有算堆排序的时间复杂度的,除了要建堆,还要删除堆,前者的复杂度可以优化到O(N-logN),后者的复杂度计算过程如图:在这里插入图片描述
可见堆删除的时间复杂度和向上调整的时间复杂度是一模一样的,都为NlogN,那么堆排序的时间复杂度为N-logN+NlogN,由于NlogN >> N >> logN,堆排序的时间复杂度可以认为是O(NlogN)级别的,正是由于大O复杂度会估算的规则,造成了堆排与快排是一样快的排序算法的假象。

文章涉及代码

#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>

typedef int HPDataType;

typedef struct Heap {
	int size;
	int capacity;
	HPDataType* arr;
}Heap;

// 堆排序(根据目前设置的堆的类型有所不同,大堆是排序结果为升序,小堆的结果为降序)
void HeapSort(HPDataType* arr, int n) { //参数arr即为待排序数组,n代表数组长度
	Heap* ph = (Heap*)malloc(sizeof(Heap));

	ph->arr = arr;
	ph->capacity = ph->size = n;

	for (int i = (n - 1) / 2; i >= 0; i--) {
		AdjustDown(ph, n, i);
	}

	while (!HeapEmpty(ph)) {
		HeapPop(ph);
	}
}

// 检查堆是否为空
bool HeapEmpty(Heap* ph) {
	assert(ph);

	return ph->size == 0;
}

// 堆的删除(可尝试用向下调整代替)
// 交换堆元素
static Swap(HPDataType* p1, HPDataType* p2) {
	assert(p1);
	assert(p2);

	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void HeapPop(Heap* ph) {
	assert(ph);
	assert(!HeapEmpty(ph));

	Swap(&ph->arr[0], &ph->arr[ph->size - 1]);
	ph->size--;

	AdjustDown(ph, ph->size, 0);
}


// 向下调整
void AdjustDown(Heap* ph, int n, int parent) {
	assert(ph);

	int child = parent * 2 + 1;
	if (child + 1 < n && ph->arr[child] < ph->arr[child + 1]) {
		child += 1;
	}
	while (child < n) {
		if (ph->arr[child] > ph->arr[parent]) {
			Swap(&ph->arr[child], &ph->arr[parent]);
		}

		parent = child;
		child = parent*2 + 1;
		if (child + 1 < n && ph->arr[child] < ph->arr[child + 1]) {
			child += 1;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值