排序算法总结三(堆排序)详解 C++代码实现(包括建造最大堆)

一、概述

最近在复习算法和数据结构,一直以来对堆(heap)不是特别熟练,今天算是好好从头到尾梳理了一下,现在做个记录,以后可别再忘了。

二、构造最大堆

1. 最大堆的定义(性质)

算法导论上是这么说的

二叉堆可以分为两种形式:最大堆和最小堆。在这两种堆中,结点的值都要满足堆的性质,但一些细节定义则有所差异。在最大堆中,最大堆性质是除了根以外的所有节点 i i i 都要满足: A [ P A R E N T ( i ) ] > = A [ i ] A[PARENT(i) ]>= A[i] A[PARENT(i)]>=A[i], 最小堆则恰好相反。

就是说最大堆的每个结点的值肯定要大于等于其孩子节点的值,最小堆则是每个结点的值小于等于孩子节点的值。

首先,我们是通过数组模拟堆(二叉树)的结构的。堆的存储方式则是按照数组从头到尾由二叉树的层序遍历存储的。在构造最大堆之前,要了解这么两个性质:
a. 对于一个存储了n个数据的堆( A [ n ] = { a [ 0 ] , a [ 1 ] , ⋯   , a [ n − 1 ] } A[n] = \{a[0],a[1], \cdots,a[n- 1]\} A[n]={a[0],a[1],,a[n1]})来说,其最后一个具有叶子节点的节点的下标为 n / 2 - 1
b. a[i] 节点的左右孩子节点分别为 a[2 * i + 1] 和 a[2 * i + 2],如果存在的话

2. 最大堆的构造

构造最大堆是一个循环迭代(或者递归)的过程,每一次迭代,都会将当前子树中的最大值移动到当前子树的根节点。所以,我们应该从最后一个包含叶子节点的节点(即 a [ n / 2 − 1 ] a[n/2- 1] a[n/21])开始进行位置更换,比较当前节点与字节点的大小,将最大节点的值与根节点交换。

例如,对于数组 a = { 3 , 5 , 1 , 2 , 4 } a =\{3,5,1,2,4\} a={3,5,1,2,4}来说,它有5个数据,我们每次从 a [ 5 / 2 − 1 ] a[5/2-1] a[5/21]这个节点开始进行位置更换。如下图所示,红色节点表示当前访问的节点,对于数组 a a a来说,只要两轮即可将其构造成一个最大堆。
在这里插入图片描述
第一轮:首先访问 a [ 1 ] = 5 a[1] = 5 a[1]=5,比较其与两个孩子节点(分别为 a [ 2 ∗ 1 + 1 ] = 2 , a [ 2 ∗ 1 + 2 ] = 4 a[2*1+1]=2,a[2*1+2]=4 a[21+1]=2,a[21+2]=4)的大小,发现 5 是最大的,不用更换位置,然后向前一个节点移动,移动到 a [ 0 ] = 3 a[0] = 3 a[0]=3,发现 a [ 1 ] = 5 a[1]=5 a[1]=5是最大的,所以将 3 和 5 的值互换,到此第一轮结束。

第二轮:首先访问 a [ 1 ] = 3 a[1] = 3 a[1]=3,比较其与两个孩子节点(分别为 2 和 4)的大小,发现 4 是最大的,将 4 与 3 互换,因为 a [ 0 ] a[0] a[0]已经是最大,不用再向前移动,所以第二轮到此结束。所以对于数组 { 3 , 5 , 1 , 2 , 4 } \{3,5,1,2,4\} {3,5,1,2,4}最终构造出来的最大堆为 { 5 , 4 , 1 , 2 , 3 } \{5,4,1,2,3\} {5,4,1,2,3}

三、堆排序

算法导论上是这么说的

初始时候,堆排序算法利用BUILD-MAX-HEAP将输入数组A[1…n]建成最大堆,其中 n = A.length。因为数组中的最大元素总在根节点A[1]中,通过把它与A[n]进行互换,我们可以让该元素放到正确的位置。

这里需要注意的是,算法导论中说的下标是从1开始的。

意思就是,堆排序是基于最大堆(最小堆是降序,最大堆是升序)进行的。最大堆的根节点,也就是 a [ 0 ] a[0] a[0]是所有元素中最大值,每次只要将最大堆的根节点即 a [ 0 ] a[0] a[0]与数组最后一位 a [ n − 1 ] a[n-1] a[n1]交换,则 a [ n − 1 ] a[n-1] a[n1]变为最大值,然后对剩下的 { a [ 0 ] , ⋯   , a [ n − 2 ] } \{a[0],\cdots, a[n - 2]\} {a[0],,a[n2]}维护最大堆,这样循环迭代(或递归)下去,就可以得到一个升序排列的数组,即完成了堆排序。堆排序过程示意如下图:
在这里插入图片描述
最大堆的维护
每次在将根节点与剩下数据的最后一位元素交换后,可能会出现违反最大堆性质的情况(即根节点的值小于子节点),例如第一轮 5 与 3 交换后,3 小于其左子节点 4,则需要将 3 与 4 交换,直到大于等于子节点(子节点不包含已经排好序的 5),重复此过程,即可得到堆排序的结果为 { 1 , 2 , 3 , 4 , 5 } \{1,2,3,4,5\} {1,2,3,4,5}

四、代码实现

递归实现

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

//时间复杂度O(logN),维护堆的性质
void maxHeapfy(vector<int>& nums, int cur, int endmark)
{
    int father = cur;
    int son = 2 * father + 1;
    while(son < endmark) //避免排好序的值又被换回去
    {
        if(son + 1 < endmark && nums[son] < nums[son + 1])
            son++;
        if(nums[father] < nums[son])
        {
            swap(nums[son], nums[father]);
            father = son;
        }
        son = 2 * son + 1;
    }
}

//时间复杂度 O(N),建堆
void buildMaxHeap(vector<int>& nums, int n)
{
    for(int i = n / 2 - 1; i >= 0; i--)
        maxHeapfy(nums, i, n);
}

//堆排序,递归方式
void heapSort(vector<int>& nums, int endmark)
{
    if(endmark == 0) return;
    maxHeapfy(nums, 0, endmark);
    swap(nums[0], nums[endmark - 1]);
    heapSort(nums, endmark - 1);
}

//堆排序,迭代方式,时间复杂度O(NlogN)
void heapSort(vector<int>& nums)
{
    for(int i = nums.size(); i > 0; i--)
    {
        maxHeapfy(nums, 0, i);
        swap(nums[0], nums[i - 1]);
    }
}

int main()
{
    vector<int> nums;
    int n;
    cin >> n;
    nums.resize(n);
    for(int i = 0; i < n; i++)
        cin >> nums[i];
    buildMaxHeap(nums, n);
    heapSort(nums);
    for(auto a : nums)
        cout << a << " ";
    cout << endl;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值