第四章 栈与队列
最后更新于:2022-04-01 23:02:06
### 一、栈的定义
栈(stack)是限定尽在表尾进行插入和删除操作的**线性表**。
我们把允许插入和删除的一端成为栈顶(top),另一端成为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(LIFO)的线性表。
图示出栈入栈操作:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368d9a96b.jpg)
### 二、栈的抽象数据类型
图示栈的各项操作:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368db5e12.jpg)
由于栈本身就是一个线性表,那么上一章我们讨论了线性表的顺序存储和链式存储,对于栈来说也是同样适用的。
### 三、栈的顺序存储结构及实现
来看一下栈的结构定义:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368de2c36.jpg)
若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize。当栈存在一个元素时,top等于0,因此常把空栈的判定条件定位top等于-1。
看一下示意图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368e0b1e2.jpg)
下面看一下push操作的代码:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368e223a2.jpg)
出栈pop,代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368e45bc8.jpg)
两者都没有涉及到任何循环语句,因此时间复杂度均为O(1)。
### 四、栈的链式存储结构及实现
栈的链式存储结构,简称链栈。
链栈的结构代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368e620fc.jpg)
进栈操作:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368e814b5.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368e98c67.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368eb30e9.jpg)
出栈操作:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368ecfdfb.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368ee270a.jpg)
上述操作也不包含循环,因此时间复杂度都是O(1)。
对于顺序栈和链栈的选择:如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好些。
### 五、栈的应用-递归
递归的最经典例子:(斐波那契额数列)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368f0b003.jpg)
代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368f1d59f.jpg)
递归的定义:我们把一个直接调用自己或通过一系列的调用语句间接地调用自己得函数,称为递归函数。
递归程序最可怕的就是陷入永不结束的无穷递归中,所以,每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
### 六、栈的应用-四则运算表达式求值
我们介绍一种不需要括号的后缀表达法,我们把它称为逆波兰(RPN)。
我们来看一个例子:对于运算:9+(3-1)*3+10/2,其逆波兰表达式为:9 3 1 - 3 * 10 2 / + 这种表达式是反人类的表达式,但是是顺计算机的,我们也就忍忍吧!
计算规则:从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到符号就将处于栈顶的两个数字出栈,进行计算,将计算结果再入栈,一直重复直到获取结果。
### 七、队列的定义
定义:队列是只允许在一段进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(FIFO)的线性表。
队列的抽象数据类型:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368f3b834.jpg)
循环队列的定义:我们把队列的这种头尾相连的顺序存储结构称为循环队列。
判断队列满的条件是:(rear+1)%QueueSize==front
计算队列长度的公式:(rear-front+QueueSize)%QueueSize
循环队列的顺序存储结构代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368f6790d.jpg)
循环队列的初始化代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368f84180.jpg)
循环队列求队列长度代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368fa21c1.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368fb9419.jpg)
循环队列的入队操作代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368fcd1c9.jpg)
循环队列的出队操作代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1368fe9bcd.jpg)
循环队列面临着数组可能会溢出的问题,我们还要研究一下不用担心队列长度的链式存储结构。
### 八、队列的链式存储结构及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c13690135b4.jpg)
链队列的结构为:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c136902847d.jpg)
队列链式存储结构--入队操作
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c136904c791.jpg)
代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c13690605ad.jpg)
队列链式存储结构--出队操作
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c136907d5aa.jpg)
代码如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-15_56c1369096b27.jpg)
总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,如果你无法预估队列的长度时,则用链队列。
### 九、总结回顾
栈是限定仅在表尾进行插入和删除操作的线性表。
队列是只允许在一段进行插入操作,而在另一端进行删除操作的线性表。
它们均可以使用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。
对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得队头和队尾可以在数组中循环变化。解决了移动数据的时间损耗,使得本来插入和删除是O(n)的时间复杂度编程了O(1)。
';