c++ 堆排序

堆排序

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。

图解堆排序

在这里插入图片描述

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

  1. 步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
  2. 此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 为 倒数第二层左右边的节点),从左至右,从下至上进行调整。
  3. 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后将剩余的n-1个元素继续调整堆**(只需调整堆顶元素)**,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

简单总结

a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

// 递归方式构建大根堆(len是arr的长度,index是第一个非叶子节点的下标)
void adjust(vector<int> &arr, int len, int index)
{
    int left = 2*index + 1; // index的左子节点
    int right = 2*index + 2;// index的右子节点

    int maxIdx = index;
    if(left<len && arr[left] > arr[maxIdx])     maxIdx = left;
    if(right<len && arr[right] > arr[maxIdx])     maxIdx = right;

    if(maxIdx != index)
    {
        swap(arr[maxIdx], arr[index]);
        adjust(arr, len, maxIdx);
    }
}
vector<int> Solution::Heapsort_Sort(vector<int> &nums){
    int len = nums.size();

    // build heap
    for(int i=(len-1)/2;i>=0;i--)
        MaxHeapify(nums,len,i);
        
    for(int i=len-1;i>=1;i--){
        swap(nums[0],nums[i]);
        MaxHeapify(nums,i,0);
    }
    return nums;
}

非递归方式构建堆

void adjust(vector<int> &arr, int len, int index)
{   
    int left,right,maxIdx;
    stack<int> p;
    p.push(index);
    while(!p.empty()){
        index = p.top();
        p.pop();
        left = 2*index + 1; // index的左子节点
        right = 2*index + 2;// index的右子节点
        maxIdx = index;
        
        if(left<len && arr[left] > arr[maxIdx])     maxIdx = left;
        if(right<len && arr[right] > arr[maxIdx])     maxIdx = right;
        
        if(maxIdx != index){
            // 如果交换,则需要继续向下一层(子树) 进行判断
            swap(arr[index],arr[maxIdx]);
            p.push(maxIdx);
        }else break;
    }
}

复杂度分析

  1. 初始化堆(建堆)

初始化堆 只需对每个非叶子节点进行 adjust_heap 函数调用,由下向上 由右向左(最后一个非叶子节点在 最底层最右边)。

假设一共有k层,则从倒数第二层开始调整,这一层所有节点都需要与 子节点 进行比较调整(如果满足,则不需要调整)。然后是第三层,所有节点与 子节点进行比较调整(如果满足,则不需要继续向下调整),如果不满足,还需要选出最大的子节点进行交换,然后打破了子树的性质(堆),还需要选择子节点那一分支继续递归调整。高层都需要递归调整

每一层调整的时间为a = 2^(i-1)*(k-i),其中i为第几层,k为总层数,2^(i-1)为当前层的节点个数,k-i表示当前层节点 需要上下比较的次数
所以总时间为 Sn = 2^(k-2)*1 + 2^(k-3)*2+...+2^0*k ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;
S = 2^k -k -1;又因为k为完全二叉树的深度,而log(n) =k,把此式带入;
得到:S = n - log(n) -1,所以时间复杂度为:O(n)

  1. 调整堆(排序)

在将堆顶点的元素放到堆尾(堆减1),需要对堆进行重建,只需要对堆的顶点调用adjustheap()函数。

每次重建意味着有一个节点出堆,所以需要将堆的容量减一。adjustheap()函数的时间复杂度k=log(n)(顶点需要上下比较的次数为k,k为堆的层数)。所以在每次重建时,随着堆的容量的减小,层数会下降,函数时间复杂度会变化。重建堆一共需要n-1次循环,每次循环的比较次数为log(i),则相加为:log2+log3+…+log(n-1)+log(n)≈log(n!)。可以证明log(n!)和nlog(n)是同阶函数:
  ∵(n/2)n/2≤n!≤nn
  ∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)
  所以时间复杂度为O(nlogn)

  1. 总结

初始化建堆的时间复杂度为O(n),排序重建堆的时间复杂度为nlog(n),所以总的时间复杂度为O(n+nlogn)=O(nlogn)。另外堆排序的比较次数和序列的初始状态有关,但只是在序列初始状态为堆的情况下比较次数显著减少,在序列有序或逆序的情况下比较次数不会发生明显变化。

参考文献

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值