目录
前言
向下调整法建堆的时间复杂度为O(N),向上调整法建堆的时间复杂度为O(N*LogN),他们的时间复杂度是如何计算的呢?堆排序中,实现升序就建立大堆,实现降序就建立小堆,又是为什么呢?这篇文章将为铁汁们解答这两个问题。
向上调整法的时间复杂度计算
如何实现向上调整法建堆呢?其实与堆的插入原理相同,就是遍历一个数组,对数组中的每个元素都进行向上调整,遍历完后,这个数组就变成堆啦!代码实现如下:
//向上调整算法
//a为需要修改的堆的地址,child为向上调整时,孩子节点的下标
void AdjustUp(HPDataType* a, size_t child)
{
assert(a);
size_t parent = (child - 1) / 2;
while (child > 0)
{
//大堆
//if (a[child] > a[parent])
//小堆,如果孩子比父亲小,就交换
if (a[child]<a[parent])
{
Swap(&a[child],&a[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
//size是需要建堆数组的元素个数
for (int i = 0; i < size; i++)
{
AdjustUp(a, i);
}
向上调整的时间复杂度计算过程如下:
向下调整法的时间复杂度计算
向下调整法建堆的方式略有不同,因为向下调整法有个前提,只有当左右子树为堆时才可以向下调整,因此我们需要倒着,从倒数第一个非叶子节点开始,一直到根节点递减,都进行向下调整,代码实现如下:
//向下调整算法
//a为需要修改的堆的地址,size为堆的有效数据个数,root为向下调整时根节点的下标
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
assert(a);
size_t parent = root;
size_t child = parent * 2 + 1;
while (child<size)
{
//先找出那个较小的孩子
//判断大小的同时要注意是否有右孩子节点,即是否造成数组越界
if (child + 1 < size && a[child + 1] < a[child])
{
child++;
}
//小堆,如果爸爸比孩子大,就往下调整
if (a[parent] > a[child])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
for(int i = (n-1-1)/2;i>=0;i--)
{
AdjustDown(a, n, i);
}
向下调整法的时间复杂度计算过程如下:
为什么升序建立大堆,降序建立小堆?
如果实现升序建立的是小堆会发生什么呢?
假如建立的是如下图小堆:
- 最小的数已经在堆顶的位置上
- 但是除了堆顶以外,剩下的数都不是有序的,如果需要找出剩下的数中最小的数,需要重新建堆,建堆最少需要O(N)个时间复杂度,而N个数,需要O(N*N)个时间复杂度,这样排序的时间复杂度太大,还不如直接遍历排序。
- 所以需要建立大堆,堆顶是最大的数,然后同最后一个数交换,再把交换后的堆顶数向下调整,然后重复交换再向下调整新堆顶数,直到实现排序(具体实现过程可以看上一篇博客机械转码日记【3】——《数据结构》堆的实现及堆的应用_逗你笑出马甲线的博客-CSDN博客)。
降序建立小堆的原因也是这样,为了防止篇幅过长,这里就不再赘述了。