王道408数据结构——第三章 栈和队列

一、栈

栈(Stack)是只允许在一端进行插入或删除操作的线性表
栈顶:线性表允许插入删除的那一端
栈底:固定的、不允许进行插入删除的另一端
栈的操作特性可以概括为后进先出(LIFO)
n个不同的元素进栈,出栈元素不同的排列个数为C2nnn+1=1n!(2n)!n!1n+1=(2n)!n!(n+1)!\frac{C^n_{2n}}{n+1}=\frac{1}{n!}\frac{(2n)!}{n!}\frac{1}{n+1}=\frac{(2n)!}{n!(n+1)!}(卡特兰数)

顺序栈

采用顺序存储的栈称为顺序栈,利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置

共享栈

利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。

链栈

采用链式存储的栈称为链栈,优点是便于多个栈共享存储空间和提升效率,不存在栈满上溢的情况。通常采用单链表实现,规定所有操作在表头进行。

二、队列

队列(Queue)只允许在表的一段进行插入,而在表的另一端进行删除。
队头(Front):允许删除的一端。
队尾(Rear):允许插入的一端。
其特性概括为先进先出(FIFO)

队列的顺序储存

分配一块连续的存储单元存放队列中的元素,并设队头指针front(指向队头元素)和队尾指针rear(指向队尾元素下一个位置)

循环队列

将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列,当队首指针Q.front = MAXSIZE - 1后,再前进一个位置就自动到0,可以利用取余运算。
队头指针front指向队头元素,队尾指针rear指向队尾元素的下一个指针。

初始时:Q.front = Q.rear = 0
队首指针进一(出队):Q.front = (Q.front + 1) % MAXSIZE
队尾指针进一(入队):Q.rear = (Q.rear + 1) % MAXSIZE
队列长度:(Q.rear + MAXSIZE - Q.front) % MAXSIZE
队空:Q.front == Q.fear
队满:(Q.rear + 1) % MAXSIZE == Q.front

队列的链式存储

队列的链式表示称为链队列,实际上是一个同时带有队头指针和队尾指针的单链表。
用单链表表示的链式队列特别适合于数据元素变动较大的情形,而且不存在队列满产生溢出的问题。

双端队列

指允许两端都进行入队出队操作的队列,其元素的逻辑结构仍是线性结构。

  • 输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列。
  • 输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列

三、队列和栈的应用

括号匹配

算法如下
初始设置一个空栈,顺序读入括号:

  • 若是左括号,将其压入栈中;
  • 若是右括号,则将其与栈顶的括号进行匹配:
    • 若栈顶是类型相同的左括号,使其出栈
    • 若栈顶括号类型不匹配,则匹配不成功,退出程序

当括号序列读取完毕时,若栈空,则匹配成功,否则匹配不成功。

表达式求值

后缀表达式(逆波兰表达式)

后缀表达式的运算符在操作数的后面,在后缀表达式中已考虑了算符的优先级。后缀表达式只有操作数和算符,无需括号。
后缀表达式与表达式树的后序遍历序列相同。

算法如下
初始设置一个空栈,顺序读入后缀表达式的每一项,根据其类型决定操作:

  • 若为操作数:将其压入栈中
  • 若为运算符<op>:连续从栈中弹出两个操作数Y、X,进行X<op>Y运算,并将结果压入栈中

当表达式的所有项都处理完后,栈顶存放的就是最后的计算结果。

栈在递归中的应用

将递归算法转换为非递归算法,需要借助栈实现。
但对于单项递归和尾递归,可以用迭代的方式消除递归。
尾递归:程序中只有一句递归语句,且在末尾。
单向递归:指程序中的递归语句,在本程序操作执行前,都已经完成,如斐波那契数列。
这样一来,它们共同的特点是在化非递归时都没有非要保存的分支路线

队列在层次遍历中的应用

使用队列保存下一步的处理顺序。


王道408数据结构——第三章 栈和队列
https://buttering.github.io/EasyBlog/2021/09/25/王道408数据结构——第三章 栈和队列/
作者
Buttering
发布于
2021年9月25日
许可协议