堆排序(Heap Sort)

本文详细介绍了堆排序算法的工作原理及其实现过程。通过讲解二叉堆的概念,进而阐述如何利用二叉堆特性进行高效排序。文章还提供了具体的代码示例,帮助读者理解堆排序的具体操作步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  堆排序是一种高效率的排序方法,它充分的利用了二叉堆的性质,无需借助额外的辅助空间,并且拥有O(n*log(n))的时间复杂度。

  首先这里我从二叉堆讲起。二叉堆是一种具有一定逻辑关系的完全二叉树(这里需要和满二叉树做一下区分),它始终满足:任意节点的值均大于(或小于)其子节点,满足该条件的二叉堆又叫大根堆(或小根堆)。

  再说堆排序,它即是利用了二叉堆的该性质,在每次调整二叉堆结构后取堆顶元素,即该二叉堆内最大(或最小)的元素,取n次后得到的序列即为一个有序序列。

  接下来来看一下队的存储结构:

int heap[HEAPLENGTH];
int *anotherHeap = new int[HEAPLENGTH];

  由于二叉堆满足“完全二叉树”的性质,因此二叉堆可以很轻松的由一个一维数组来表示,而祖先节点和儿子节点可以由索引很方便的计算出,来看代码:

int getLeftChild(const int &index) {
    if (index < 0) return (0);
    return (2 * index + 1);
}

int getRightChild(const int &index) {
    if (index < 0) return (0);
    return (2 * index + 2);
}

  需要注意的是这里计算索引的前提是使用了“0~n - 1”下标的数组,这样可以不造成内存浪费。

  通过这两个方法可以快速的得到与传入参数对应的该完全二叉树的左(右)儿子,方便后续的算法操作。

  接下来就看一下构建堆的过程,首先笔者总结了一个规律:在一个拥有n个节点的完全二叉树中,最后一个拥有孩子的节点在层次遍历序列中的位置为(n / 2) - 1。因此,为了节省资源,可以从该位置进行递归向下调整,直至整个二叉完全树满足堆的条件。下面给出代码:

void adjustHeap(int *arr, const int &index, const int &n) {
    int child = getLeftChild(index);
    int now = index;
    while (child < n) {
        if (getRightChild(now) < n && arr[child] /**/ < /**/ arr[getRightChild(now)]) {
            child = getRightChild(now);
        }
        if (arr[now] /**/ < /**/ arr[child]) {
            dataSwap(arr[now], arr[child]);
        }
        else break;
        now = child;
        child = getLeftChild(now);
    }
}

  当然,你可能注意到该段代码里的getRightChild完全可以被child + 1替代(因为右儿子肯定紧挨着左儿子),这里只是为了易读。

  在调整过后,我们可以由堆的性质得知堆顶(最前面那个元素)一定是大根堆(可以通过替换代码中有注释部分的“<”为“>”来将调整过程改为小根堆调整),然后再将第(0)的元素和第(n - 1 - 调整次数)的元素交换,来保证第(n - 1 - 调整次数)的元素到第(n - 1)的元素的序列为有序序列。此时堆顶元素变化,因此可能破坏了堆的性质,则再从堆顶进行递归向下调整,最终经过n - 2次调整,即从第(n - 1 - (n - 2) = 1)的位置到第(n - 1)的位置为有序序列,第0的位置为堆,自然整个序列满足了排序的最终要求,堆排序完成。下面给出堆排序代码:

void dataSwap(int &a, int &b) {
    if (&a == &b || a == b) return;
    a = a + b;
    b = a + b;
    a = b - a;
    b = b - 2 * a;
}

void heapSort(int *arr, const int &n) {
    if (arr == NULL || n <= 0) {
        return;
    }
    for (int i = (n / 2) - 1; i >= 0; i--) {
        adjustHeap(arr, i, n);
    }
    for (int i = n - 1; i > 0; i--) {
        dataSwap(arr[0], arr[i]);
        adjustHeap(arr, 0, i);
    }
}

  有一个有趣的函数,即“dataSwap”,本质上是一个交换函数,但是不需要借助中间量。

  如有不对敬请指出,感谢阅读!

参考资料
完全二叉树
满二叉树
二叉堆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值