[关闭]
@Zh1Cheung 2018-03-16T12:34:13.000000Z 字数 1695 阅读 522

排序(3):堆排序

堆排序是利用堆的性质进行的一种选择排序。下面先讨论一下堆。

堆实际上是一棵完全二叉树,其满足性质:任何一结点大于等于或者小于等于其左右子树结点。

堆分为大顶堆和小顶堆,满足“任何一结点大于等于其左右子树结点”的称为大顶堆,满足“任何一结点小于等于其左右子树结点”的称为小顶堆。由上述性质可知:大顶堆的堆顶肯定是最大的,小顶堆的堆顶是最小的。

下面举个例子(资源来自堆排序-海子)来说明堆排序的过程(以升序为例):

(1)

给定整型数组:{ 16, 7, 3, 20, 17, 8 },根据该数组“构建”完全二叉树(并不是真的写代码去构建,只是把数组看成完全二叉树去操作)。

程序从最后一个非叶子结点开始,即3。判断其左右孩子:8,8比3大,把8调整上去。

(2)

3结点下无孩子,判断结束。

继续往前一步,至7结点,判断其左右孩子:20和17,20是最大的,将其调整上去。

(3)

7结点下无孩子,判断结束。

继续往前一步,至16结点,判断其左右孩子:20和8,20是最大的,将其调整上去。

(4)

判断16结点下左右孩子:7和17,17是最大的,将其调整上去。

(5)

16结点下无孩子,判断结束。

遍历已至头部,结束。

(6)至此数组已经满足大顶堆的性质,接下来的操作就很简单了。

看完上面所述的流程你至少有两个疑问:

以下代码默认数组下标从1开始,请读者注意。

/* 已知 array[left]...array[right] 的值除 array[left] 之外均满足堆的定义,
本函数调整 array[left],使 array[left]...array[right] 成一个大顶堆 */
void HeapAdjust(int array[], int left, int right)
{
    int index = left;

    for (int i = left * 2; i <= right; i = i * 2)
    {
        if (i < right && array[i] < array[i + 1])  // 找到孩子中较大者
            i++;
        if (array[index] > array[i])
            return;
        swap(array[index], array[i]);
        index = i;
    }
}

void HeapSort(int array[], int left, int right)
{
    int len = right - left + 1;

    for (int i = len / 2; i >= left; i--)  // 把数组调整成大顶堆
        HeapAdjust(array, i, right);

    for (int i = right; i > left; i--)     // 排序
    {
        swap(array[left], array[i]);
        HeapAdjust(array, left, i - 1);
    }
}

时间复杂度为,证明如下。

首先计算建堆的时间,也就是下面的代码,

for (int i = len / 2; i >= left; i--)  // 把数组调整成大顶堆
    HeapAdjust(array, i, right);

n个结点,从第0层至第层。对于第i层的个点如果需要往下走步,那么把走的所有步相加得,

接下来就是排序的时间,即下面的代码:

for (int i = right; i > left; i--)  // 排序
{
    swap(array[left], array[i]);
    HeapAdjust(array, left, i - 1);
}

HeapAdjust()耗时,共n次,故排序时间为

综上所述,堆排序时间复杂度为

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注