堆手动实现模板 / STL小根堆:堆排序

题目链接:https://www.acwing.com/problem/content/840/

题目:

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

输入格式

第一行包含整数 n 和 m。

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

输出格式

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

数据范围

1≤m≤n≤1e5
1≤数列中元素≤1e9

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

堆的结构是一个完全二叉树的结构。

 其存储方式:

使用一个一维的数组进行存储。

注意:完全二叉树的存储方式一般都是使用一个一维的数组进行存储的。

而使用一维数组的方式存储,所以下标从1开始比较好。如果以1作为开头的话,则左孩子下标为 2* x, 右孩子为 2 * x + 1 .        如果使用0下标开始的话,则左孩子为 2 * x + 1 , 右孩子为 2 * x + 2. 

STL中的堆就是优先队列:priority_queue 

而其中手写堆能够完成的操作有:

down(k):

 up()操作类似,不过其只需与其父结点进行比较即可。如果当前节点小于父结点,则与父结点进行交换。

1.查询最小值: q[1]

2.插入一个值: q[idx] = x , up(idx);

3.删除最小值: q[1] = q[idx] , idx-- , down(1) ; // 因为我们是以一个一维数组的方式进行存储的,一维数组中第一个节点不好删除,但最后一个节点很容易就可以删除,所以当我们想要删除第一个节点的值的时候,可以直接将最后一个值覆盖第一个节点的值,然后down(1)一遍即可。

// 前三个操作,priority_queue也可完成。

4.删除任意一个位置的元素: q[k] = q[idx],  idx -- , down(k),up(k);  // 同3的操作,但是要注意idx的值可能比q[k]大那么就可能要down(),也可能比idx要小,那么就可能要up(),也可能既不用down(),也不用up().     所以直接既down(),up; 因为它只会执行其中一个。

5.修改任意一个元素, q[k] = x , down(k),up(k) ;    

down()和up()操作的时间复杂度:

此两个操作的时间复杂度都与树的高度有关,所以为logn. 

代码实现:

# include <iostream>
using namespace std;
const int N = 1e5 + 10;

int m,n;

int e[N];

void down(int k)
{
    int t = k;
    if(2 * k <= n && e[k] > e[2 * k])
    {
        t = 2 * k;
    }
    if(2 * k + 1 <= n && e[t] > e[2 * k + 1])
    {
        t = 2 * k + 1;
    }
    if(t != k)
    {
        swap(e[t],e[k]);
        down(t);
    }
}

void up(int k)
{
    while(k / 2 && e[k / 2] > e[k])
    {
        swap(e[k / 2] , e[k]);
        k = k / 2;
    }
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i = 1 ; i <= n ; i++)
    {
        scanf("%d",&e[i]);
    }
    for(int i = n / 2 ; i > 0 ; i--)
    {
        down(i);
    }
    for(int i = 1 ; i <= m ; i++)
    {
        printf("%d ",e[1]);
        e[1] = e[n];
        n--;
        down(1);
    }
    return 0;
}

需要注意的是:将一维数组e[]变为小根或者大根堆的话,down()操作下标的顺序需要注意:

for(int i = n / 2 ; i > 0 ; i--)

{

        down(i);

}的原因是,

如果先for(int i = 1 ; i <= n / 2 ; i ++)的话,

比如:

 而从for(i = n / 2 ; i > 0 ;i-- )进行down()

则每一次,都是使得当前这一层到最后一层都满足小根堆之后,再去使得这一层的上一层成立。如同dfs一般,最后一层满足小根堆之后,再去使得,最后一层 加 倒数第二层满足小根堆,  然后在使得最后一层,倒数第二层,倒数第三层满足小根堆。 一直到第一层,所有都满足小根堆。

 STL小根堆代码实现:

# include <iostream>
# include <queue>
using namespace std;
priority_queue<int,vector<int>,greater<int>> q;  //小根堆
int n,m;

int main()
{
    scanf("%d %d",&n,&m);
    for(int i =1 ; i <= n ; i++)
    {
        int temp;
        scanf("%d",&temp);
        q.push(temp);
    }
    while(m--)
    {
        printf("%d ",q.top());
        q.pop();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值