排序算法之堆排序

堆的定义

若干个元素序列{a1, a2, ……, an}满足

则分别称为该序列{a1, a2, ……, an}为小根堆大根堆

从堆的定义可以看出,堆实质是满足如下性质的完全二叉树二叉树中任一非叶子结点均小于(大于)它的孩子结点

堆排序

若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列重新又建成一个堆,则得到n个元素的次小值(次大值)……如此反复,便能得到一个有序序列,这个过程称之为堆排序。

实现堆排序需要解决的两个问题

  • 如何由一个无序序列建成一个堆?

答:堆的建立:

显然:单结点的二叉树是堆;

在完全二叉树中所有以叶子结点(序号i>n/2)为根的子树是堆。这样,我们只需要依次以序号为n/2, n/2-1, ……, 1的结点为根的子树均调为堆即可。

  • 如何在输出堆顶元素后,调整剩余元素为一个新的堆?

答:小根堆:

  1. 输出堆顶元素后,以堆中最后一个元素替代;
  2. 然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;
  3. 重复上述操作,直至叶子结点,将得到新的堆,称这个堆顶至叶子的调整过程为“筛选”

实质上,堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的。

堆排序分析

  1. 堆排序的时间主要耗费在建初始堆和调整新堆时进行的反复“筛选”上。堆排序在最坏情况下,其时间复杂度也为O(nlog2n),这是堆排序的最大优点。无论待排序列中的记录是正序还是逆序排列,都不会使堆排序处于“最好”或“最坏”的状态。
  2. 另外,堆排序仅需要一个记录大小供交换用的辅助空间。
  3. 然而堆排序是一种不稳定的排序方法,它不适于待排记录个数n较少的情况,但是对于n较大的文件还是很有效的。

堆排序的代码实现

C语言实现堆排序

#include <stdio.h>

void Swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 已知arr[]中除了关键字R[s]以外,其他都满足堆的定义
void HeapAdjust(int arr[], int s, int size)
{
    int rc = arr[s];                      // 备份出错的结点
    for (int i = 2 * s; i < size; i *= 2) // 从出错结点的孩子开始,沿较小孩子向下筛选
    {
        if (i <= size - 2 && arr[i] > arr[i + 1]) // 如果两个孩子都在,先选出其中较小的
            i++;
        if (rc < arr[i]) // 如果最小的大于根节点,那就调整好了
            break;
        arr[s] = arr[i]; // 将较小的孩子上移为根节点
        s = i;           // 更新s结点,以便下次筛选
    }
    arr[s] = rc; // 找到错误结点的正确位置后,将备份值取回
}

void HeapSort(int arr[], int size)
{
    int i;
    for (i = size / 2 + 1; i > 0; i--) // 构建堆,即第一次挑出最小值
    {
        HeapAdjust(arr, i, size);
    }
    for (i = 1; i < size; i++)
    {
        Swap(&arr[1], &arr[size - i]); // 最小值交换到最后(降序排序)
        HeapAdjust(arr, 1, size - i);  // 将前面部分继续调整成为新的堆
    }
}

void Show(int arr[], int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("%d\t", arr[i]);
    }
}

int main()
{
    int arr[] = {0, 49, 38, 65, 97, 76, 13, 27, 49};
    int size = sizeof(arr) / sizeof(arr[0]);
    HeapSort(arr, size);
    Show(&arr[1], size - 1);
    return 0;
}

c++实现堆排序

在c++中可以利用其封装好的数据结构实现堆排序,相比c语言来说代码相对简单。

在c++中可以利用优先级队列来实现堆排序。

什么是优先级队列呢?

首先介绍一下队列,队列是一种先进先出(First in First out,FIFO)的数据类型。每次元素的入队都只能添加到队列尾部出队时从队列头部开始出。

对于优先级队列(priority_queue),其实就是将队列的入队和出队附加了一个条件,而不是简单的从头部出队,从尾部入队,而这个附加的条件就是优先级优先级队列入队后,其位置会根据优先级排列,出队的时候会弹出优先级最高的元素。而这个优先级可以由用户自己定义,对于优先级队列(priority_queue)这个c++封装的数据类型其优先级默认是less,就是大顶堆,排序出来就是降序排列。当使用自定义的数据结构时,可以重写比较函数,也可以对比较运算符(“>”和“<”)进行重载。

现在重新再看优先级队列,就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。

C++中,使用优先级队列需要包含头文件<queue>,优先级队列的定义如下:

priority_queue<typename, container, functional>
  • typename是数据的类型;
  • container是容器类型,可以是vector,queue等用数组实现的容器,不能是list,默认可以用vector;
  • functional是比较函数,可以由用户自定义。

完整的c++堆排序代码如下:

#include <iostream>
using namespace std;
#include <vector>
#include <queue>

class Solution
{
public:
    vector<int> heapsort(vector<int> &nums)
    {
        //默认比较函数less,为大根堆,即降序排列
        priority_queue<int, vector<int>> pque(nums.begin(), nums.end()); 
        // 更改比较函数为greater,为小根堆,升序排列,也可自己定义
        // priority_queue<int, vector<int>,greater> pque(nums.begin(), nums.end());
        int size = pque.size();
        vector<int> result(size);
        for (int i = 0; i < size; i++)
        {
            result[i] = pque.top();
            pque.pop();
        }
        return result;
    }
};

int main()
{
    vector<int> v = {49, 38, 65, 97, 76, 13, 27, 49};
    Solution s;
    v = s.heapsort(v);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值