「标签」= “见人说人话见鬼说鬼话”
队列的类型定义
队列的操作与栈的类似,不同的是,
删除是在表的头部(即队头)进行
在此可回顾一下 栈与队列
-
基本操作
-
InitQueue(&Q)
构造空队列Q -
DestroyQueue(&Q)
销毁队列Q -
ClearQueue(&Q)
清空队列Q -
QueueEmpty(Q)
若Q为空队列,则返回true,否则返回false -
QueueLength(Q)
返回Q的元素个数,即队列的长度 -
GetHead(Q)
返回Q的队头元素 -
EnQueue(&Q,e)
插入元素e为Q的新的队尾元素 -
DeQueue(&Q,&e)
删除Q的队头元素,并用e返回其值 -
QueueTraverse(Q)
从队头到队尾,依次对Q的每个数据元素访问
队列的顺序存储结构
队列有两种存储表示,分别为
顺序表示
和链式表示
在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素外,还需附设两个整型变量front和rear分别指示队列头元素和队列尾元素的位置,即为头指针和尾指针。
#define MAXQSIZE 100 //队列可能达到的最大长度
typedef struct
{
QElemType *base; //存储空间的基地址
int front; //头指针
int rear; //尾指针
}SqQueue;
顺序分配的队列中头、尾指针和元素之间的关系:
- 初始化创建空队列时,令front = rear = 0 ,每当插入新的队列尾元素时,尾指针 rear 增加 1 ;每当删除队列头元素时,头指针 front 增加 1
- 非空队列中,头指针始终指向队列头元素,尾指针始终指向队列尾元素的下一个位置
循环队列
将顺序队列变成一个环状的空间即循环队列可巧妙的解决由“队尾入队,队头出队”这种受限制的操作造成的假溢出
(假溢出:因数组越界而导致程序的非法操作错误,但事实上,此时队列的实际可用空间并未占满)
示意图
循环队列中头、尾指针和元素之间的关系:
-
(a)中队头元素是 J5 ,在元素 J6 入队之前,在 Q.rear 的值是 5 ,当元素 J6 入队之后,通过“模”运算, Q.rear = (Q.rear + 1)% 6 ,得到 Q.raer 的值为 0 ,而不会出现“假溢出”状态。
-
(b)中, J7、J8、J9、J10 相继入队,则队列空间均被占满,此时头、尾指针相同。
-
(c)中,若 J5 和 J6 相继从(a)的队列中出列,使队列此时呈“空”的状态,头、尾指针的值也是相同的。
-
(d)中,当 J7、 J8、J9 进入队列后, (Q.rear + 1)%MAXQSIZE 的值等于 Q.rear ,此时认为队满。
-
循环队列不能以头、尾指针的值是否相同来判别队列空间是“满”还是“空”
区别队满还是队空
1:少用一个元素空间,即队列空间大小为m时,有m-1个元素就认为是队满。这样判断队空的条件不变,即当头、尾指针的值相同时,则认为队空;而当尾指针在循环意义上加 1 后等于头指针,则认为队满
。
队空的条件:Q.front == Q.rear
队满的条件:(Q.rear + 1) % MAXQSIZE == Q.front
2:另设一个标志位以区别队列是“空”还是“满”。
部分操作的实现
- 初始化
循环队列初始化是动态分配一个预定义大小为 MAXQSIZE 的数组空间
Status InitQueue(SqQueue &Q)
{ //构造空队列Q
Q.base=new QElemType[MAXQSIZE]; //为队列分配一个最大容量为 MAXQSIZE 的数组空间
if(!Q.base) exit(OVERFLOW); //存储分配失败
Q.front=Q.rear=0; //头指针和尾指针置为 0 ,队列为空
return OK;
}
- 求队列长度
非循环队列:尾指针和头指针的差值等于队长
循环队列:差值可能为负值,所以需要将差值加上 MAXQSIZE ,然后与 MAXQSIZE 求余
int QueueLength(SqQueue Q)
{ //返回Q的元素个数,即队列的长度
return(Q.rear-Q.front+MAXQSIZE) % MAXQSIZE;
}
- 入队
入队指在队尾插入一个新的元素
Status EnQueue(SqQueue &Q,QElemType e)
{ //插入元素e为Q的新的队尾元素
if((Q.rear + 1) % MAXQSIZE == Q.front) //尾指针在循环意义上加 1 后等于头指针,表明队满
return ERROR;
Q.base[Q.rear] = e; //新元素插入队尾
Q.rear = (Q.rear + 1) % MAXQSIZE; //队尾指针加 1
return OK;
}
- 出队
出队是将队头元素删除
Status DeQueue(SqQueue &Q,QElemType &e)
{ //删除Q的队头元素,用e返回其值
if(Q.front == Q.rear) return ERROR; //队空
e=Q.base[Q.front]; //保存队头元素
Q.front = (Q.front + 1) % MAXQSIZE; //队尾指针加 1
return OK;
}
- 取队头元素
队列非空时,返回当前队头元素的值,队头指针保持不变
SElemType GetHead(SqQueue Q)
{ //返回Q的队头元素,不修改队头指针
if(Q.front!=Q.rear) //队列非空
return Q.base[Q.front]; //返回队头元素的值,队头指针不变
}
- 若用户的应用程序中设有
循环队列
,则必须为它设定一个最大队列长度
;若用户无法预估所用队列的最大程度,则宜采用链队
链队
链队指采用链式存储结构实现的队列,
通常用单链表来表示
。
这里和线性表的单链表一样,为了操作方便,给链队添加一个头结点,并令头指针始终指向头结点。
在此可回顾一下 单链表
示意图
队列的链式存储结构
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct
{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
- 链表的操作即为单链表插入和删除操作的特殊情况,只需进一步修改尾指针或头指针
部分操作的实现
- 初始化
链队初始化是构造一个只有一个头结点的空队
Status InitQueue(LinkQueue &Q)
{ //构造空队列Q
Q.front = Q.raer = new QNode; //生成新结点作为头结点,队头和队尾指针指向此结点
Q.front->next = NULL ; //头结点的指针域置空
return OK;
}
- 入队
和循环队列的入栈操作不同的是,链队在入队前不需要判断队是否满,需要为入队元素动态分配一个结点空间
Status EnQueue(LinkQueue &Q,QElemType e)
{ //插入元素e为Q的新的队尾元素
p=new QNode; //为入队元素分配结点空间,用指针p指向
p->data=e; //将新结点数据域置为 e
p->next = NULL ; Q.rear->next=p; //将新结点插入到队尾
Q.rear=p; //修改队尾指针
return OK;
}
队列运算你指针变化情况:
- 出队
和循环队列一样,链队在出队前也需要判断队列是否为空,不同的是,链队在出队后需要释放出队元素的所占空间
Status DeQueue(LinkQueue &Q,QElemType &e)
{ //删除Q的队头元素,用e返回其值
if(Q.front==Q.rear) return ERROR; //若队列空,则返回ERROR
p=Q.front->next; //p指向队头元素
e=p->data; //e保存队头元素的值
Q.front->next=p->next; //修改头结点的指针域
if(Q.rear==p) Q.rear=Q.front; //最后一个元素被删,队尾指针指向头结点
delete p; //释放原队头元素的空间
return OK;
}
在链队出队操作时还要考虑当队列中最后一个元素被删后,队列尾指针也丢失了,因此需对队尾指针重新赋值(指向头结点)
- 取队头结点
与循环队列一样,当队列非空时,返回当前队头元素的值,队头指针保持不变
SElemType GetHead(LinkQueue Q)
{ //返回Q的队头元素,不修改队头指针
if(Q.front!=Q.rear) //队列非空
return Q.front->next->data; //返回队头元素的值,队头指针不变
}
例1:数制的转换
void conversion(int N)
{ //对于任意一个非负十进制数,打印输出与其等值的八进制数
InitStack(S); //初始化空栈S
while(N) //当N非零时,循环
{
Push(S,N % 8); //把 N 与 8 求余得到的八进制数压入栈S
N=N/8; //更新为 N 与 8 的商
}
while(!StackEmpty(S)) //当栈 S非空时,循环
{
Pop(S,e); //弹出栈顶元素e
cout<<e; //输出e
}
}
- 时间和空间复杂度均为O(log
8
n)
例2:括号的匹配
Status Matching()
{ //检验表达式中所含括号是否正确匹配,如果匹配,则返回true,否则返回false
//表达式以“#”结束
InitStack(S); //初始化空栈
flag=1; //标记匹配结果以控制循环及返回结果
cin>>ch; //读入第一个字符
while(ch!=‘#’&&flag) //假设表达式以“#”结尾
{
switch(ch)
{
case'[': //若是左括号,则将其压入栈
case'(':
Push(S,ch);
break;
case')': //若是“)”,则根据当前栈顶元素的值分情况考虑
if(!StackEmpty(S)&&GetTop(S)=='(')
Pop(S,x); //若栈非空且栈顶元素是“(”,则正确匹配
else flag=0; //若栈空或栈顶元素不是“(”,则错误匹配
break;
case']': //若是“]”,则根据当前栈顶元素的值分情况考虑
if(!StackEmpty(S)&&GetTop(S)=='[')
Pop(S,x); //若栈非空且栈顶元素是“[”,则正确匹配
else flag=0; //若栈空且栈顶元素不是“[”,则错误匹配
break;
} //switch
cin>>ch; //继续读入下一个字符
} //while
if(StackEmpty(S)&&flag) return true; //匹配成功
else return flase; //匹配失败
}
- 时间和空间复杂度均为O(n)
例3:舞伴问题
void DanceParter(Person dancer[],int num)
{ //结构数组dancer中存放跳舞的男女,num是跳舞的人数
InitQueue(Mdancers); //男士队列初始化
InitQueue(Fdancers); //女士队列初始化
for(i=0;i<num;i++) //依次将跳舞者根据其性别入队
{
p=dancer[i];
if(p.sex=='F') EnQueue(Fdancers,p); //插入女队
else EnQueue(Mdncers,p); //插入男队
}
cout<<"The dancing parters are:\n";
while(!QueueEmpty(Fdancers)&&!QueueEmpty(Mdancers))
{ //依次输出男女舞伴的姓名
DeQueue(Fdancers,p); //女士出队
cout<<p.name<<" "; //输出出队女士姓名
DeQueue(Mdancers,p); //男士出队
cout<<p.name<<endl; //输出出队男士姓名
}
if(!QueueEmpty(Fdancers)) //女士队列非空,输出队头女士的姓名
{
p=GetHead(Fdancers); //取女士队头
cout<<"The first woman to get a parter is: "<<p.name<<endl;
}
else if(!QueueEmpty(Mdancers)) //男士队列非空,输出队头男士的姓名
{
p=GetHead(Mdancers); // 取男士队头
cout<<"The first man to get a parter is: "<<p.name<<endl;
}
}
- 时间和空间复杂度均为O(n)