在 堆的实现+画图分析当中,介绍了堆的向上调整和向下调整,如果有些遗忘可以再看一遍。
一、 排升序 - 建大根堆
这里我们利用堆删除的思想进行排序。这里只介绍排升序。
注:
- 排降序时如果使用向上调整,向上调整的交换条件把大于号改成小于号。排降序:建小根堆
- 如果使用向下调整,在选孩子节点的时候选左右孩子节点的较小值,然后再和双亲节点进行比较,如果孩子节点小于双亲节点则交换。
#include <stdioi.h>
typedef int HPDataType;
//交换
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType x = *a;
*a = *b;
*b = x;
}
//向上调整
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//孩子比双亲大,交换
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
//判断有孩子是否存在,如果存在判断左右孩子哪一个大,右孩子大则加1
if (child + 1 < n && a[child] < a[child + 1])
{
child++;
}
//在这里使用较大的孩子和双亲比较,如果孩子节点比双亲节点大,交换。
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
int i = 0;
//第一种方法:向上调整建堆
//for (i = 1; i < n; i++)
//{
// //从第一个孩子节点向上调整
// AdjustUp(a, i);
//}
//第二种方法:向下调整建堆 (推荐使用) 这里的n-1-1是先获取下标,然后根据完全二叉树左右孩子序号2*i+1(2)
for (i = (n - 1 - 1) / 2; i >= 0; i--)
{
//从倒数第一个双亲节点
AdjustDown(a, n, i);
}
//升序排序,使用堆删除的思想,把最大的数也就是根节点和最后一个节点交换,然后循环完成这一过程,当然每次运算之后,最后一个就不在参与运算。
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
//每次去掉一个
AdjustDown(a, end, 0); // 孩子的下标小于最大有效数据的下标
end--;
}
//打印升序数组
for (i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 27,15,19,18,28,34,65,49,25,37 };
//计算数组大小
int sz = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr, sz);
return 0;
}
二、 建堆的时间复杂度
求的是完全二叉树,但是用的满二叉树计算的时间复杂度,因为多几个少几个节点对总体来说影响不大。本身时间复杂度求得也只是近似值
1. 向下调整建堆的时间复杂度
//向下调整建堆
for (i = (n - 1 - 1) / 2; i >= 0; i--)
{
//从倒数第一个双亲节点开始向下调整。因为它要保证左右子树都已成堆,所以要从最小的左右子树开始。
AdjustDown(a, n, i);
}
2. 向上调整建堆的时间复杂度
//向上调整建堆
for (i = 1; i < n; i++)
{
//从第一个孩子节点开始向上调整。因为他要保证前面已经建成一个堆
AdjustUp(a, i);
}
结论
- 向上调整的建堆时间复杂度:O(N*logN)
- 向下调整的建堆时间复杂度:O(N)
所以向下调整建堆的时间复杂度更优。
三、 堆排序的时间复杂度
已经完成建堆,这里进行堆排序,利用堆删除的思想
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
//每次去掉最后一个
AdjustDown(a, end, 0);
end--;
}
总结
使用向下建堆,时间复杂度更优一些。
因为堆排序时间复杂度是O(NlogN),所以无论使用向上调整或者向下调整,整个排序的时间复杂度还是O(NlogN)。我们需要掌握向下建堆,掌握一个向下建堆就可以完成堆排序,并且还要比向上建堆好一些。
- 向下调整建堆:节点少调整次数多。节点多调整次数少。
- 向下调整建堆:节点少调整次数少,节点多调整次数多。