第四讲---队列
一、队列
队列简称队,一种操作受限的线性表。只允许在一端(队尾)插入,一端(队头)删除(出去)。也就是先进先出的线性表。空队列是指不含任何元素的空表。
举个栗子
上次我们讲到唐僧师徒五人春游时候掉进了一口井里,费了老大的劲几个人才出来。此时,唐僧以及半残了。他们几个准备找个村庄,让师傅休息一下。他们就一路上沿着一条小溪走,林尽水源,便得一山,山有小口,仿佛若有光..初极狭,才通人。(他们看到了一个洞洞)。心想洞洞后面一定有人家,于是乎...这帮人又开始钻洞了:
1.初极狭,才通人~不存在在洞里面,后面那个人把前面那个人超了,或者是并排走的情况。
2.八戒要想出去,必须唐僧先出去,然后猴哥出去,八戒才能出去。
(先进先出)
二、循环队列
传统队列的存储方式一般会造成很大的空间浪费,比如我们申请了50个空间,刚开始时候队头是0,队尾也是0,现在我们加入了40个人,队头是0,队尾是39。现在让着40个人全部出去,队头是39,队尾是39。因为加人是从队尾加的,队尾最大是49,所以只能在加10个人了。前面0~39这40个元素空间就全部浪费了。所以传统的队列一般是完全用不了的。
所以我们采用循环队列,使得队伍真的能达到其预先申请的最大人数。循环队列完全可以理解为一个钟表的形式。在最开始时候:
我们把整个表分成8个小格,理论上,我们最大可以存储8个个体,但是我们一般存7个(后面会讲到为什么)。
front为队头,指向排在第一个的人;rear为队尾,指向排在最后一个人的下一位(这是规定,这样的话后面一些操作会很方便)。
刚开始时候,队列的头front和尾rear都指向第0个格子,表示队里还没有人。
现在唐僧加进来以后,整个循环队列成了这个样子:
所有人都加进来以后:
我们可以再加几个人,方便说明队满时候的状态:
假设我们一共加了8个人,这个时候,可以看到rear和front又同时指向了0号位置,这个状态和我们之前队空时候是一摸一样的。那么如何去检测队列是否满了呢?你可以另外开辟一个变量,记录当前队列人数,通过检测这个变量是否和最大人数相等,来检测队列是否满了。但是我们一般不另外开辟,我们会用一个比较骚的操作去解决这个问题(身为一个程序员,骚是第一位)。我们一般让最大人数是MaxSize-1,这里就是7,这样的话:
我们只需要判断(rear+1)%MaxSize == front就好了,而且只有队满时候,返回值才会是true。
我这里就只讲判断是否队满的操作,插入删除时候指针变化可以看书上,讲的很清楚,也比较简单。和传统队列相比,无非就是增加了一个模运算。
三、循环队列的顺序存储结构
1.老规矩,先说下结构体:
#define MaxSize 50 //最大栈的容量(井里最多放50人)
typedef struct{
int data[MaxSize];
int front;//用来表示队头的位置
int rear;//用来表示队尾的位置
}SqQueue;//一直没有说这个,Sq表示的是线性表的顺序表示Sequence
2.InitQueue(&Q):初始化队列
void InitStack(SqQueue &Q){
Q.front = 0;
Q.rear = 0;
}
3.isEmpty(Q):判断是否为空
bool isEmpty(SqQueue Q){
if(Q.rear == Q.front)//队头等于队尾当然是队里面没人了
return true;
else
return false;
}
4.EnQueue(&Q,x):将x加入队列中
bool EnQueue(SqQueue &Q, int x){
//1.检查
if((Q.rear+1)%MaxSize == Q.front)//循环队列判断是否队满
return false;
//2.赋值
Q.data[Q.rear] = x;
//3.修改
Q.rear = (Q.rear+1)%MaxSize; //循环队列下递增的方式
//4.返回成功
return true;
}
5.DeQueue(&Q,&x):将队首元素出队并赋值给x
bool DeQueue(SqQueue &Q,int x){
//1.检查
if(Q.rear==Q.front){
return false;
}
//2.赋值
x = Q.data[Q.front];
//3.修改
Q.front = (Q.front+1)%MaxSize;
return true;
}
6.isFull(Q)检查是否队满:
bool isFull(SqQueue Q){
if((Q.rear+1)%MaxSize==Q.front){
return true;
}
else{
return false;
}
}
7.getSum(Q)
int getSum(SqQueue Q){
//这里面有个骚操作 + MaxSize
//这样可以保证%前面那一项始终为正
//因为我们有可能遇到Q.rear = 1,Q.front= 6的情况
//而 (a+b)%b == a%b
//这是取模运算里面的一个定理
return (Q.rear - Q.front + MaxSize)%MaxSize;
}
四、链式队列和双端队列
和链式栈一样,链式队列也不是考试的主要内容。所以不需要太多的了解,核心内容是头指针(front)指向队头结点,尾指针(rear)指向队尾结点(没有头结点,除非特殊说明)。
我们主要看双端队列。在循环队列中,只能在队头出队,队尾入队。但是双端队列就不一样了,其两端都可以出队。为了更好理解,我们就把双端队列的两端叫做前端和后端。双端队列一般细分为两种(两端都可以进出的队列一般不考,因为太简单了),一种是输出受限(俩插一删,输出受限,意思就是把出队的那一个操作限制了,出队的操作是删除嘛,所以少一个删除),一种是输入受限(俩删一插)。
书上对双端队列里面,前段进入的元素排列在队列中后端进的元素的前面....xxxx..xx...x。反正我是记不住这些东西~双端队列一般考的是选择题,队列元素一般不超过5个,我们完全可以一个一个试,就可以得到答案了。现在直接做一个题把: