对顶堆的理解与使用(直播获奖,中位数)

简介: 对顶堆的理解与使用(直播获奖,中位数)

1.前言


在今天的做题过程中遇见了这样一类问题:

动态维护n个数中的 第k大的数 / 第k小的数 / 中位数

一开始没有什么思路,然后尝试每加入一个数用一次sort然后输出所要求的的数,不出意外爆掉了,在补题的时候看到了对顶堆这种非常优秀的数据结构,所以 学完写一篇博客,持续更新;


2.实现原理


我们用STL中的 单调队列大根堆和小根堆来实现对顶堆;


对顶堆由一个小根堆和一个大根堆组成,维护小根堆堆顶为第 k 大的元素,大于它的放在小根堆,小于它的放入大根堆


priority_queue< int > maxp;//大根堆

priority_queue<int,vector< int >,greater< int > >minp;//小根堆


在小根堆里维护小根堆堆顶为 第 k 大个元素

(维护中位数是维护第 k 大个元素的一个特例,维护第 k 小个元素即是维护第(n-k)大个元素)


4d69715935d7fc16a7ac1d259b5bef05_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBALkFzaHku,size_20,color_FFFFFF,t_70,g_se,x_16.png


1.元素的插入:


当我们有一个元素之后,我们是选择放入大根堆还是放入小根堆呢,我们在这里把小根堆堆顶作为一个标志(后面的实现会给出用大根堆堆顶做标志的不方便之处),大于它的放入小根堆,否则放入大根堆,等于它的放在哪里都可,因为对后面维护没影响,这样插入就保证了整个对顶堆的数据整体的有序性,一开始把第一个元素作为标志元素,方便维护;


void push(int num)
{
  if(num>=minp.top())//小根堆堆顶做标志
  minp.push(num);
  else 
  maxp.push(num);
  trans();//每插入一个数据维护一次
}


2.元素的维护:


每次放入一个元素后我们都要做一次维护,维护很简单,因为整体元素已经有序,我们只要保证小根堆中的元素恰好为 k 个即可。少元素在大根堆堆顶取出,否则把自己堆顶元素放入大根堆


void trans()
{
  while(minp.size()<now)//now为k,即要维护的第k大,不够就去大根堆中取
  {
  minp.push(maxp.top());
  maxp.pop();
  } 
  while(minp.size()>now)//多了就往大根堆放
  {
  maxp.push(minp.top());
  minp.pop();
  }
}


3.例题


1.洛谷P7072 直播获奖

题目大意:

根据题中给出人数与分数线的关系动态维护第 k 大的元素;


priority_queue<int> maxp;
priority_queue<int,vector<int>,greater<int>>minp;
int n,w,now,num;
void trans()//维护
{
  while(minp.size()<now)
  {
  minp.push(maxp.top());
  maxp.pop();
  } 
  while(minp.size()>now)
  {
  maxp.push(minp.top());
  minp.pop();
  }
} 
void push(int num)//插入
{
  if(num>=minp.top())
  minp.push(num);
  else 
  maxp.push(num);
  trans();
}
int main()
{
  scanf("%d %d",&n,&w);
  scanf("%d",&num);
  minp.push(num);
  printf("%d ",minp.top());//把第一个元素放在小根堆堆顶做标志元素,同时根据题意,当只有一个元素时第一个元素一定是分数线
  for (int p=2;p<=n;p++)
  {
  now=max(1,p*w/100);//整除就是小数的向下取整,不会出现误差
  scanf("%d",&num);
  push(num);
  printf("%d ",minp.top()); 
  }
  return 0;
}



2.洛谷P3871 中位数

题目大意:

动态维护中位数

根据题意,无论我们要维护的数总数为奇还是为偶

我们要的 k 总为( n / 2 ) + 1;


