第3章 栈和队列

。_饼干妹妹 提交于 2020-02-05 09:01:48

3.1 栈的定义及抽象数据类型

栈(stack)是一种特殊的线性表,这种表只能在固定的一端进行插入与删除运算。通常称固定插入、删除的一端为栈顶(top),而另一端称为栈底(bottom)。位于栈顶和栈底的元素分别称为顶元和底元。当表中没有元素时,称为空栈。为了与一般的线性表相区别,通常将栈的插入操作称为入栈,将删除操作称为出栈。

线性表S=(a,b,c,d,e)

将S中的元素按照a、b、c、d、e的顺序依次入栈,则出栈的元素顺序为e、d、c、b、a 

可以发现,最先进入栈中的元素a最后才出栈,最后入栈的元素e最先被出栈,因为栈的这一特性,故又将其称为后进先出的线性表(lastinfirstout,简称为LIFO)。

3.2 栈的实现

栈的实现既可以采用顺序存储结构也可以采用链式存储结构。采用顺序存储结构实现的栈称为顺序栈,采用链式存储结构实现的栈称为链栈

3.2.1 顺序栈

栈的初始化操作是指按指定的大小为栈动态分配一片连续的存储区,并将该存储区的首地址同时送给栈顶指针top和栈底指针base,表示栈里没有任何元素,此时的栈为空栈。若base的值为NULL时则表明栈不存在。当插入一个新元素时栈顶指针加1,当删除一个元素时栈顶指针减1。栈顶指针top始终比顶元超前一个位置,因此栈满的条件是top-base=stacksize。图3-3所示为栈顶指针和栈中元素的关系。

值得注意的是,栈在运算过程中可能会发生“溢出”。溢出有两种情况,一种称为“上溢”(overflow),另一种称为“下溢”(underflow)。若系统作为栈用的存储区已满,还有元素要求进栈,则称发生上溢;反之,若系统作为栈用的存储区已空,这时还要退栈,则称发生下溢。上溢是一种错误现象,一旦发生上溢,就要给栈分配一个更大的存储空间,以避免有用信息的丢失。而下溢则常用来作为控制转移的条件或程序结束的标志。

3.2.2 链栈

链栈实质上是一个规定只能在表头进行插入与删除操作的单链表。链栈具有链式存储结构的优点,例如,可以提高结点的插入和删除的效率,可以根据实际需要为每个栈分配相应的单元等。

3.3 栈的应用举例

3.3.1 数制的转换问题

【问题描述】 要求编制一个程序实现下述功能:对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数。

【算法思想】 十进制转换为八进制的方法是除8取余。现以(1348)10=(2504)8为例进行分析,其转换过程如下。

3.3.2 括号匹配的检测

【问题描述】 假设一个算术表达式中允许含圆括号、方括号和花括号三种类型的括号,其嵌套的顺序随意,但是左右括号必须正确匹配。例如,“[( )]))”是不正确的格式。例如,“[( )]))”是不正确的格式。编写一个判别表达式中括号是否正确配对出现的函数,函数原型为

【算法思想】 该问题利用栈能很好地解决。具体算法为:从前向后扫描表达式exp中的每一个字符e,若e为左括号就将e进栈,若e为右括号就弹出栈顶元素,判断它们是否属于同一种括号,若是则继续扫描,否则返回不配对标识。当整个算术表达式扫描完毕时,若栈为空,则表示括号正确配对,否则表示不配对。

3.3.3 栈与递归

一般来说,每一个递归函数的定义都应包括以下两个基本要素。

(1)递归终止的条件,即对无需递归的最小问题的处理方法。

(2)将一般问题简化为一个或多个规模较小的相同性质的问题,递归地调用同样的方法求解这些问题,使这些问题最终到达递归终止。

一个递归函数的执行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。与函数递归调用相关的一个重要概念是递归执行的“层次”。假设将调用该递归函数的主函数所处的层次定义为0层,则从主函数调用递归函数为进入第1层,从第1层递归调用本函数为进入第2层,依此类推,直至从第i层递归调用本函数为进入第i+1层。反之,在退出第i层递归时,应返回到上一层,即第i-1层。为保证递归的正确执行,系统也如同处理函数嵌套调用那样,在系统工作栈中为递归函数开辟数据存储区。每一层递归所需的信息构成了一个工作记录,该工作记录记录了所有的实际参数和局部变量,以及返回到上一层的地址。每进入一层递归,就产生一个新的工作记录“进栈”,每退出一层递归,就从栈顶“退出”一个工作记录。这项工作一直进行到栈空为止,此时整个程序运行结束。

3.4 队列的定义及抽象数据类型

