【动漫宅也要写代码 基础篇】某队列的超电磁炮

来源:互联网 发布:linux定时脚本怎么编辑 编辑:程序博客网 时间:2024/06/11 19:34

【队列】一种FIFO(先进先出)的线性表结构

萌新的个人整合,请各位大神勿喷(萌新抱腿) 这里写图片描述


【动漫世界观】

以下动漫背景请参照《某科学的超电磁炮(とある科学の超電磁砲)》
这里写图片描述
不要告诉我你作为动漫宅连炮姐都不知道(傲娇脸)


【问题背景】

学园都市一年一度(?)的超能力测试(体检)来了,为了防止发生混乱,超能力者们要在检测处排队进行超能力等级检测。

每次排在队伍最前面的人会被叫去检测,而新来的人只能排在队伍的最后。在排队过程中,不允许插队、交换位置

现在,御坂美琴(炮姐)、上条当麻(当妈)、白井黑子(百合黑子)、御坂妹(不知道是第几号)、一方通行(萝莉控)要来排队进行检测(不要问我为什么御坂妹和一方萝莉控也要来检测),请你模拟这一过程。
炮姐


【普通队列】

这不就是排队吗?要用什么数据结构呢?

顾名思义,排队排队,就是要用队列嘛。

队列,是一种线性表结构。它的主体是一个数组,第一个队内元素所在的位置叫做队首,而最后一个元素所在的位置叫做队尾。队列允许在队首队尾进行操作。

此外,队列还具有一个特殊而重要的性质FIFO(先进先出)

什么?你说你没听懂?那么下面我们就来实际模拟一下:

  • 1.【建队与初始化】 首先我们需要一个建立一个队列
#define MaxSize 10010int Queue[MaxSize], Head, Tail;

其中MaxSize队列的大小
Queue数组存的是队内的元素,编号从1MaxSize1
Head队首指针,它指向队首元素前的一项(就是说它储存队首元素的前一个位置的编号);
Tail队尾指针,它指向队尾元素(就是说它储存队尾元素的位置的编号)。

回到问题背景。一开始,队内是没有人的,那么队头指针与队尾指针都指向0号元素(就是没有)

Head = Tail = 0;

这样就初始化好了。如下图所示:
这里写图片描述 (Head、Tail指针指向检测处(0号))

  • 2.【入队】接下来有人要入队了!

这里写图片描述炮姐头像(Head指针指向检测站(0号)、Tail指针指向炮姐(1号))

那么这怎么在代码中实现呢?

void InQueue(int x) {    Queue[++Tail] = x;    return ;}

这就是入队函数x是要入队的元素(炮姐),将Tail++给炮姐腾出位置,并将炮姐放入新的队尾

484很简单?

  • 3.【出队】又有几个人进来了,现在排在第一的炮姐要被叫去检测了

这里写图片描述炮姐头像黑子头像当妈头像一方头像
(Head指针指向检测站(0号)、Tail指针指向一方(4号))
这里写图片描述空黑子头像当妈头像一方头像
(Haed指针指向空(1号)、Tail指针指向一方(4号))

代码实现依旧简单:

int OutQueue() {    return Queue[Head++];}

这就是出队函数,就是把Head指针往后移1位,使得队首元素(炮姐)不在队中。

  • 4.【判断队伍是否为空】那么现在在进行了几轮操作(出队与入队)后,检测处的工作人员想知道这时还有没有人在排队等候,请你帮助他们。

检测处空空当妈头像一方头像御坂妹头像
(Head指针指向空2(2号)、Tail指针指向御坂妹(5号),队列非空)

检测处空空空空空
(Head指针指向空5(5号)、Tail指针指向空5(5号),队列为空)

其实只要判断Head指针是否与Tail指针重合就好了:

bool Empty() {    return Head == Tail;}

5.【小结】现在知道什么是FIFO(先进先出)了吗?

因为在操作过程中,任何先入队的元素一定会先出队(因为不能插队),比如当妈就绝对不能比先来的炮姐早走(就算是CP也不能插队),所以这保证了入队与出队的有序性。

这里写图片描述


【循环队列】

你以为这样事情就结束了?图样图森破,上台拿衣服。

为了模拟排队,你开了一个如下的队列:

int Queue[20001], Head, Tail;

这时来了20000个御坂妹(别问我为什么会有20000个御坂妹,就当实验还没开始吧),她们不断进行着如下操作:

1号御坂入队,然后出队;
2号御坂入队,然后出队;
3号御坂入队,然后出队;
。。。
20000号御坂妹入队,然后出队。

检测处空空空这里写图片描述空
(Head指针指向空20000(20000号)、Tail指针指向空20000(20000号))

这时,我们可爱的20001号御坂“最后之作”来了,她也想来玩玩。
可是正当她想入队的时候,竟然SF(一般是数组越界)了!!

明明队里一个人都没有,怎么不能进入?御坂御坂不解地问道。

最后之作

原来,每次操作后,Head指针与Tail指针都会往后移1位,当第20000号御坂出队后,Head指针与Tail指针已经都指向了20000,也就是Queue数组的上限。这时再入队,肯定是会SF的。

现在令你限时解决这个问题,不然一方萝莉控将会对你进行严肃的制裁

其实这种因Head与Tail指针到达数组上界而无法入队的情况叫做假溢出。这时候要用循环队列解决。

