Acwing算法基础篇(二) 堆排序

输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。

输入格式
第一行包含整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。

数据范围
1≤m≤n≤1051≤数列中元素≤109
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3

的定义:一颗完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
 而堆又有大根堆小根堆之分,若子节点的值小于等于父节点,则为小根堆,反之为大根堆。
 介绍了堆的概念后,我们渴望手写出一个堆,它具有以下功能。

  • 插入一个数
  • 求最小值
  • 删除最小值
  • 删除任意一个元素
  • 修改任意一个元素
  •  具体实现时我们选用一维数组实现。

 首先是编号问题。

在这里插入图片描述
 根节点编号为1,对于编号为x的元素,它的左儿子为2x,右儿子为2x+1。
 堆功能的实现主要由两个操作达成,它们为up()与down(),它们的功能是维持堆的有序性。下面先通过小根堆解释down()操作。
在这里插入图片描述
 如图,此时堆并不满足小根堆性质,因此我们需要对破坏了性质的“6”元素进行down操作,把它放至它的正确位置。
 首先根据小根堆的性质,我们选择3来与6进行交换,因为对于6,3,4来说,3是最小的元素,将它上调并不会影响堆的有序性。
在这里插入图片描述
 移动过后发现6仍处于错误的位置,因此再执行一次上述操作。
在这里插入图片描述
 同理,up操作就是处于底层的数需要不断的上移,来寻找自己合适的位置。例子如下
在这里插入图片描述
 显然2的位置是错误的。因此我们让2与自己的兄弟结点进行比较,并从中挑出较小的元素与父节点进行交换,完成up操作。
在这里插入图片描述
在这里插入图片描述
 现在我们使用这两个操作来完成目标的5个操作,我们令heap为堆数组,size为当前堆大小

  • 插入一个数:heap[++size]=x;up(size);
  • 求最小值:heap[1]
  • 删除最小值:heap[1]=heap[size];size–;down(1)
  • 删除任意一个元素:heap[k]=heap[size];size–;down(k);up(k);
  • 修改任意一个元素:heap[k]=x;down(k);up(k);
     在这里比较需要注意的就是删除最小值这个操作。因为我们使用一维数组实现堆,所以想要删除第一个元素对我们来说是比较困难的,删除后的down操作也不方便。因此我们选择对最容易操作的末尾元素进行一个down操作,通过它完成整个堆的重排列过程。
     除此之外就是删除任意一个元素,再用末尾元素替换原元素之后,我们为了避免分支判断直接对它进行两次操作。down和up操作不会都执行。
 #include <algorithm>
#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int h[N], cnt;

void down(int u) {
//t表示三个点中的最小值的编号
    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) {
        t = u * 2;
    }
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) {
        t = u * 2 + 1;
    }
    if (u != t) {
        swap(h[u], h[t]);
        down(t);
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> h[i];
    }
    cnt = n;
    for (int i = n / 2; i; i--) {
        down(i);
    }
    while (m--) {
        cout << h[1] << " ";  //输出堆顶(最小值)
        h[1] = h[cnt--];  //删除堆顶(用最后一个数覆盖第一个数) 并且长度 减1
        down(1);  //让覆盖好的数往下面走
    }
    cout << endl;
    return 0;
}


作者:Bug-Free
链接:https://www.acwing.com/solution/content/29416/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 建堆时为了达到O(N)的时间复杂度,我们从2/n开始往上进行down操作。n/2代表最后一个非叶子结点的编号,n/4就是该层的元素个数。假设每个元素都需要(最坏情况下)down至叶子层。那么这一层的操作数就是n/4(元素个数)1(down的层数)。
 同理,对于倒数第二个非叶子层,它的元素个数是n/8,最坏情况下它需要down两层,因此未n/8
2。
在这里插入图片描述
 最终算出来总的时间复杂度都就是O(N)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值