priority_queue<ll>maxq;
priority_queue<ll,vector<ll>,greater<ll>>minq;
ll n;
ll num,now;
ll m,cnt;
string s; 
void trans()//维护
{
  while(minq.size()>now)
  {
  maxq.push(minq.top());
  minq.pop();
  }
  while(minq.size()<now)
  {
  minq.push(maxq.top());
  maxq.pop();
  }
}
void push(int num)//插入
{
  if(num>=minq.top())
  minq.push(num);
  else
  maxq.push(num);
  trans();
}
int main()
{
  cin>>n;
  scanf("%lld",&num);
  minq.push(num);// n 必然大于 0 ,因此我们把第一个数作为标志元素
  for(int i=2;i<=n;i++)
  {
  scanf("%lld",&num);
  now=i/2+1;
  push(num);
  }//把 n 个元素插入并维护
  cin>>m;
  for(int i=1;i<=m;i++)
  {
  cin>>s;
  if(s=="add")
  {
    cnt++;
    now=(cnt+n)/2+1;
    scanf("%lld",&num);
    push(num);//记录新插入元素的个数,并维护对顶堆
  }
  else
  {
    printf("%lld\n",minq.top());//维护的小根堆的堆顶即为中位数
  }
  }
  return 0; 
}



这里我们给出用大根堆堆顶作为标志的不方便之处

假如 n = 1;然后不插入直接查询,我们让大根堆堆顶作为标志的话,小根堆是空的最后还要特判,不如直接使用小根堆堆顶做插入标志方便,同时小根堆堆顶还是最后维护的值,比较好记;


4.拓展


当遇到不是动态维护第 k 大元素的时候,而仅仅是求一个无规则数列中的第 k 大的数时,我们可以借助STL中的 nth_element这个函数来实现


nth_element的作用是求第 k 小的元素,我们稍加改造就能求第 k 大的元素

它会使第 k 小的元素归位,使它前边的元素小于它,使它后面的元素大于它;

它的用法是

nth_element(a+x,a+x+y,a+x+len);


能使下标从 x 到 x+y-1 的元素小于 x+y 
          从 x+y+1 到 x+y+len 的元素大于 x+y;


时间复杂度只有 O(n),十分好用


例题链接 洛谷P1923 求第k小的数


本人是新手,有什么错误欢迎指出~


目录
相关文章
|
9月前
【每日一题Day361】LC2558从数量最多的堆取走礼物 | 大顶堆
【每日一题Day361】LC2558从数量最多的堆取走礼物 | 大顶堆
50 0
每日三题-乘积最大子数组、零钱兑换、戳气球
每日三题-乘积最大子数组、零钱兑换、戳气球
98 0
每日三题-乘积最大子数组、零钱兑换、戳气球
|
存储
L1-049 天梯赛座位分配 (20 分)( for循环的深入理解+三维数组+错误分析)
L1-049 天梯赛座位分配 (20 分)( for循环的深入理解+三维数组+错误分析)
191 0
|
9月前
面试题 08.13:堆箱子
面试题 08.13:堆箱子
78 0
字节跳动 区间最大值 牛客网
字节跳动 区间最大值 牛客网
94 0
字节跳动 区间最大值 牛客网
【八月】 每日一题 - 768. 最多能完成排序的块 I and II
【八月】 每日一题 - 768. 最多能完成排序的块 I and II
112 0
【八月】 每日一题 - 768. 最多能完成排序的块 I and II
|
机器学习/深度学习 算法
『牛客|每日一题』岛屿数量
基础算法无论在研究生面试还是求职面试都是十分重要的一环,这里推荐一款算法面试神器:牛客网-面试神器;算法题只有多刷勤刷才能保持思路与手感,大家赶紧行动起来吧(温馨提示:常见的面试问答题库也很nice哦 https://www.nowcoder.com/link/pc_csdncpt_ll_sf
137 0
『牛客|每日一题』岛屿数量
|
C语言
第十四周:*链表
第一遍没看明白也没关系,慢慢来,会慢慢看懂的。C语言的基础到这里你就都了解了,配套的课程移步去中国大学慕课看浙江大学翁恺老师的课程,那是梦的开始
117 0
|
算法 Java
网易2018校招内推编程题 堆棋子
小易将n个棋子摆放在一张无限大的棋盘上。第i个棋子放在第x[i]行y[i]列。同一个格子允许放置多个棋子。 每一次操作小易可以把一个棋子拿起并将其移动到原格子的上、下、左、右的任意一个格子中。小易想知 道要让棋盘上出现有一个格子中至少有i(1 ≤ i ≤ n)个棋子所需要的最少操作次数.
3471 0
|
算法 索引
代码随想录算法训练营第二天| 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,总结
代码随想录算法训练营第二天| 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,总结

热门文章

最新文章