什么是循环队列呢?就是在HeadTail超过MaxSize1后将其重新变为0。具体代码实现如下:

#define MaxSize 20001int Queue[MaxSize], Head, Tail, Size;void InQueue(int x) {    Tail = (Tail + 1) % MaxSize, ++Size;    Queue[Tail] = x;    return ;}int OutQueue() {    int Ret = Queue[Head];    Head = (Head + 1) % MaxSize, --Size;    return Ret;}bool Empty() {    return !Size;}

我们发现,在原有的变量的基础上,多了一个Size。因为头尾指针在循环后不好以是否相等判断队列长度。所以多了一个Size来记录队列长度

在入队与出队时,将原来的Head++Tail++改为了Head = (Head + 1) % MaxSize,Tail = (Tail + 1) % MaxSize,同时更新Size,这样就可以保证在HeadTail等于(MaxSize1)时,下一项是MaxSize % MaxSize=0。这样就像是把队列头尾连在了一起,所以叫循环队列

循环队列

这样问题就解决了。皆大欢喜!

这里写图片描述

【小结】循环队列是普通队列的一种改进,用于节省储存空间,建议在不知道操作次数多少时尽量使用循环队列。


【单调队列】

虽然排队的问题圆满解决绝了,但我们的任务还未结束。

  • 【问题】超能力等级测试后,炮姐和她的小伙伴们站成了一排,请问你能求出每段长度为k的区间内等级最高的是谁吗?

当妈头像一方头像御坂妹头像炮姐头像黑子头像
已知
上条当麻——Lev.0 把妹之手 幻想杀手;
一方通行——Lev.5 矢量操纵;
御坂妹妹——Lev.2 缺陷电力;
御坂美琴——Lev.5 超电磁炮;
白井黑子——Lev.4 瞬间移动。

  • 【题解】

这是一道经典的单调队列题目

顾名思义,单调队列可以保证队内元素时刻保持单调(单调递增、单调递减、单调非减、单调非增)

单调队列虽然叫“队列”,但其实和队列有很大差别:为了保证队内元素单调,会强行舍弃队中的一些元素。这点与FIFO的队列有很大不同,所以应该把单调队列看作是原数列的一个单调子序列

1.【建队与初始化】单调队列的建队与初始化与队列相同,但建议使用循环队列。在此不赘述。

2.【入队】单调队列的入队是与普通队列区别最大的一项,下以单调非增队列为例:

void InQueue(int x) {    int To = (Tail + 1) % MaxSize, ++Len;    while(Queue[Tail] < x && Len) {        To = (To - 1) % MaxSize;        --Len;    }    Queue[To] = x;    return ;}

入队时,将新入队的元素与队内的元素从后往前一一比较,直到找到第一个不比它小的元素,然后将新入队的元素插入该元素后,并将该元素后的所有原有元素舍弃。

回到原题,我们举k=2的情况。现在将1号当妈入队,因为队内为空,所以,将Tail指针指向当妈

当妈头像
(Head = 0,Tail = 1)

因为还没有到第1个长度为2的区间[1,2],所以接着讲一方入队。这时我们的当妈就被(欢乐地)挤了出去:
一方头像
(Head = 0, Tail = 1)

这时,到了第一个长度为2的区间[1,2],所以弹出队首(队列中最大的一项),就是该区间内最大的一项。所以区间[1,2]的答案为Lev.5。

3.【出队】出队比较简单,判断队首是否在需要操作的区间内,是就留下,不是就舍弃:

int OutQueue() {    if(Queue[Head]在区间内) reutrn 0;    int Ret = Queue[Head];    Head = (Head + 1) % MaxSize, --Len;    return Ret; }

再回到原题,现在要从[1,2]区间变成[2,3]区间,所以要将1号出队,3号入队。
但是我们发现队首是2号一方,2号在[2,3]区间内,所以不用出队
一方头像
(Head = 0, Tail = 1)

将3号御坂妹入队,显然挤不走一方:
一方头像御坂妹头像
(Head = 0, Tail = 2)

所以[2,3]区间内的答案还是队首一方,Lev.5。

接着将[2,3]变为[3,4],2号出队,4号入队。
我们发现队首是2号一方,不在[3,4]区间内,出队:
空御坂妹头像
(Head = 1, Tail = 2)

然后4号炮姐入队,将3号御坂妹挤走:
空炮姐头像
(Head = 1, Tail = 2)

所以[3,4]区间内的答案就是队首炮姐,Lev.5。

剩下以此类推。

4.【小结】
单调队列虽然没有普通队列的应用广泛,使用起来也容易与重复,但还是属于常见的数据结构。
而且单调队列虽然思想简单,操作起来还是有一定的难度的。(我就被卡了好久)
所以建议大家不妨多写几道题练练手:(以下题目来自洛谷)
http://www.luogu.org/problem/lists?name=&orderitem=pid&tag=56


【PS】 C++的STL里有一个叫“优先队列”的模板,虽然叫队列但实际功能更像是另外一个数据结构——,所以在此我就不讲了,放到以后堆的内容里。(虽然单调队列还算是优先队列的一种)

想了解的朋友可以自行百度。就连队列也有STL的模板哟。

这里写图片描述


OK,今天就讲到这里。撒哟那拉,米娜桑。

这里写图片描述

2 0