堆是一种数据结构,它是一种完全二叉树。堆分为最大堆和最小堆两种类型。最大堆的特点是任何一个父节点的值都大于它的子节点的值,最小堆则相反,任何一个父节点的值都小于它的子节点的值。
堆被广泛应用于排序算法,例如堆排序就采用了最大堆的性质。堆也经常用于优先队列,因为堆的特性可以很方便地获取最大或最小的元素。
下面我们就来用一道例题来观看堆的应用
输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。
输入格式
第一行包含整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。
数据范围
1≤m≤n≤10^5
1≤数列中元素≤10^9
1、暴力做法
如果用简单的做法,那么用先用一个冒泡排序来做就可以了
但是这样做法的话像上面给出的数据的话,那么也会超时
2、用之前学过的单调队列
3、今天,我们用一个小根堆来实现
首先,什么是小根堆呢
在一个数据结构中,储存类型是一棵树,它的父节点有两个儿子,左儿子和右儿子,父节点的数值大小一定比两个儿子的数值小,两个儿子的大小不定,它们的祖宗节点的数值是最小的,所以我们可以利用这个特点来完成我们今天这道题目
输入样例
5 3
4 5 1 3 2
输出样例
1 2 3
先给出具体代码再仔细分析
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int q[N];
int n,m,x;
void down(int u)
{
int t=u;
if(u*2<=x&&q[u*2]<q[t]) t=u*2;
if(u*2+1<=x&&q[u*2+1]<q[t]) t=u*2+1;
if(u!=t)
{
swap(q[u],q[t]);
down(t);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&q[i]);
x=n;
for(int i=n/2;i;i--) down(i);
while(m--)
{
printf("%d ",q[1]);
q[1]=q[x];
x--;
down(1);
}
return 0;
}
1、我们先开比数据多十个类型的数组,是为了防止数据溢出
2、down函数
想一下,如果我们取出了第一个最小的数,那我们怎么得到第二小的数值呢?我们先让该堆中的最后一个元素变为堆顶元素,然后让该元素和它的左儿子和右儿子比较大小,比较的顺序是任意的,如果堆顶元素比它的儿子数值更大,就交换,然后再让该元素和它的左,右儿子比较大小,直至不能比较为止
这时取得了堆顶元素之后,要把该元素删除掉,然后再和最后一个元素交换,再删除,这样就能得到第二小数值的数,重复m次,就能得到n个数中前m个小的数了
3、怎么得到一个小根堆
我认为从n/2开始,还有一个角度可以理解,因为n是最大值,n/2是n的父节点,因为n是最大,所以n/2是最大的有子节点的父节点,所以从n/2往前遍历,就可以把整个数组遍历一遍,这样也能减少时间复杂度
注意,在得到整个数组之后,让x=n,这样就是n就是最大值
最后欢迎大家来acwing学习