动图演示
实现步骤
- 把数组用堆的形式排列(小堆或者大堆)
- 对堆进行排序
Step1. 建堆
方法一:把第一个数就看成是一个堆,之后的数据依次当作push数据,再向上调整堆到合适位置。
- 如图,第一个数是70,已经是一个堆
- 插入56,进行向上调整(此处认为建小堆)
- 插入30,再次调整堆,依次类推…
方法二:使用向下调整算法。
向下调整算法的前提左右子树都是堆。
Q: 这里有一个问题?该从哪里开始调整。
因为叶子节点没有左右子树,叶子所在的子树不需要调整,所以要从倒数第一个非叶子节点的子树开始,即:最后一个结点的父亲。
Step2. 排序建堆
结论:
排升序,选大堆;排降序,选小堆。
如果排升序,选择小堆
每一次选走了堆顶元素,剩下的数字就会失去堆的结构,需要重新建堆。选择小堆不是不可以,但是时间复杂度是N*N,排序效率太低了。
如果排大堆
建大堆,可以选出最大的数;最大的数(堆顶)和最后一个数交换,相当于pop了堆顶后再次进行向下调整。向下调整算法的时间复杂度n个数 : N*logN
堆排序代码(排升序)
// 升序
void HeapSort(int* a, int n)
{
// 排升序,选大堆
// 建堆:从倒数第一个非叶子节点开始向下调整
// 为什么是end>=0 ?
// 因为现在的堆顶元素是 a[0],需要再次进行向下调整,创建堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
// 升序,选大堆
// 交换堆顶和最后一个元素后重新向下调整
// 为什么是end>0 ?
// 因为,只留下一个元素,已经是一个堆了,不用再调整了
for (int end = n - 1; end > 0; --end)
{
// 交换
Swap(&a[end], &a[0]);
// 最后一个元素作为堆顶,再次向下调整
AdjustDown(a, end, 0);
}
}
向下调整
void AdjustDown(int* a, int n, int parent)
{
// 调整为大堆
int child = parent * 2 + 1;// 默认是左孩子
while (child < n)
{
// 选出左右孩子中较大的一个
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
// 如果大孩子大于父亲,继续向下调整
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
// 大孩子小于父亲
break;
}
}
}