排序系列(二)堆排序

堆排序和简单选择排序同属于选择排序。
堆排序利用的是一种特殊的二叉树结构对无序序列进行排序。
堆是一种特殊的二叉树,每个子节点的值总是小于(或者大于)它的父节点,我们分别称他们为最大堆和最小堆。
由于堆是一个完全二叉树,一般情况下堆排序都是用数组的方式来实现。
堆排序的核心思想是:利用最大堆(或者最小堆)输出堆顶元素,即最大值(或最小值),将剩余元素继续生成最大堆(或最小堆),继续输出堆顶元素,重复此过程,直到全部元素都已输出得到的输出元素序列即为有序序列。
实现堆排序方法一种简单的做法是额外开辟一个辅助的数组空间,将堆顶元素逐一放入辅助数组里,最后再把辅助数组的内容复制回原始的数组。这种方法的额外空间复杂度是0(N)。下面我们讨论一种更聪明的方法,只用0(1)的额外空间即可。
如下图所示,首先将一个无序的序列生成一个最大堆,如图(a)所示。接下来我们不需要将堆顶元素输出,只要将他与堆的最后一个元素对换位置即可,如图(b)所示。这时我们确知最后一个元素99一定是递增序列的最后一个元素,而且已经在正确的位置上。
现在的问题变成了如何将剩余的元素重新生成一个最大堆——也很简单,只要依次自上而下进行过滤,使其符合最大堆性质。图(c)是调整后形成的新的最大堆。要注意的是,99已经被排除在最大堆外,即在调整的时候堆中最大元素个数应该减1,结束第1轮调整后,再次将当前堆中最后一个元素22与堆顶元素换位,如图(d)所示,再继续调整成新的最大堆... ... 如此循环直到堆中只剩1个元素,即可停止,得到一个从小到大排列的有序序列。

c6ad4532-b0ae-46e0-a44d-478064e44903.png

这个方法的时间复杂度为O(NlogN),但是不需要额外的辅助数组,所以额外空间复杂度是O(1)
算法实现(C#):

//堆排序 一种特殊结构的二叉树
public static void HeapSort(int[] arr)
{
    // 建立最大堆
    int i;
    for (i = arr.Length / 2 - 1; i >= 0; i--)
        PercDown(arr, i, arr.Length);

    // 删除最大堆顶
    for (i = arr.Length - 1; i > 0; i--)
    {
        Swap(ref arr[0], ref arr[i]);
        PercDown(arr, 0, i);
    }
}

//下滤操作
private static void PercDown(int[] arr, int index, int length)
{
    // 将arr[index]为根的length长度个元素的堆调整为最大堆
    int parent, child = 0, x;
    // 取出根节点存放的值
    x = arr[index];

    for (parent = index; (parent * 2 + 1) < length; parent = child)
    {
        child = parent * 2 + 1;
        // child指向左右字节点中较大者
        if ((child != length - 1) && (arr[child] < arr[child + 1]))
            child++;
        // 找到合适位置
        if (x >= arr[child])
            break;
        else// 下滤X
            arr[parent] = arr[child];
    }

    arr[parent] = x;
}

(注意:Swap函数见排序序列第一篇文章)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值