堆排序 C++递归实现

一、堆排序与简单选择排序

堆排序是简单选择排序的进阶版。

前面说了,简单选择排序是每次从操作后的数组中选出最值。这个过程中,选择的对象,也就是数组,除了长度会改变外,其他未被处理的元素的相对位置是不变的。也就是没有在处理过程中保留对我们有利的信息。

而堆排序恰恰是实现了保留有用信息的这一点。

二、堆排序思路

1. 堆的概念

我们后续的操作就是基于堆的概念完成的,需要用数组构建堆结构。只针对简单的数据结构来说,堆是一个完全二叉树,并且可以分为大顶堆和小顶堆。

大顶堆:父节点的值大于他任意子节点的值,那么完全二叉树的根节点就是整个数组中的最大值。

小顶堆:父节点的值小于他任意子节点的值,那么完全二叉树的根节点就是整个数组的最小值。

而针对完全二叉树,访问一个节点的父节点和他的左右孩子有如下公式:
父节点:(i - 1)/ 2
左孩子:2 * i + 1
右孩子:2 * i + 2

2. 建立堆、取值、维护堆

整个排序过程的要点就是建立堆,交换根节点,维护堆。

在我们建立好堆以后,取出他的根节点,与数组最后一个节点进行交换。这时,最大值就被放到了数组的最后。我们更新数组,在逻辑上去掉此时的最后的一个元素,生成有n-1个元素的新数组。

然后根据新数组去建立堆,由于这时候的堆与初始的堆相比,只是根节点不同,且少了一个元素。故不用去按照建立堆的步骤,只需要去维护堆即可。

维护堆是根据新数组,建立堆是根据原数组,既然最终结果都是生成一个符合要求的堆,那么他们肯定有重合的部分。而前面说到,维护堆是在建立堆的基础上,那么操作更少,我们先来看维护堆。

维护堆(递归

在交换根节点,维护堆时,只有当前根节点的位置从原来的数组末尾变到了现在的位置,所以从根节入手来维护他。假设当前根节点的下标为 i

首先,根节点的左孩子2 * i + 1是否的值是否小于根节点的值?如果大于,那么就要将左孩子和根节点交换。

如果左孩子小于根节点,那右孩子的值是否小于根节点?如果大于,那么就要将右孩子和左孩子进行交换。

如果交换,那么交换后的堆还是可能不符合要求,所以需要再对交换到左孩子或右孩子的原根节点进行判断。这一步就是递归。
附上代码和注释

// 堆排序是选择排序的优化,选择排序每次寻找最大值或者最小值的方法是遍历剩下的数组
// 而堆排序寻找最大值或最小值的方法是维护堆。但在这个过程中,保留了一些排序的信息
// 也就是每次维护堆的序列与原始序列存在差别,一定程度上加快了寻找最大值或最小值的速度

// 堆排序有三个动作:
// 1. 根据无序序列构建堆(大顶堆为例,也就是需要升序,因为把排序后的值放入序列最后)。是自下而上的
// 2. 将顶部元素和数组末尾元素交换(确定最大值排序后的位置),此时破坏了大顶堆,
//    则需要维护大顶堆,维护大顶堆的序列是去掉已经排号序的最后一个值的序列,是自上而下进行检查的
// 3. 维护好大顶堆后,继续交换堆顶元素和新序列的最后一个值,重复下去。直到新序列只剩一个值

// 维护堆
void fixHeap(int arr[], int index, int size)
{
    int left = 2 * index + 1;
    int right = 2 * index + 2;
    int target = 0;
    // 左孩子和根节点比较,左孩子比较大就交换
    if (left < size && arr[index] < arr[left])
    {
        target = left;
    }
    else
    {
        target = index;
    }
    // 右节点跟target比较,实际上是跟左孩子或父节点比较
    // 在左孩子和有孩子还有根节点中,找到最大值
    if (right < size && arr[target] < arr[right])
    {
        target = right;
    }

    // 上面比较下来,如果当前节点不是父节点,就把他当前节点与父节点交换
    if (index != target)
    {
        swap(arr[index], arr[target]);
        // 交换值以后,可能破坏大顶堆的结构,所以递归检查
        fixHeap(arr, target, size);
    }
}
建立堆

维护堆是根据根节点进行的,那么我们建立堆同样可以根据根节点建立,最大的根节点是n/2,最小的根节点是0,那就从n/2到下标0都维护一遍,堆不就建立好了吗

代码

// 建立堆
void buildHeap(int arr[], int length)
{
    // int length = sizeof(arr)/sizeof(int);
    for (int i = length / 2; i >= 0; i--)
    {
        fixHeap(arr, i, length);
    }
}
堆排序

分析了建立堆和维护堆,那么堆排序就很简单了。首先,先建立堆,然后把根节点和数组最后一个元素交换;交换后维护新的堆,继续交换。直到最后数组长度为1(在代码中体现就是循环length遍),排序就结束了。
代码

// 堆排序
void heapSort(int arr[], int length)
{
    // 先建立堆
    // int length = sizeof(arr)/sizeof(int);
    buildHeap(arr, length);
    int j = length;
    while (j > 1)
    {
        // 把顶步元素和序列最后一个元素交换
        swap(arr[0], arr[j - 1]);
        // 上述操作破坏了大顶堆,同时我们也需要下一个最大值
        // 所以维护大顶堆,找到剩下队列中的最大值
        j--;
        fixHeap(arr, 0, j);
    }
}
主函数
int main()
{
    int arr[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
    int length = sizeof(arr) / sizeof(int);
    heapSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    system("pause");
    return 0;
}

有问题评论区交流,互相进步。既要有输入,也要有输出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值