文章目录
提示:以下排序均以升序排列为例
堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过建堆来进行选择数据。
要使用堆排序,首先要学习堆的向下调整算法,因为要使用堆排序,必须先建堆,而建堆的次数可以通过堆的向下调整算法得出。
堆的向下调整算法(使用前提):
1.若想将其调整为小堆,那么根结点的左右子树必须都为小堆。
2.若想将其调整为大堆,那么根结点的左右子树必须都为大堆。
堆的两个特性:
1. 结构性:用数组表示的完全二叉树
2. 有序性: 任一结点的值是其子树的最大值(最小值)。
向下调整算法的基本思想(以建小堆为例):
从根结点处开始,选出左右孩子中小的那个与父亲进行比较,如果比父亲小就交换(如果不比父亲小就结束),然后继续向下调整,直到调整到叶子结点为止。
//堆的向下调整算法
void AdjustDwon(int* a, int n, int root)
{
int parent = root;
int child = parent * 2 + 1; // 默认是左孩子
while (child < n)
{
// 1、选出左右孩子中大的那一个
if (child + 1 < n && a[child+1] > a[child])
{
child += 1;
}
//与父亲比大小
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
上面说到,使用堆的向下调整算法的前提是根结点的左右子树为大堆或是小堆,但我们并不能保证我们要排序的序列都满足这个条件,如果左右子树都不是小堆,此时不能直接使用向下调整算法了,怎么办?
实际上我们只需要从倒数第一个非叶子结点的子树开始,从后往前,按下标,依次作为根去向下调整即可。
// 建堆 O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDwon(a, n, i);
}
那么我们应该建大堆还是建小堆呢?
如果是建小堆,最小数在堆顶,最小数选出后,剩下的数中再去选数,但是剩下的树结构都乱了,只能重新建树,才能选出下一个数,建队的时间复杂度为O(N),不合理,应该建大堆。
void HeapSort(int* a, int n)
{
// 建堆 O(N)
//排升序,建大堆
//从第一个非叶子结点开始向下调整,一直到根
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDwon(a, n, i);
}
// 排升序,建大堆还是小堆?建大堆
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);//将堆顶的数据和堆的最后一个数据交换
AdjustDwon(a, end, 0);
--end;
}
}
建堆的时间复杂度为O(N);
堆排序的特性总结:
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(1)
- 稳定性:不稳定