队列 = =

理解:

先进先出(食堂打饭,先来的先打饭,就能先从队伍里出去了)

基本知识:

9个数全部放入队列后,初始化队列如下:

head = 1;

tail = 10; (队列中已经有9个元素了,tail指向队尾的后一个位置)

这样初始化的原因是:我们这里引入了两个变量head和tail。head用来记录队列的队首(即第一位),tail用来记录队列的队尾(即最后一位)的下一个位置。为啥不用tail不直接记录队尾,却要记录队尾的下一个位呢?这是因为当队列中只剩下一个元素时,队首和队尾重合会带来一些麻烦。所以我们规定队首和队尾重合时,队列为空。

此时head(1)和tail(10)之间的数就是目前队列中“有效”的数。要想删除一个数的话,head ++就OK了,这样仍然可以保持head和tail之间的数为目前队列中“有效”的数。这样做虽然浪费了一个空间,却节省了大量的时间(算法题就是拿空间换时间),这是非常划算的。

写在前面:

队列是一种特殊的线性结构,它只允许在队列的首部(head)进行删除操作,这成为“出队”;

在队列的尾部(tail)进行插入操作,这称为“入队”;

当队列中没有元素时(即head = tail),成为“空队列

操作:

(1)在队首删除一个数的操作:((出队)(dequeue)

head ++;

(2)在队尾增加一个数(x)的操作:((入队)(enqueue)

q[tail] = x ;
tail ++; 

用处:

是学习广度优先搜索以及队列优化的Bellman-Ford最短路算法的核心数据结构。所以现在将队列的三个基本元素(一个数组,两个变量)封装为一个结构体类型,如下:

struct queue
{
	int data[N]; // 队列的主体,用来存储内容
	int head; // 队首
	int tail; // 队尾 
}; 

基本操作代码:

#include<bits/stdc++.h>

using namespace std;

int main()
{
	int i;
    int q[102] = {0,6,3,1,7,5,8,9,2,4},head,tail;
	//初始化队列 
	head = 1;
	tail = 10; //队列中已经有9个元素了,tail指向队尾的后一个位置 
	while(head < tail) // 当队列不为空的时候执行循环
	{
		//打印队首并将队首出队
		cout << q[head] << ' ';
		head ++;
		
		//先将新队首的数添加到队尾
		q[tail] = q[head];
		tail ++;
		//再将队首出队
		head ++; 
	}
	getchar();
	getchar(); 
    return 0;
}

分类:

1.普通队列:

// hh 表示队头,tt表示队尾 
int q[N], hh = 0, tt = -1;

//向队尾插入一个数
q[ ++ tt] = x;

// 从队头弹出一个数
hh ++;

//队头的值
q[hh];

//判断队列是否为空
if(hh <= tt)
{
	not empty
} 

2.循环队列

//hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;

// 向队尾插入一个数
q[tt ++] = x;
if(tt == N) tt = 0;

// 从队头弹出一个数
hh ++;
if(hh == N) hh = 0;

//队头的值
q[hh];

//判断队列是否为空
if(hh != tt)
{
	not empty
} 

3.单调队列

常见模型:找出滑动窗口的最大值/最小值+

模板:

int i;
int hh = 0, tt = -1;
for(i = 0;i < n;i ++)
{
	while(hh <= tt && check_out(q[hh])) hh++; // 判断队头是否滑出窗口
	while(hh <= tt && check(q[tt], i))  t --;
	q[ ++ tt] = i; 
}

题目:

题目1:(普通队列纯裸题)

实现一个队列,队列初始为空,支持四种操作:

  1. push x – 向队尾插入一个数 x
  1. pop – 从队头弹出一个数;
  2. empty – 判断队列是否为空;
  3. query – 查询队头元素。

现在要对队列进行 M个操作,其中的每个操作 3 和操作 4都要输出相应的结果。

输入格式

第一行包含整数 M,表示操作次数。接下来 M行,每行包含一个操作命令,操作命令为 push xpopemptyquery 中的一种。

输出格式

对于每个 emptyquery 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YESNOquery 操作的查询结果为一个整数,表示队头元素的值。

数据范围

1≤M≤100000,1≤x≤10^9,
 

输入样例:

10
push 6
empty
query
pop
empty
push 3
push 4
pop
query
push 6

输出样例:

NO
6
YES
4

AC代码1:(数组模拟)

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int q[N];
int hh = 0,tt = -1;

int main()
{
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(false);
	
	int i;
	int m;
	cin >> m;
	
	while(m --)
	{
		string s;
		cin >>s;
		
        if(s == "push") 
		{
			int x;
			cin >> x;
			q[++ tt] = x;
		}
		
		if(s == "pop")  hh ++;
	    
		if(s == "empty")
		{
			if(hh <= tt)  cout << "NO" << endl;
			else  cout << "YES" << endl;
		}
		
		if(s == "query")  cout << q[hh] << endl;
	} 
	
	return 0;
}

AC代码2:(STL)

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

queue< int >q;

int hh = 0,tt = -1;

int main()
{
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(false);
	
	int i;
	int m;
	cin >> m;
	
	while(m --)
	{
		string s;
		cin >>s;
		
        if(s == "push") 
		{
		    int x;
		    cin >> x;
		    q.push(x); // push函数:在队尾插入元素 
		}
		
		if(s == "pop")  q.pop(); // pop函数:弹出队头元素 
	    
		if(s == "empty") 
		{
			if(q.empty())  cout << "YES" << endl; // empty函数:查询队列是否为空,空返回是,不空返回否
			else  cout << "NO" << endl;
		}
		
		if(s == "query")  cout << q.front() << endl; // front函数:查询队头元素 
	} 
	
	return 0;
}

题目2:(单调队列纯裸题)

题目描述:

给定一个大小为 n≤106的数组。有一个大小为 k的滑动窗口,它从数组的最左边移动到最右边。只能在窗口中看到 k个数字。每次滑动窗口向右移动一个位置。以下是一个例子:该数组为 [1 3 -1 -3 5 3 6 7],k为 3。

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

输入包含两行。第一行包含两个整数 n和 k,分别代表数组长度和滑动窗口的长度。第二行有 n数,代表数组的具体数值。同行数据之间用空格隔开。

输出格式

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:

8 3
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

AC代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int a[N],q[N];// a数组存数字   q数组存下标 

int main()
{
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(false);
	
	int i;
	int n,k;
	cin >> n >> k;
	for(i = 0 ; i < n ; i ++)  cin >> a[i];
	
	//输出每个区间的最小值 
	int hh = 0,tt = -1;
	
	//枚举每一个元素 
	for(i = 0 ; i < n ; i ++)
	{
		//判断队头是否已经滑出窗口
		if(hh <= tt && i - k + 1 > q[hh])  hh ++; // 队列不为空且当前区间的第一个数(i是当前区间最后一个数)的下标 > 队头,说明已经出队,所以队头要向后移动一位 
		
		while(hh <= tt && a[q[tt]] >= a[i]) tt--; // 队列不为空且新插入的数a[i]小于队尾,因为我们要输出最小值,那么队尾就没有用了,要把队尾删掉q数组队尾元素出队 
		
		q[++ tt] = i; // 把数a[i]的下标存到q数组里面去,此语句在if的上面的原因是,如果数a[i]很小,我们要保证输出当前的a[i];
		
		if(i >= k - 1)  cout << a[q[hh]] << ' '; //当前数足k个才输出,不足k个就不用输出了,这是题目要求 ,而且a[q数组]是单增的,输出队头就是当前区间的最小值 
	}
	cout << endl;
	
	//最大值同理,但是a[q数组要单减].
	int hh = 0,tt = -1; //一定要重置 
	
	//枚举每一个元素 
	for(i = 0 ; i < n ; i ++)
	{
		//判断队头是否已经滑出窗口
		if(hh <= tt && i - k + 1 > q[hh])  hh ++; 
		
		while(hh <= tt && a[q[tt]] <= a[i]) tt--; // 变号 
		
		q[++ tt] = i; 
		
		if(i >= k - 1)  cout << a[q[hh]] << ' ';
	}
	cout << endl;
	 
	
	return 0;
}

题目3:(单调队列+前缀和)

题目描述:

输入一个长度为n的整数序列,从中找出一段不超过m的连续子序列,使得整个序列的和最大。
例如 1,-3,5,1,-2,3
当m=4时,S=5+1-2+3=7
当m=2或m=3时,S=5+1=6

输入描述:

第一行两个数n,m(n,m≤300000)
第二行有n个数,要求在n个数找到最大子序和

输出描述:

一个数,数出他们的最大子序和

示例1:

输入:

6 4

1 -3 5 1 -2 3

输出:

7

考点:

单调队列


分析:
看到区间二字,我们首先想到前缀和数组,将原序列转化为前缀和数组后,我们要求的一段序列和,也就成了前缀和数组S[i]在[l,r]上的和,即s[r]-s[l-1];
也就是说,我们要在前缀和数组上找到两个点x,y,使得s[x]-s[y]最大,并且x-y<=m。
但又有一个问题,如果我们直接暴力枚举x,y,那必然超时,这个时候队列的作用就体现了,我们可以先固定右端点i,再去寻找一个左端点j,使得s[j]值最小,并且我们还可以确定j的范围[i-m,i-1],然后自己手捏数据找规律,我们可以发现 最优决策集合一定是一个下标位置递增,对应前缀和数组值也递增的序列。

AC代码:

#include<bits/stdc++.h>
 
using namespace std;
 
const int N = 3e5 + 10;
int q[N],s[N];

int main()
{
	int i;
    int n,m;
    int ans = 0;
    cin >> n >> m;
    for(i = 1 ; i <= n ; i ++)
    {
    	cin >> s[i];
    	s[i] = s[i] + s[i - 1]; // 前缀和 
	}
	int hh = 0,tt = 0;
	for(i = 1 ; i <= n ; i ++)
	{
		if(hh <= tt && i - m > q[hh])  hh ++; // 区间长度大于m,出队 
		ans = max(ans,s[i] - s[q[hh]]); // 寻找最优解
		while(hh <= tt && s[q[tt]] >= s[i])  tt --; // 若队尾不满足单调递增,出队
		q[++ tt] = i; // 把i当新的决策点 
	}
	cout << ans << endl; 
    return 0;
}

题目4:(队列)

题目描述:

题目描述:
有n个小组要排成一个队列,每个小组中有若干人。
当一个人来到队列时,如果队列中已经有了自己小组的成员,他就直接插队排在自己小组成员的后面,否则就站在队伍的最后面。
请你编写一个程序,模拟这种小组队列。


输入格式:
输入将包含一个或多个测试用例。
对于每个测试用例,第一行输入小组数量t。
接下来t行,每行输入一个小组描述,第一个数表示这个小组的人数,接下来的数表示这个小组的人的编号。
编号是0到999999范围内的整数。
一个小组最多可包含1000个人。
最后,命令列表如下。 有三种不同的命令:
1、ENQUEUE x - 将编号是x的人插入队列;
2、DEQUEUE - 让整个队列的第一个人出队;
3、STOP - 测试用例结束
每个命令占一行。
当输入用例t=0时,代表停止输入。
需注意:测试用例最多可包含200000(20万)个命令,因此小组队列的实现应该是高效的:
入队和出队都需要使用常数时间。


输出样例
对于每个测试用例,首先输出一行“Scenario #k”,其中k是测试用例的编号。
然后,对于每个DEQUEUE命令,输出出队的人的编号,每个编号占一行。
在每个测试用例(包括最后一个测试用例)输出完成后,输出一个空行。


数据范围
1≤t≤1000

样例1:

输入:

2

3 101 102 103

3 201 202 203

ENQUEUE 101

ENQUEUE 201

ENQUEUE 102

ENQUEUE 202

ENQUEUE 103

ENQUEUE 203

DEQUEUE

DEQUEUE

DEQUEUE

DEQUEUE

DEQUEUE

DEQUEUE

STOP

2

5 259001 259002 259003 259004 259005

6 260001 260002 260003 260004 260005 260006

ENQUEUE 259001

ENQUEUE 260001

ENQUEUE 259002

ENQUEUE 259003

ENQUEUE 259004

ENQUEUE 259005

DEQUEUE

DEQUEUE

ENQUEUE 260002

ENQUEUE 260003

DEQUEUE

DEQUEUE

DEQUEUE

DEQUEUE

STOP

0

输出:

Scenario #1

101

102

103

201

202

203

Scenario #2

259001

259002

259003

259004

259005

260001

AC代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 1005;

queue<int> q[N],tq;
map <int,int> team;

int main()
{
	int i,t;
    int k=0;
    while(cin >> t && t != 0)
    {
        cout << "Scenario #" << ++ k << endl;
        while(!tq.empty())  tq.pop();
        team.clear();
        for(i = 0 ; i < N ; i++)  while(!q[i].empty())  q[i].pop(); // //初始化
        for(i = 0 ; i < t ; i ++)
        {
            int n,x;
            cin>>n;
            while(n --)
            {
                scanf("%d",&x);
                team[x]=i;//使用map将同一组数据映射到同一个i,i代表这个数据的组别
            }
        }
        string s;
        while(cin>>s)
        {
            int v;
            if(s=="STOP")  break;
            if(s=="ENQUEUE")
            {
                scanf("%d",&v);
                int i = team[v];//查询这个元素的组别
                if(q[i].empty())   tq.push(i);//如果当前组内没有元素被enqueue,就在teamqueue中插入这个组
                q[i].push(v);
            }
            else
            {
                if(q[tq.front()].size()==1)
                {
                    cout << q[tq.front()].front() << endl;
                    q[tq.front()].pop();
                    tq.pop();//如果这个组中所有插入的元素都将要被dequeue,就在teamqueue中pop这个组
                }
                else
                {
                    cout << q[tq.front()].front() << endl;
                    q[tq.front()].pop();
                }
            }
        }
        cout << endl;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

21RGHLY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值