第栈和队列3.docx
- 文档编号:7260905
- 上传时间:2023-01-22
- 格式:DOCX
- 页数:25
- 大小:125.53KB
第栈和队列3.docx
《第栈和队列3.docx》由会员分享,可在线阅读,更多相关《第栈和队列3.docx(25页珍藏版)》请在冰豆网上搜索。
第栈和队列3
第3章栈和队列
栈和队列的逻辑结构都是线性表,其特别点在于都对线性表的插入和删除点做了限制:
栈只能在线性表的表头(栈顶)插入和删除;队列则只能在线性表的表尾(队尾)插入和表头(队头)删除。
本章讨论栈和队列的逻辑结构、存储结构和相关运算的实现算法等。
3.1栈(Stack)
3.1.1栈的定义
图3.1栈的示意图
栈是限制为仅仅能在表的一端插入和删除的线性表,是生活中某些过程的抽象。
插入删除的的一端称为栈顶(Top)、插入操作通常称为进栈或者入栈(Push),也有形象地称之为压入。
不能插入删除的一端称为栈底(Bottom)、删除操作通常称为出栈或者退栈(Pop),也有形象地称之为弹出。
类似于线性表,没有元素的栈通称空栈。
依据栈的定义,栈顶的元素总是最后进栈的,并且是最先出栈的;栈底元素正好相反,最先进栈,最后出栈,因此,栈有着后进先出(LastInFirstOut—LIFO)的特性,也称为后进先出表。
软件设计中,栈广泛地应用于中断和函数调用时的现场保护,也用于编译程序中序列的转换和表达式的求值。
栈由于属于特殊的线性表,线性表的顺序存储结构和链式存储结构均可以用于栈的实现,通称顺序存储结构上实现的栈为顺序栈,链式存储结构上实现的栈为链栈。
栈的基本运算主要如下:
∙StackInitial(S):
初始化。
创建一个为空的栈S。
∙IsEmpty(S):
栈判空。
如果S为空则返回1,否则返回0。
∙Push(S,e):
进栈。
在栈S的顶部插入数据元素e。
∙Pop(S):
出栈。
如果栈S不为空,将栈顶元素删除,并且函数返回该元素的值。
∙GetTop(S):
取栈顶元素。
如果栈S不为空,函数返回栈顶元素的值。
∙MakeEmpty(S):
栈置空。
将栈S设置为空栈。
在每次出栈时需要判断栈是否为空,为空时,通称为下溢(Underflow);
由于顺序栈的空间大小在声明时已经严格限制,所以在进栈时需要判断栈是否已满,已满的栈再进栈则通称为上溢(Overflow),顺序栈的基本运算中相应地增加了一个运算:
∙IsFull(S):
栈判满。
如果S已满则返回1,否则返回0。
因为链栈的结点空间系动态分配,所以结束栈的操作后需要释放掉所有动态分配空间,所以链栈的基本运算中相应地也增加了一个运算:
∙Destroy(S):
销毁链栈―释放链栈S的所有结点空间。
3.1.2栈的顺序存储结构及其基本运算的实现
栈的顺序存储结构是利用一维数组来依次存放从栈底到栈顶的元素,由于栈限定为只能够在栈顶插入,因此可以只使用一个标志top来表示当前的栈顶位置。
为算法设计的方便,通常以数组下标小的一端作为栈底,下标大的一端作为栈顶,插入和删除元素时,直接修改top的值就可以完成了:
空栈时top的值为-1;进栈时top首先加1,元素再进栈;出栈时首先返回元素值,然后top再减去1;当top等于数组的最大下标时,栈就满了。
图3.2顺序栈的进栈、出栈过程示意
顺序栈的进栈与出栈的过程如图3.2所示。
以下是用C/C++语言描述的顺序栈的数据类型:
#defineMaxSize100/*最大元素个数*/
typedefstruct/*顺序栈的类型定义*/
{ElemTypedata[MaxSize];/*栈元素存储空间*/
inttop;/*栈顶指针*/
}SeqStack;
以下是顺序栈基本操作的算法实现。
(1)顺序栈的初始化
voidStackInitial(SeqStack*pS)
{/*创建一个由指针pS所指向的空顺序栈*/
pS->top=-1;
}
(2)顺序栈判空
intIsEmpty(SeqStack*pS)
{/*顺序栈为空时返回1,否则返回0*/
returnpS->top==-1;
}
(3)顺序栈判满
intIsFull(SeqStack*pS)
{/*栈为满时返回1,否则返回0*/
returnpS->top>=MaxSize-1;
}
(4)元素进栈
voidPush(SeqStack*pS,ElemTypee)
{/*若栈不满,则元素e进栈*/
if(IsFull(pS))/*栈已满,退出*/
{printf("栈满溢出!
\n");
exit
(1);
}
pS->data[++pS->top]=e;
}
(5)元素出栈
ElemTypePop(SeqStack*pS)
{/*若栈不为空,则删除栈顶元素,并返回它的值*/
if(IsEmpty(pS))/*栈为空,退出*/
{printf("空栈!
\n");
exit
(1);
}
returnpS->data[pS->top--];
}
(6)取栈顶元素值
ElemTypeGetTop(SeqStack*pS)
{/*若栈不为空,则返回栈顶元素的值*/
if(IsEmpty(pS))/*栈为空,退出*/
{printf("空栈!
\n");
exit
(1);
}
returnpS->data[pS->top];
}
(7)栈置空
voidMakeEmpty(SeqStack*pS)
{/*将由指针pS所指向的栈变为空栈*/
pS->top=-1;
}
3.1.3栈的链式存储结构及其基本运算的实现
栈的链式存储结构通常采用单链表表示,结点结构与单链表的一致,同样需要数据域和指针域。
由于栈只需要固定在表的某一个端点进行插入和删除操作,所以将链表的表头作为栈顶实现起来最为方便,而且没有表头结点操作起来更为简单。
但是为了与前面的带表头结点的链表统一处理,所以给其加上表头结点,所有初始化和判断条件需要作相应的更改。
图3.3链栈的进栈、出栈过程示意
链栈及其进栈与出栈的过程如图3.3所示。
以下是用C/C++语言描述的链栈的数据类型:
typedefstructstackNode/*链栈结点的类型定义*/
{ElemTypedata;/*数据域*/
structstackNode*next;/*指针域*/
}StackNode;
typedefstruct/*链栈的类型定义*/
{
StackNode*top;/*栈顶指针*/
}LinkStack;
以下是链栈基本操作算法的实现。
(1)链栈的初始化
voidStackInitial(LinkStack*pS)
{/*指针pS所指向的链栈初始化为有表头结点链表*/
StackNode*p;
p=(StackNode*)malloc(sizeof(StackNode));
if(p==NULL)/*内存分配失败,退出*/
{printf("内存分配失败!
\n");
exit
(1);
}
p->next=NULL;/*头结点指针域置空*/
pS->top=p;
}
(2)链栈判空
intIsEmpty(LinkStack*pS)
{/*链栈为空时返回1,否则返回0*/
returnpS->top->next==NULL;
}
(3)元素进栈
voidPush(LinkStack*pS,ElemTypee)
{/*将元素e插入到栈顶*/
StackNode*p;
p=(StackNode*)malloc(sizeof(StackNode));
if(p==NULL)/*内存分配失败*/
{printf("内存分配失败!
\n");
exit
(1);
}
p->data=e;
p->next=pS->top->next;/*栈顶插入*/
pS->top->next=p;
}
(4)元素出栈
ElemTypePop(LinkStack*pS)
{/*若栈不为空,则删除栈顶元素,并返回它的值*/
ElemTypetemp;
StackNode*p;
if(IsEmpty(pS))/*栈为空,退出*/
{printf("空栈!
\n");
exit
(1);
}
p=pS->top->next;
temp=p->data;/*保存栈顶结点数据*/
pS->top->next=p->next;/*栈顶指针后移*/
free(p);/*释放结点*/
returntemp;
}
(5)取栈顶元素值
ElemTypeGetTop(LinkStack*pS)
{/*若栈不为空,则返回栈顶元素的值*/
StackNode*p;
if(IsEmpty(pS))/*栈为空,退出*/
{printf("空栈!
\n");
exit
(1);
}
p=pS->top->next;
returnp->data;
}
(6)栈置空
voidMakeEmpty(LinkStack*pS)
{/*将链栈设置为空栈,仅保留头结点*/
StackNode*p,*q;/*p为当前结点指针*/
p=pS->top->next;/*从第一个结点开始*/
while(p!
=NULL)/*当没有到达栈底时*/
{q=p;/*暂存当前结点指针*/
p=p->next;/*当前结点指针后移*/
free(q);
}
pS->top->next=NULL;/*头结点指针域置空*/
}
(7)栈销毁
voidDestroy(LinkStack*pS)
{/*释放链栈所有结点的存储空间*/
StackNode*p,*q;
p=pS->top;/*从第一个结点开始*/
while(p!
=NULL)
{q=p;/*暂存当前结点指针*/
p=p->next;/*当前结点指针后移*/
free(q);
}
pS->top=NULL;/*栈顶指针置空*/
}
3.1.4栈的应用举例
栈的应用较多,以下是一些典型应用实例。
1.进制转换
编写算法将非负的十进制整数转换为其他进制的数输出,10及其以上的数字用从'A'开始的字母表示。
十进制整数转换其他进制整数方法通常采用的是“除以基数取余数”,依次对除以基数得到的商再次求余数得到的值,为待转换进制数的从低位到高位,当商为0时,转换完毕。
具体实现时采用栈暂时存放每次除得到的余数,当算法结束时(也就是商为0时),从栈顶到栈底就是转换后从高位到低位的数字。
对应的算法如下:
typedefcharElemType;
staticcharpszResult[100];/*用于存放转换后的进制的字符串*/
voidTransform(unsignedlongintnDecimal,unsignedshortintnBase)
{/*将nDecimal的十进制数转换为nBase(2≤nBase≤36,且10)进制数*/
SeqStackstack;
charch;
inti=0;
if(nBase<=1||nBase>36||nBase==10)/*待转换的进制不合适*/
{printf("待转换的进制错误!
\n");
exit
(1);
}
StackInitial(&stack);
while(nDecimal!
=0)
{ch=(char)(nDecimal%nBase);/*求余数并转换为字符*/
ch=ch+(ch<10?
48:
65–10);/*对转换得的字符做修正,
在0..9中间直接用'0'..'9'表示,48为'0'的ASCII码;
大于9则采用'A'..'Z'表示,65为'A'的ASCII码*/
Push(&stack,ch);/*进栈*/
nDecimal/=nBase;/*继续求更高位*/
}
while(!
IsEmpty(&stack))
pszResult[i++]=Pop(&stack);/*出栈时存放在数组中*/
pszResult[i]='\0';/*加入字符串结束标志*/
}
2.回文判断
回文指的是一个字符串从前面读和从后面读都一样,如:
"abcba"、"123454321",编写算法判断一个字符串是否为回文。
由于回文是从前到后以及从后到前都是一样的,所以只要将待判断的字符串颠倒,然后与原字符串相比较,就可以决定是否回文了。
将字符串从头到尾的各个字符依次放入一个栈中,由于栈的特点是后进先出,则从栈顶到栈底的各个字符,正好是字符串从尾到头的各个字符;
然后将字符串从头到尾的各个字符,依次与从栈顶到栈底的各个字符相比较,如果两者不相同,则表明该字符串不是回文,相同则继续比较;如果相互之间相匹配直到比较完毕,则说明该字符串是回文。
对应的算法如下:
typedefcharElemType;
intIsPalindrome(charpszText[])
{/*判断给定字符串pszText是否回文,是返回1,否则返回0*/
inti=0;/*字符串的下标*/
charch,temp;
SeqStackstack;
StackInitial(&stack);
while((ch=pszText[i++])!
='\0')/*所有字符依次全部进栈*/
Push(&stack,ch);
i=0;/*字符串下标复位*/
while(!
IsEmpty(&stack))/*栈不为空*/
{temp=Pop(&stack);/*栈顶字符*/
if(temp!
=pszText[i++])/*两者字符不相同*/
return0;
}
return1;
}
3.括号匹配
设一个算术表达式中间可以包括三种括号:
圆括号“(”和“)”、方括号“[”和“]”、以及花括号“{”和“}”,并且这三种括号可以按照任意的次序嵌套使用,但是不能相互交叉使用,如“(…[…)…]”就是错误的,“(…{…(…[…]…)…{…[…]…}…}…(…)…)”为正确的,设计算法判断给定表达式中所含括号是否正确配对。
在表达式中从左向右检查,三种括号可以按序依次对消的就是正确格式的表达式,否则不正确,例如:
([]())或[([{[]}]{})]格式正确
[(])或([()]或(()))格式不正确
表达式中间可能出现的不匹配的情况如下:
(1)到来的右括号与栈顶的左括号并不匹配
(2)栈为空时,到来右括号
(3)直到结束,也没等到所“期待”的括号
算法过程如下:
从左向右按字符扫描表达式直到结尾
(1)如果该字符是“左括号”则进栈;
(2)如果该字符是“右括号”,首先检查栈是否空:
①如果栈为空,则表明该“右括号”多余,返回;
②否则该字符与出栈的栈顶元素相比较:
如果两者相匹配对消,则继续执行否则表明不匹配,返回;
(3)其他字符继续执行;
(4)当表达式检验结束时,如果栈空,则表明表达式中匹配正确,返回正确值;否则,表明“左括号”有余,返回。
对应的算法如下:
typedefcharElemType;
enum
{/*枚举表达式中括号出现的所有情况*/
MATCH=1,/*括号完全匹配*/
NOT_MATCH=2,/*括号不匹配*/
LEFT_MORE=3,/*左边括号多*/
RIGHT_MORE=4,/*右边括号先出现*/
};
intIsPair(charcLeft,charcRight)
{/*判断右边括号与左边括号是否配对,是返回1,否则返回0*/
returncLeft=='('&&cRight==')'
||cLeft=='['&&cRight==']'
||cLeft=='{'&&cRight=='}';
}
intIsMatch(charpszExpression[])
{/*判断字符串pszExpression中存放的表达式的括号是否匹配*/
charch,temp;
inti=0;
SeqStackstack;
StackInitial(&stack);/*栈初始化*/
while((ch=pszExpression[i++])!
='\0')
{/*当字符串pszExpression没有扫描完*/
switch(ch)
{
case'(':
case'[':
case'{':
/*如果是左边括号则直接进栈*/
Push(&stack,ch);
break;
case')':
case']':
case'}':
/*右边括号*/
if(IsEmpty(&stack))/*栈空表示右边括号先来到*/
returnRIGHT_MORE;
temp=Pop(&stack),
if(!
IsPair(temp,ch))/*右括号与栈顶括号不匹配*/
returnNOT_MATCH;
break;/*两者匹配则继续向后扫描*/
default:
/*其它字符的处理部分*/
break;
}
}
/*字符串到这里已经处理完毕*/
if(!
IsEmpty(&stack))/*栈不空表示栈中还有左边括号未匹配*/
returnLEFT_MORE;
returnMATCH;/*匹配成功*/
}
3.2队列(Queue)
3.2.1队列的定义
队列是限制为仅仅能在表的一端插入和另一端删除的线性表,是生活中排队的抽象。
插入的一端称为队尾(Rear)、插入操作通称进队(Enqueue);
删除的一端称为队头(Front)、删除操作通称出队(Dequeue);
图3.4队列的示意图
类似于栈,没有元素的队列通称为空队列。
依据队列的定义,队头的元素,最先进队列,也是最先出队列;队尾的元素正好相反总是最后进队列的,并且前面的所有元素没有出队前,该元素是不可能出队的,因此,队列有着先进先出(FirstInFirstOut—FIFO)的特性,也称为先进先出表。
操作系统中大量使用队列来管理各类进程和缓冲输入输出的信息,在日常生活中的排队就是“先进先出”的完整写照。
队列由于同样属于特殊的线性表,类似于栈的实现,线性表的顺序存储结构和链式存储结构均可以用于队列的实现,通称顺序存储结构上实现的栈为顺序队列,链式存储结构上实现的队列为链式队列。
队列的基本运算主要如下:
∙QueueInitial(Q):
初始化―初始化队列Q为空。
∙IsEmpty(Q):
队列判空―如果Q为空则返回1,否则返回0。
∙EnQueue(Q,e):
进队列—在队列Q的尾部插入数据元素e。
∙DeQueue(Q):
出队列—如果队列Q不为空,将队头元素删除,并且函数返回该元素的值。
∙GetFront(Q):
取队头元素—如果队列Q不为空,函数返回队头元素的值。
∙MakeEmpty(Q):
队列置空—将队列Q设置为空栈。
在每次出队列时同样需要判断队列是否为空,为空时,通称为下溢(Underflow);
由于顺序队列的空间大小在声明时已经严格限制,所以在进队列时需要判断队列是否已满,已满的队列再进队则通称为上溢(Overflow),顺序队列的基本运算中相应地增加了一个运算:
∙IsFull(Q):
队列判满―如果Q已满则返回1,否则返回0。
因为链式队列的结点空间系动态分配,所以结束队列的操作后也需要释放掉所有动态分配空间,所以链式队列的基本运算中相应地也增加了一个运算:
∙Destroy(Q):
销毁链式队列―释放链式队列Q的所有结点空间。
3.2.2队列的顺序存储结构及其基本运算的实现
队列的顺序存储结构同样也是利用一维数组来依次存放从队尾到队头的元素,由于队列限定为只能够在队尾插入、队头删除,在插入删除元素如果不移动队列中的数据元素,就需要使用两个活动标志:
front来指示队列当前队头元素的位置、rear来指示队列当前队尾元素的位置。
为算法设计的方便,通常以数组下标小的一端作为队头,下标大的一端作为队尾,插入和删除元素时,直接修改front和rear的值就可以完成了,删除元素时从现象上看是队头追赶队尾。
为了队列操作的方便,通常约定队头标志front指向队头元素的前一个位置,队尾标志rear指向队尾元素,此时计算队列的长度、初始化操作等等可以做到统一。
图3.5顺序队列的进队、出队过程示意
初始时空队列的front和rear的值均为0;入队时rear首先加1,元素再进队;出队列时首先front加1,然后再返回元素值。
顺序队列的进队与出队的过程如图3.5所示:
这样的顺序队列随着插入和删除操作的进行,队头和队尾标志顺序向后移动,当元素被插入到数组中的最高位置上之后,队列的空间就用完了,如图3.5(4)。
如果此时再插入元素,虽然数组的低端还有许多空闲空间,但已经无法插入,这种现象通称为顺序队列的“假溢出”。
为解决顺序队列的“假溢出”,可以将存储队列的数组看作是首尾相连的循环结构,亦即将数组中最大下标的位置与最小下标相接,通过取余数操作即可很容易地实现,这种头尾相接顺序存储的队列通称为“循环队列”,如图3.6
(1)所示:
循环队列虽然解决了“假溢出”,随之又带来了新的问题:
队列为空和为满的判定问题:
图3.6循环队列的示意图
如图3.6
(2)所示,当队列中元素不停地出队,队头标志front追上队尾标志rear,此时队列为空,有front=rear;
另一种情况下,当队列中元素不停地入队,队尾标志rear追上队头标志front时,此时队列已满,此时同样有front=rear,如图3.6(3)所示,如何区分这两种情况?
解决循环队列的队满和队空状态判定问题的解决方法通常有三种:
方法1:
少用一个存储空间
front永远指向队头元素的前一个位置,队列中有一个元素空间不可用
队空判断条件:
rear==front
队满判断条件:
(rear+1)%MaxSize==front
方法2:
设置标
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 队列