第三章栈和队列.docx
- 文档编号:10670429
- 上传时间:2023-02-22
- 格式:DOCX
- 页数:36
- 大小:50.61KB
第三章栈和队列.docx
《第三章栈和队列.docx》由会员分享,可在线阅读,更多相关《第三章栈和队列.docx(36页珍藏版)》请在冰豆网上搜索。
第三章栈和队列
第三章栈和队列
教学目的与要求
本章的目的是介绍栈和队列的逻辑结构定义及在两种结构上如何实现栈和队列的基本运算。
要求在掌握上和队列的特点的基础上,懂得在什么样的情况下能够使用栈和队列。
重点与难点
本章重点是掌握计算和队列的两种存储结构上实现的基本运算,难点是循环队列中对边界条件的处理。
教学内容
3.1栈
3.1.1栈的定义及其运算
(1)栈:
是限制仅在表的一端进行插入和删除运算的线性表。
允许进行插入和删除的这一端称为栈顶,另一端称为栈底。
当表中没有元素时称为空栈。
(2)栈的特点:
从定义不难看出栈具有后进先出(Lastinfirstout)的特点,简称为LIFO表。
(3)栈的基本运算:
Initstack(S)构造一个空栈。
Stackempty(S)判定栈是否为空,若为空返回true,否则,返回false。
Stackfull(S)判定栈是否已满,若已满返回true,否则,返回false。
该函数只适合于顺序栈。
Push(S,x)进栈操作。
若栈不满,则将元素x压入栈顶。
Pop(S)出栈操作。
若栈S非空,则删除栈顶元素并将该元素返回。
Stacktop(S)读取栈顶元素。
若栈S非空,则返回栈顶元素。
3.1.2顺序栈
1.栈的顺序表示与实现
由于栈是操作受限制的线性表,所以,线性表的存储结构对栈的也适合。
顺序栈类型定义如下:
#defineStacksize100//假定预分配的栈空间最多为100个元素
typedefchardatatype;//假定栈元素的数据类型为char
tefstruct{
datatypedata[Stacksize];//用一维向量data作为栈的存储空间
inttop;//top为栈顶指针
}Seqstack;
可以看出栈被定义为结构类型。
在实际应用中,可根据具体情况使用结构型变量或指向结构型变量的指针变量来表示栈。
2.栈操作中应注意的问题:
(设S是指向栈的指针)
①栈顶指针:
S->top;栈顶元素:
S->data[S->top];
②栈空的判定条件:
S->top==-1;栈满的判定条件:
S-top==Stacksize-1;
③栈中的元素个数:
S->top+1;
④若S是结构型变量则所涉及操作中的S->应改为S.;
3.栈的基本运算在顺序栈上的实现
(1)进栈操作(push(Seqstack*S,datatypex))
基本步骤:
若栈不满,则
栈顶指针加1:
S->top++;
元素入栈:
S->data[S-top]=x;
(2)出栈操作(pop(Seqstack*S))
基本步骤:
若栈非空,则
读取栈顶元素:
x=S->data[S->top];
栈顶指针减1:
S->top--;
返回栈顶元素:
return(x);
【分析】出栈操作和入栈操作是栈的两种重要操作,应注意操作的判定条件。
栈的有效使用空间是0到Stacksize-1,所以,当S->top==-1时,表示栈空;当S->top==Stacksize-1时,表示栈满。
时间复杂度均为O
(1)。
3.1.3链栈
1.链栈及类型定义
栈以链式存储方式存储所形成的存储结构,称为链栈。
就是单链表。
其数据类型定义如下:
typedefstructstacknode{
datatypedata;
structstacknode*next;
}Stacknode;
typedefstruct{
Stacknode*stop;
}Linkstack;
Linkstack*s;
2.链栈操作应注意的问题
与链表不同,链栈的插入和删除操作仅限定在表头位置进行,所以,链栈不需要附加头结点。
链表的头指针就是链栈的栈顶指针:
S->top;
栈顶元素为:
S->top->data。
空栈的判定条件是:
S->top==null.;
只要系统的备用空间能够分配出存储结点就不会发生上溢。
所以,程序员不必考虑链栈栈满的问题。
链栈的操作就是单链表的操作,只需注意栈本身的操作要求即可。
如:
进栈、出栈操作就是在表头进行插入和删除;求栈中的元素个数就是求链表中的结点个数。
3.1.4栈的应用举例
1.设计算法判断一个算术表达式的圆括号是否正确配对。
(提示:
凡遇‘(‘就进栈,遇‘)’就退掉栈顶的‘(‘,表达式扫描完毕,栈应为空)
算法
intbracketsmatch(chara[])
{//设算术表达式以字符串形式存储在数组中
Seqstack*S;
Initstack(S);
intk=0;
while(a[k]!
=’\0’)
if(a[k]=='(')Push(S,'(');
elseif(a[k]==')')
if(Stackempty(S))return0;
elsePop(S);
if(Stackempty(S))return1;
elsereturn0;
}
2.一个双向栈S是在同一向量空间里实现的两个栈,它们的栈底分别设在向量空间的两端。
试为此双向栈设计初始化Initstack(S)、入栈push(S,x,i)和出栈pop(i)算法,其中,i为0或1用于指示栈号。
算法
#definmaxsize100
typedefstructnode{
datatypedata[maxsize];
inttop1,top2;
}Seqstack;
(1)双向栈的初始化
viodInitstack(Seqstack*S)
{
S->top1=-1;S->top2=maxsize;
}
(2)双向栈入栈算法
viodpush(Seqstack*S,datatypex,inti)
{if(S->top1+1==S->top2)
erroe(overflow);
else
if(i==0)
{S->top1++;
S->data[S->top1]=x;}
else
{S->top2--;
S->data[S->top2]=x;}
}
(3)双向栈出栈算法
datatypepop(Seqstack*S,inti)
{if(i==0)
if(S->top1==-1)
error(downflow);
else
{returnS->data[S->top1]}
else
if(S->top2==maxsize)
error(downflow);
else
returnS->data[S->top2];
}
3.Ackerman函数的定义如下:
n+1当m=0时
AKM(m,n)=AKM(m-1,1)当m≠0,n=0时
AKM(m-1,AKM(m,n-1)) 当m≠0,n≠0时
请写出递归算法。
算法如下:
intakm(intn,intm)
{if(m==0)
returnn+1;
elseif(n==0&&m!
=0)
returnakm(m-1,1);
else
returnakm(m-1,akm(m,n-1));
}
3.2队列
3.2.1队列的定义及其基本运算
1.队列的概念
队列:
是一种操作受限的线性表。
它只允许在表的一端进行插入,在表的另一端进行删除。
允许删除的一端称为的队头(front),允许插入的一端称为队尾(rear)。
当队列中没有元素时称为空队列。
队列中包含的元素数称为队列的长度。
空队列的长度为0。
2.队列的特点:
从定义不难看出队列具有先进先出(firstinfirstout)的特点,简称为FIFO表。
3.队列的基本运算:
Initqueue(Q):
置空队。
构造一个供空队列;
Queueempty(Q):
判定队列Q是否为空,为空返回真值,否则返回价假值;
Queuefull(Q):
判定队列Q是否已满,若已满返回真值,否则返回价假值;
Enqueue(Q,x):
入对操作。
如果队列不满,则将元素x插入队列Q;
Dequeue(Q):
出队操作。
若队列非空,则删除队头元素并将其返回;
Queuefront(Q):
读取队头元素。
若队列非空,则返回队头元素。
3.2.2顺序队列
1.顺序队列与循环队列
队列的顺序存储结构称为顺序队列,顺序队列实际上是运算受限的顺序表。
由于队列的操作总是在表的两端进行,所以设置两个指针分别指向对头和队尾位置。
顺序队列存在的“假溢出”现象,使得被删除元素的存储空间永远无法被重新利用,所以顺序队列不能充分利用存储空间。
为解决假溢出现象,我们可以将向量空间想象为一个首尾相接的环形结构,并称这种向量为循环向量,存储在其中的队列称为循环队列。
通常,我们总是在循环队列上完成队列的操作。
2.循环队列的类型定义:
#defineQueuesize100
typedefchardatatype;
typedefstruct{
datatypedata[Queuesize];
intfront,rear;
intcount;
}Cirqueue;
这里我们将循环队列的存储结构定义为结构类型Cirqueue。
应用时,可根据具体需要将队列定义为结构体变量或指向结构的指针变量。
例如:
Cirqueue*Q,Q1;其中,Q为指针变量,Q1为结构体变量注意二者访问成员的方法不同。
3.循环队列应注意的几个问题
①队头指针和队尾指针在循环意义下加1操作的实现:
(向量下标的有效范围下界是0,上界是Queuesize-1)
Q->front=(Q->front+1)%Queuesize;
Q->rear=(Q->rear+1)%Queuesize;
②循环队列队满、队空的判定:
为了区别队满和队空,有三种方法可供采用,分别为:
方法1:
引入一个布尔变量以区别队列的空和满两种状态;
方法2:
采用少用一个元素的空间的策略,例如:
队头指针指向队头元素,队尾指针指向队尾元素下一个位置,则
队空的判定条件为:
Q-ront==Q->rear
队满的判定条件为:
Q->front==(Q->rear+1)%Queuesize1;
但方法3:
引入一个计数器记录队列元素的总数。
在众多的参考书中更多地采用的方法2,应对方法2给予足够的重视。
4.基本运算在循环队列上的实现(队满、队空的判定采用方法2)
(1)入队操作
voidEnqueue(Cirqueue*Q,datatypex){
if(Q->front==(Q->rear+1)%Queuesize)
error("Queueoverflow");
else
{Q->data[Q->rear]=x;
Q->rear=(Q->rear+1)%Queuesize;}
}
(2)出队操作
datatypeDequeue(Cirqueue*Q){
if(Q->front==Q->rear)
error("Queuedownflow");
else
{x=Q->data[Q->front];
Q->front=(Q->front+1)%Queuesize;
return(x);
}
}
【分析】入队、出队操作是队列的两种重要操作。
算法采用方法2区别队满和队空,应注意队满和队空的判定条件,以及入队和出队后队尾指针和队头指针在循环队列中的修改(即加1后进行取模运算)。
两算法的时间复杂度均为O
(1)。
3.2.3链队列
1.链队队列及类型定义
队列的链式存储结构简称为链队列。
其是限制仅在表头删除和表尾插入的单链表。
为了操作方便,一个链队列即设置头指针又设置尾指针。
链队列类型描述如下:
typedefstructqueuenode{
datatypedata;
structqueuenode*next;
}Queuenode;//链队列中的结点结构
typedefstruct{
Queuenode*front;//队头指针
Queuenode*rear;//队尾指针
}Linkqueue;//将队头指针、队尾指针封装在一个结构类型之中
2.链队列操作应注意的几个问题
①链队列可以定义为指针类型,也可以定义为结构类型。
例如:
Linkqueue*Q,Q1;其中,Q是指针型,Q1是结构类型。
应注意二者使用时的区别,作为函数参数,若为加工型操作,则选择指针型,作为引用型操作,则选择结构型。
②与单链表一样为了操作方便,链队列通常也带头结点。
教材中的上算法没有考虑头结点。
③链队列为空的判定条件:
当链队列不带头结点时为:
Q->front==NULL;当链队列带头结点时为:
Q->rear==Q->front。
3.队列的基本操作带头结点的链队列上的实现
(1)入队操作
C语言算法:
voidenqueue(Linkqueue*Q,datatypex)
{//将元素x插入队列Q(Q指针型)
Queuenode*p;
p=(Queuenode*)malloc(sizeof(Queuenode));//动态分配新结点
p->data=x;p->next=null;//构造新结点
Q->rear->next=p;
Q->rear=p;//完成插入操作
}
(2)出队操作
C语言算法
datatypeDequeue(Linkqueue*Q)
{//删除队头元素并将其值返回
datatypex;
Queuenode*p;
If(Q->front==Q->rear)
error("queuedownflow");//队列若为空,则出错返回
p=Q->front->next;//工作指针p指向队头元素
x=p->data;//保存队头元素值
Q->front->next=p->next;//
if(Q->rear==p)
Q->rear=Q->front;//删除队头元素后若队列有空,则修改队尾指针
free(p);//释放删除结点
return(x);//
}
插入和删除算法的时间复杂度均为O
(1)。
4.应用举例
1.算法阅读题
如图所示,利用同一循环向量空间实现两个队列,其类型Queue2定义如下:
(2003)
typedefstruct{
DataTypedata[MaxSize];
intfront[2],length[2];
}Queue2;
对于i=0或1,front[i]和length[i]分别为第i个队列的头指针和长度域。
请在空缺处填入合适的内容,实现第i个循环队列的入队操作。
intEnQueue(Queue2*Q,inti,DataTypex)
{//若第i个队列不满,则元素x入队列,并返回1,否则返回0
if(i<0||i>1)return0;
if(
(1))
return0;
Q->data[
(2)]=x;
Q->length[(3)]++;
return1;
}
【分析】利用同一循环向量空间实现两个循环队列,每个队列分别设置头指针和长度域。
以下标i为0或1区分两个队列。
若当前队列为第i个,则另一个可表示为(i+1)%2;要实现循环队列的循环机制,在入队、出队确定队头、队尾位置时应以向量长度为模取模,即出队时队头指针为:
front[i]=(front[i]+1)%MaxSize入队时队尾位置为:
(Q->front[i]+Q->length[i])%MaxSize。
算法功能是第i个队列的入队操作,所以算法中
(1)处为判断队列是否为满队列;
(2)处为第i个队列的队尾位置;(3)指定修改第i个队列的队长。
【答案】
(1)Q->front[(i+1)%2]==(Q->front[i]+Q->length[i])%MaxSize
(2)(Q->front[i]+Q->length[i])%MaxSize
(3)i
试题分析与习题
1.历届试题分析
一、选择题:
1.由两个栈共享一个向量空间的好处是( )(2001)
A.减少存取时间,降低下溢发生的机率 B.节省存储空间,降低上溢发生的机率
C.减少存取时间,降低上溢发生的机率 D.节省存储空间,降低下溢发生的机率
【分析】由两个栈共享一个向量空间,使得两个栈的剩余空间可共享,从而节省存储空间,降低上溢发生的机率。
【答案】B
2设数组data[m]作为循环队列SQ的存储空间,front为队头指针,rear为队尾指针,则执行出队操作后头指针的值为(2001)
A.front=front+1B.front=(front+1)%(m-1)
C.front=(front-1)%mD.front=(front+1)%m
【分析】该题的考核点是循环队列(同类型的题还可以是入队操作时,队尾指针的变化)。
模长(数组长度)为m,队头指针后移通过加1实现。
所以,正确答案为D。
【答案】D
3..判定“带头结点的链队列为空”的条件是( )(2003)
A.Q.front==NULL B.Q.rear==NULL
C.Q.front==Q.rear D.Q.front!
=Q.rear
【分析】带头结点的链队列为空,即链队列中只有一个头结点,这时头指针和尾指针均指向了头结点。
所以,正确答案为C。
【答案】C
4.若进栈序列为a,b,c,则通过入出栈操作可能得到的a,b,c的不同排列个数为( )(2002)
A.4 B.5 C.6 D.7
【分析】
若入栈先后次序为a,b,c,则不能得到c,a,b的出栈序列,可得到的序列为:
abc,acb,bac,bca,cba。
【答案】B
二、填空题
5.栈顶的位置是随着 操作而变化(2001)
【分析】该题考核点是栈的概念。
【答案】入栈和出栈。
6.假设以S和X分别表示进栈和退栈操作,则对输入序列a,b,c,d,e进行一系列栈操作SSXSXSSXXX之后,得到的输出序列为________。
(2002)
【分析】该题考核点是栈的特点:
先进后出。
在入栈序列确定的情况下,入栈、出栈操作的交替次序不同,将会得到不同的出栈序列。
【答案】BCEDA
7.在队列中,允许进行插入操作的一端称为____________,允许进行删除操作的一端称为____________。
(2003)
【分析】该题考核点是队列的概念。
【答案】队尾、队头
8.如图两个栈共享一个向量空间,top1和top分别为指向两个栈顶元素的指针,则“栈满”的判定条件是____________。
(2003)
【分析】两个栈共享一个向量空间,两个栈底分别处于向量的两端,而栈顶处于中间,随着入栈操作的进行,当两个栈顶指针相邻时为栈满。
【答案】top1+1==top2(或top1==top2-1)
三、算法阅读题
9.如图所示,利用同一循环向量空间实现两个队列,其类型Queue2定义如下:
(2003)
typedefstruct{
DataTypedata[MaxSize];
intfront[2],length[2];
}Queue2;
对于i=0或1,front[i]和length[i]分别为第i个队列的头指针和长度域。
请在空缺处填入合适的内容,实现第i个循环队列的入队操作。
intEnQueue(Queue2*Q,inti,DataTypex)
{//若第i个队列不满,则元素x入队列,并返回1,否则返回0
if(i<0||i>1)return0;
if(
(1) )
return0;
Q->data[
(2) ]=x;
Q->length[ (3) ]++;
return1;
}
【分析】利用同一循环向量空间实现两个循环队列,每个队列分别设置头指针和长度域。
以下标i为0或1区分两个队列。
若当前队列为第i个,则另一个可表示为(i+1)%2;要实现循环队列的循环机制,在入队、出队确定队头、队尾位置时应以向量长度为模取模,即出队时队头指针为:
front[i]=(front[i]+1)%MaxSize入队时队尾位置为:
(Q->front[i]+Q->length[i])%MaxSize。
算法功能是第i个队列的入队操作,所以算法中
(1)处为判断队列是否为满队列;
(2)处为第i个队列的队尾位置;(3)指定修改第i个队列的队长。
【答案】
(1)Q->front[(i+1)%2]==(Q->front[i]+Q->length[i])%MaxSize
(2)(Q->front[i]+Q->length[i])%MaxSize
(3)i
10.设栈S=(1,2,3,4,5,6,7),其中7为栈顶元素。
请写出调用algo(&s)后栈S的状态。
(2002)
voidalgo(Stack*S)
{
inti=0;
QueueQ;StackT;
InitQueue(&Q);InitStack(&T);
while(!
StackEmpty(S))
{
if((i=!
i)!
=0)Push(&T,Pop(&S));
elseEnQueue(&Q,Pop(&S));
}
while(!
QueueEmpty(Q))
Push(&S,DeQueue(&Q));
while(!
StackEmpty(T))
Push(&S,Pop(&T));
}
【分析】该题是栈和队列的综合应用题,其功能是通过工作栈T和队列Q对栈中内容重新排放。
算法中第一个循环是将栈S中元素依次出栈,并交替入栈T和入队Q(算法中判断条件(i=!
i)!
=0当i为0时成立,当i为1时不成立,i值交替为0和1)。
循环结束后T中值为:
(7,5,3,1),Q中值为:
(6,4,2);第二个循环是将队列Q中的元素依次出队并入栈S,循环结束后S中值为:
(6,4,2);第三个循环是将栈T中的元素依次出栈并入栈S,循环结束后S中值为:
(6,4,2,1,3,5,7)。
【答案】算法结束后S中值为:
(6,4,2,1,3,5,7)。
11.假设两个队列共享一个循环向量空间(参见右下图),其类型Queue2定义如下:
typedefstruct{
Datatypedata[maxsizi];
intfront[2],rear[2];
}Queue21;
对于i=0或1,front[i]和rear[i]分别为第i个队列的头指针和尾指针。
请对以下算法填空,实现第i个队列的入队操作。
(2001)
intEnqueue(Queue2*Q,inti,datatypex)
{//
if(i<0||i>1) return0;
if(Q->rear[i]==Q->fr
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第三 队列
![提示](https://static.bdocx.com/images/bang_tan.gif)