数据结构番外篇【stl应用(1)】优先队列

stl是一种重要技巧,可以极大地简化编程过程
在总结stl之前,我们先简单介绍一下迭代器。
迭代器可以简单理解为地址的等价物。
在不同数据类型中迭代器支持的操作略有不同
官方文档中的定义
其中vector使用的是随机访问迭代器,其支持的操作可以参考上述表格
虽然本文用不上预备知识,但是还是先说一下吧
接下来介绍常用stl——优先队列

1.优先队列

优先队列(英文priority_queue)是一种维护集合最大最小值的数据结构(堆)
它能够在O(1)时间得到一个集合的最大(小)值,在O(logn)时间加入新的元素或者删除最大(小)的元素
常见操作如下:
优先队列默认为大根堆,即维护最大值

#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>
using namespace std;
priority_queue<int> q1;//默认大根堆
priority_queue<int,vector<int>,less<int> > q2;//等价的大根堆
priority_queue<int,vector<int>,greater<int> > q3;//小根堆
int main()
{
	int n;
	scanf("%d",&n);
	for(int i = 1; i <= n; i ++)
	{
		int x;scanf("%d",&x);
		q1.push(x);
		q2.push(x);
		q3.push(x);//插入新元素
	}
	while(!q1.empty())//检查是否非空
	{
		printf("%d\n",q1.top());//返回最大(小)值
		q1.pop();//删除最大(小)值
	}
	while(q3.size()>0)//返回元素个数
	{
		printf("%d\n",q3.top());
		q3.pop();
	}
}

优先队列的优化十分优秀,很多情况下甚至比手写heap还要快

例题

洛谷P3871 中位数
题目描述
给定一个由N个元素组成的整数序列,现在有两种操作:
1 add a
在该序列的最后添加一个整数a,组成长度为N + 1的整数序列
2 mid
输出当前序列的中位数
中位数是指将一个序列按照从小到大排序后处在中间位置的数。(若序列长度为偶数,则指处在中间位置的两个数中较小的那个)
例1:1 2 13 14 15 16 中位数为13
例2:1 3 5 7 10 11 17 中位数为7
例3:1 1 1 2 3 中位数为1
输入输出格式
输入格式:

第一行为初始序列长度N。第二行为N个整数,表示整数序列,数字之间用空格分隔。第三行为操作数M,即要进行M次操作。下面为M行,每行输入格式如题意所述。
输出格式:

对于每个mid操作输出中位数的值
https://www.luogu.org/problemnew/show/P3871
这道题有很多做法,在此给出简单且时间效率高的优先队列做法
将数组按照从小到大排序,将较小的一半放入一个大根堆,将较大的一半放入一个小根堆
每次添加数的时候与两个堆堆顶元素比较,将其放入相应的堆中
确保左堆的元素个数>=右堆的元素个数>=左堆元素个数-1
若打破了这个条件,只需将某堆中的堆顶元素换到另一个堆即可
每一次询问输出左堆堆顶

#include <iostream>
#include <algorithm>
#include <set>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
priority_queue <int,vector<int>,greater<int>> qright;
priority_queue <int,vector<int>,less<int>> qleft; 
vector <int> v;
int main() 
{
    int n,m;
    scanf("%d",&n);
    v.push_back(0);
    for(int i = 1; i <= n ;i++)
    {
        int x;
        scanf("%d",&x);
        v.push_back(x);
    }
    sort(v.begin()+1,v.end());
    for(int i = 1; i <= n/2; i ++)qleft.push(v[i]);
    for(int i = n/2+1; i <= n; i ++)qright.push(v[i]);
    scanf("%d",&m);
    while(m--)
    {
        char str[10];
        scanf("%s",str);
        if(str[0]=='m')
        {
            printf("%d\n",qleft.top());
        }
        else
        {
            int x;
            scanf("%d",&x);
            if(x<=qleft.top())
            {
                qleft.push(x);
            }
            else qright.push(x);
            int a = qleft.size(),b=qright.size();
            if(a-b>1)
            {
                qright.push(qleft.top());
                qleft.pop();
            }
            else if(b>a)
            {
                qleft.push(qright.top());
                qright.pop();
            }
        }
    }
}

https://www.luogu.org/problemnew/show/P1484
洛谷P1484 种树
题目描述
cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。
输入输出格式
输入格式:

第一行,两个正整数n,k。
第二行,n个正整数,第i个数表示在直线上从左往右数第i个坑种树的获利。
输出格式:

输出1个数,表示cyrcyr种树的最大获利。
这道题采用的思想是在一棵树被种之后,要改种左右两颗树获利等于左右两树之和减去这棵树,我们把这个获利再当成之前被种的树的获利放入堆中,并且标记左右两棵树无效。
最后k次取堆顶元素求和

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
#define N 500004
int f[N],l[N],r[N],n,k,cnt,tot;
long long ans;
bool vis[N<<2];
struct num
{
 int pos,val;
 bool operator < (const num b)const
 {
  return val<b.val;
 } 
}buf; 
priority_queue <num> heap;
int main()
{
 scanf("%d%d",&n,&k);
 for(int i = 1; i <= n; i ++)
 {
  scanf("%d",f+i);
  heap.push((num){i,f[i]});
  vis[i]=1;
  r[i]=i+1;
  l[i]=i-1;
 }
 for(int i = 1; i <= k ; i ++)
 {
  while(!heap.empty()&&!vis[heap.top().pos])heap.pop();
  if(heap.empty())break;
  buf=heap.top();
  heap.pop();
  vis[r[buf.pos]]=vis[l[buf.pos]]=0;
  if(buf.val<0)break;
  ans+=buf.val;
  heap.push((num){buf.pos,f[buf.pos]=f[r[buf.pos]]+f[l[buf.pos]]-buf.val});
  r[buf.pos]=r[r[buf.pos]];l[r[buf.pos]]=buf.pos;
  l[buf.pos]=l[l[buf.pos]];r[l[buf.pos]]=buf.pos;
  vis[buf.pos]=1;
 }
 cout<<ans;
} 
 

猜你喜欢

转载自blog.csdn.net/qq_37412789/article/details/84439564