队列(queue)是另一种特殊的线性表。在这种表中,删除运算限定在表的一端进行,而插入运算则限定在表的另一端进行。约定把允许插入的一端称为队尾(rear),把允许删除的一端称为队首(front)。位于队首和队尾的元素分别称为队首元素和队尾元素。

 线性表S=(a,b,c,d,e)

将一列元素按照a,b,c,d,e的顺序依次插入到队列中,则从队列中取出这些元素的顺序仍然是a,b,c,d,e。可以发现队列的特点为,先进入队列的元素先出来,后进入队列的元素后出来,故又将其称为先进先出的线性表(firstinfirstout,简称为FIFO)

3.5 队列的实现

队列的实现既可以采用顺序存储结构,也可以采用链式存储结构。下面分别介绍队列的顺序存储结构实现——循环队列,以及链式存储结构实现——链队列

3.5.1 循环队列

队列的顺序存储结构和顺序栈类似,常借助一维数组来存储队列中的元素,同时设置两个指针front和rear分别指向队首元素和队尾元素的位置

由于一维数组定义时需要指定最大长度,因此顺序队列实现时常设置一个符号常量MAXQSIZE来表示队列的最大容量。初始时建立一个空队列(此时front=rear=0),每当插入一个元素时,队尾指针rear增加1;每当删除一个元素时,队首指针front增加1。因此,在一个非空的队列中,队首指针始终指向队首,而队尾始终指向队尾元素的下一个位置。图3-7所示为最大容量为6的顺序队列中,头、尾指针的变化情况。

其中,图(a)表示该队列的初始状态为空,此时rear=front=0;图(b)表示有3个元素a1、a2、a3相继进入队列,此时rear=3,front的值不变;图(c)表示a1、a2、a3先后出队,队列又变为空,此时rear=front=3;图(d)表示有3个元素a4、a5、a6进入队列,此时front=3,rear=6。倘若还有元素a7请求进入队列,由于队尾指针已经超出了队列的最后一个位置,因而插入a7就会发生“溢出”。此时,直接的解决方法是采用顺序栈的解决方法,这样可以扩大存储空间,但这种方法用在此处并不合理,因为这时的队列并非真的满了,事实上,队列中尚有3个空位。也就是说,系统作为队列用的存储空间并没有真正的用完,但队列却发生了溢出,这种现象称作虚溢出。解决虚溢出的方法通常有以下两种。

1)采用平移元素的方法

一旦发生虚溢出就把整个队列的元素平移到存储区的首部,将a4、a5、和a6平移到0号位置至2号位置,而将a7插到第3个位置上。显然,平移元素方法的效率是很低的。

2)将整个队列作为循环队列来处理

该方法的思想是将顺序队列想象成一个环形的空间。当发生虚溢出时,将待插入的元素插入到队首位置。这样,虽然物理上队尾在队首之前,但逻辑上队首仍然在前,作插入和删除运算时仍按“先进先出”的原则处理

但是使用该方法需要注意以下问题:根据等式rear=front=0,无法判断队空还是队满。图3-9(b)所示为元素a8和a9进入队列后的情形。此时队列已满,如果还要插入元素就会发生上溢。而它与图3-9(c)所示队列为空的情形一样,均有front=rear。由此可见,在循环队列中,根据等式rear=front无法判断队空还是队满。解决该问题的方法有:设置一个布尔变量来区分队空和队满;或者不设布尔变量,而把尾指针加1后等于头指针作为队满的标志,但此时会损失一个存储空间,也就是说必须用有maxlength+1个元素的数组才能表示一个长度为maxlength的循环队列。

3.5.2 链队列

队列也可以采用链式存储结构实现,队列的链式存储结构简称为链队列。链队列的实质是一个规定只能在表尾进行插入操作,以及在表头进行删除操作的单链表。通常链队列在实现上会设置两个指针,即队首指针front指向删除数据端,队尾指针rear指向插入数据端。链队列具有链式存储结构的优点,例如,可以提高结点插入和删除的效率,可以根据实际需要为每个队列分配相应单元等。

3.6 队列的应用举例

排队取餐,车辆调度

本章小结

1.栈是一种运算受到限制的特殊线性表,它仅允许在线性表的同一端作插入和删除操作。

2.队列也是一种运算受到限制的特殊线性表,它仅允许在线性表的一端进行插入操作,而在另一端进行删除操作。

3.队列的顺序存储一般采用循环队列形式,可以克服“虚溢出”的缺点,但最大的存储容量为循环队列容量减1。

4.队列的链式存储结构与单链表类似,但删除结点只能在表头,插入元素只能在表尾。

5.栈和队列的应用都很广泛。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!