C++基础数据结构——队列

队列中的数据存取方式是“先进先出”,只能像队尾插入数据,从队头移出数据。队列和栈的主要问题是查找较慢,需要从头到尾一个个的查找。在某些情况下可以用优先队列,让优先级最高(最大的数或者最小的数)先出队列。

竞赛中一般用STL queue或手写静态数组实现队列。本文只介绍STL queue。

队列的主要操作如下

#include<queue>//如果要使用队列的话 必须包含这个头文件
//主要操作如下:
queue<Type>q;//定义队列,Type为数据类型,如int,float,char等
q.push(item);//把item放进队列
q.front();//返回队首元素,但不会删除 
q.pop();//删除队首元素 
q.back();//返回队首元素 
q.size();//返回元素个数 
q.empty(); //检查队列是否为空 

 下面给出一道例题,用STL queue 实现:https://www.luogu.com.cn/problem/P1540

 

#include<iostream>
#include<queue>
using namespace std;
int Hash[1103]={0};
queue<int> mc;//队列模拟内存 
int main()
{
	int m,n;
	cin>>m>>n;
	int ans=0;
	while(n--)
	{
		int a; cin>>a;
		if(!Hash[a])//如果内存中没有这个单词 
		{
			ans++;
			mc.push(a);
			Hash[a]=1;
			while(mc.size()>m)//内存满了 
			{
				Hash[mc.front()]=0;
				mc.pop();
			}
		}
	}
	cout<<ans<<endl;
	return 0;
} 

下面来接受双端队列和单调队列 

双端队列是一种具有队列和栈性质的数据结构,它能在俩高端进行插入和删除,而且也只能在两端插入和删除。双端队列的用法如下:

#include<deque>//使用时必须包含这个头文件
deque<Type>dq//建立双端队列
dq[i]//返回下标为i的元素
dq.front();//返回队首元素 
dq.back();//返回队尾元素 
dq.pop_back();//弹出队尾元素 
dq.pop_front();//弹出队头元素 
dq.push_back();//在队尾插入一个元素 
dq.push_front(); //在队首插入一个元素 

双端队列的经典应用是单调队列。单调队列中的元素是单调有序的,且元素在队列中的顺序和原来在序列中的顺序是一致的。单调队列的复杂度是O(n)

单调队列与滑动窗口 :https://www.luogu.com.cn/problem/P1886

思路:本体暴力法的话代码很好写:从头到尾扫描,每次检查k个数字,一共检查O(nk)次。暴力法一定会超时。下面用单调队列求解,它的复杂度是O(n)

在本题中,单调队列有一下特征。

(1)队头的元素始终是队列中最小的。需要根据输出队头,但是不一定弹出。

(2)元素只能从队尾进入队列。从队尾和队头都可以弹出。

(3)序列中的每个元素都是必须进入队列。例如,x进入队尾时,和原队尾y比较,如果x<=y,就从队尾弹出y;一直弹出队尾所有比x大的元素,最后x进入队尾。这个入队操作保证了队头元素是队列中最小的。

#include<iostream>
#include<deque>
using namespace std;
const int N=1000005;
int a[N];
deque<int>q;//队列中的数据实际上是元素在原序列中的位置 
int main()
{
	int n,m; cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)//输出最小值 
	{
		while(!q.empty()&&a[q.back()]>a[i]) q.pop_back();//去尾 
		q.push_back(i);
		if(i>=m)//每个窗口输出一次 
		{
			while(!q.empty()&&q.front()<=i-m) q.pop_front();//删头 
			cout<<a[q.front()]<<" ";
		}
	}
	cout<<endl;
	while(!q.empty()) q.pop_front();
	for(int i=1;i<=n;i++)
	{
		while(!q.empty()&&a[q.back()]<a[i]) q.pop_back();
		q.push_back(i);
		if(i>=m)
		{
			while(!q.empty()&&q.front()<=i-m) q.pop_front();
			cout<<a[q.front()]<<" ";
		}
	}
	cout<<endl;
	return 0;
}

单调队列与最大子序和问题

什么是子序和?给定长度为n的整数序列A,它的“子序列”定义为A中非空的一段连续匀速。例如,序列(1,2,3,4,5),前4个元素的子序和为10.

最大子序和问题按照子序有无长度限制分为两种。

问题(1):不限制子序列长度。在所有可能的子序列中找到一个子序列,该子序列最大

问题(2):限制子序列的长度,给定一个限制长度为m,找出一段长度不超过m的连续子序列,使它的子序列最大。

问题一的求解:

方法一:贪心法。逐个扫描序列中的元素并累加。加一个正数时,子序和会增加;加一个负数时,子序和会减小。如果当前得到的和变成了负数,这个负数会在接下来的累加中会减小后面的求和,所以抛弃它,从下一位置求和。

#include<bits/stdc++.h>
using namespace std;
const int INF=0x7fffffff;
int main()
{
	int t; cin>>t;
	for(int i=1;i<=t;i++)
	{
		int n;cin>>n;
		int maxsum=-INF;
		int start=1,end=1,p=1;
		int sum=0;
		for(int j=1;j<=n;j++)
		{
			int a; cin>>a;
			sum+=a;
			if(sum>maxsum){maxsum=sum;start=p;end=j;}
			if(sum<0){
				sum=0;
				p=j+1;
			}
		}
			printf("Case %d:\n",i);printf("%d %d %d\n",maxsum,start,end);
			if(i!=t) cout<<endl;
	}
	return 0;
}

方法二:动态规划。定义状态dp[i],表示以a[i]结尾的最大自诩和。dp[i]的计算有两种情况:

1.dp[i]之包括一个元素,就是a[i];

2.dp[i]包括多个元素,从前面某个a[v]开始,v<i,到a[i]结束,即dp[i-1]+a[i]。去两者最大值

 

#include<bits/stdc++.h>
using namespace std;
int dp[10005];
int main()
{
	int t; cin>>t;
	for(int k=1;k<=t;k++)
	{
		int n;cin>>n;
		for(int i=1;i<=n;i++)cin>>dp[i];
		int start=1,end=1,p=1;
		int maxsum=dp[1];
		for(int i=2;i<=n;i++)
		{
			if(dp[i-1]+dp[i]>=dp[i])
			dp[i]=dp[i-1]+dp[i];
			else
			p=i;
			if(dp[i>maxsum])
			{
				maxsum=dp[i];start=p;end=i;
			}
		}
		printf("Case %d:\n",k);printf("%d %d %d\n",maxsum,start,end);
		if(k!=t) cout<<endl;
	}
	return 0;
}

 问题(2)的思路:

首先求前缀和s[i],s[I]是a[1]到a[i]的和。之后问题转化为找出两个位置i,k,使s[i]-s[k]最大,并且I-k<=m.如果暴力的去检查的话复杂度是O(mn),会超时。我们不妨用一个长度为m的滑动窗口即单调队列来维护。代码如下

#include<bits/stdc++.h>
using namespace std;
deque<int>dq;
int s[100005];
int main()
{
	int n,m; scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&s[i]);
	for(int i=1;i<=n;i++) s[i]=s[i-1]+s[i];
	int ans=-1e8;
	dq.push_back(0);
	for(int i=1;i<=n;i++) 
	{
		while(!dq.empty()&&dq.front()<i-m) dq.pop_front();
		if(dq.empty()) ans=max(ans,s[i]);
		else ans=max(ans,s[i]-s[dq.front()]);
		while(!dq.empty()&&s[dq.back()]>=s[i]) dq.pop_back();
		dq.push_back(i);
	}
	printf("%d",ans);
	return 0;
}

优先队列会结合堆来讲。 

 

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值