●个人主页:你帅你先说.
●欢迎点赞关注收藏
●既选择了远方,便只顾风雨兼程。
●欢迎大家有问题随时私信我!
●版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
前面我们已经学过了堆的各种实现,现在我们要基于堆的各种操作来实现一个堆排序。
假设我们排的是升序,基于前面TopK的思想,我们可以先建一个小堆,然后取堆顶,然后删除堆顶,调堆,一直重复这样下去,Pop
n次就可以得到最终结果。
代码实现:
void HeapSort(int* a, int n)
{
HP hp;
HeapInit(&hp);
// 建一个N个数的小堆
for (int i = 0; i < n; ++i)
{
HeapPush(&hp, a[i]);
}
// Pop N 次
for (int i = 0; i < n; ++i)
{
a[i] = HeapTop(&hp);
HeapPop(&hp);
}
HeapDestroy(&hp);
}
各种接口的细节->数据结构—二叉树、堆
这样实现确实可以,没毛病,但如果你要排一个非常大的数组,就要消耗很多的空间,能不能想一个方法把空间复杂度优化到O(1)。
我们可以直接在原数组上进行操作,这样就不用消耗额外的空间。
首先,我们先把数组调整成一个小堆
//法一:向上调整
for(i = 1;i < n;i++)
{
AdjustUp(a,i);
}
//法二:向下调整
for (i = (n - 1 - 1)/2; i >= 0; i--)
{
AdjustDown(a,n,i);
}
:向下调整的使用前提是:根结点的左右子树都为小堆
,所以向下调整需要做一些改变,我们从倒数第一个非叶子结点的子树
开始调小堆。
调成小堆后,堆顶元素就是最小的,接下来我们就是选出次小的数,选出次小的数我们需要把剩下的结点重新建成一个小堆,这个时候问题就来了,建一个堆的时间复杂度为O(N)要建N次,时间复杂度为O( N 2 N^2 N2),那我们这个排序的效率就太低了,体现不出它的优势。
那我们换种思路,如果我们建大堆呢?我们可以利用删除堆顶元素的思想,每次调成大堆后就与最后一个元素进行交换,这样最大的数就放在了最后一个,然后再次调堆,把次大的数放在倒数第二个…一直这样下去。
上述过程用动图演示:
void HeapSort(int* a, int n)
{
// O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
// 依次选数,调堆
// O(N*logN)
for (int end = n - 1; end > 0; end--)
{
Swap(&a[end], &a[0]);
// 再调堆,选出次小的数
AdjustDown(a, end, 0);
}
}
堆排序的时间复杂度为O(n l o g 2 n log_2n log2n)
觉得写的不错可以给个一键三连
点赞关注收藏