数据结构:四 栈和队列

1. 栈的定义

栈(stack):是限定仅在表尾进行插入和删除操作的线性表

栈顶(top)允许插入和删除的一端称为栈顶;另一端称为栈底(bottom)

空栈:不包含任何数据元素的栈

栈又被称为后进先出(Last In First Out)的线性表,简称 LIFO 结构

栈的插入操作,叫做进栈,也称压栈,入栈

栈的删除操作,叫做出栈,也有的叫作弹栈

2. 栈的抽象数据类型

ADT 栈(stack)

Data
    同线性表。元素具有相同的类型,相邻元素具有前驱和后堆关系。

Operation
    InitStack ( *S ):初始化操作.建立一个空栈S。
    DestroyStack ( *S ):若栈存在,則销毁它。
    ClearStack (*S):将栈清空。
    StackEmpty ( S ):若栈为空,返回true,否則返回 false。
    GetTop (S,*e):若栈存在且非空,用e返回S的栈顶元素。
    Push (*S,e):若栈S存在,插入新元素e到栈S中并成为栈頂元素。
    Pop (*S,*e):删除栈S中栈顶元素,并用e返回其值。
    StackLength (S):返回回栈S的元素个数。

endADT

 

3. 栈的顺序存储结构及实现

定义一个 top 变量来指示栈顶元素在数组中的位置

进栈操作

  • 栈顶指针加一
  • 将新插入元素赋值给栈顶空间
  •  

出栈操作

  • 将要删除的栈顶元素赋值给 e
  • 栈顶指针减一

进栈和出栈都未涉及循环语句,时间复杂度为 O(1)

 

4. 两栈共享空间

数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为 0 处,另一个栈为栈的末端,即下标为数组长度 n-1 处

这样,两个栈如果增加元素,就使两端点向中间延伸

关键思路

  • 它们是在数组的两端,向中间靠拢
  • top1 和 top2 是栈 1 和栈 2 的栈顶指针

 

5. 栈的链式存储结构及实现

存储结构

  • 把栈顶放在单链表的头部,已经有了栈顶在头部了,单链表中常用的头结点就失去了意义
  • 通常对于链栈来说,是不需要头结点的
  • 对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是 top=NULL 的时候
  •  

进栈操作

  • 假设元素值为 e 的新结点是 s,top 为栈顶指针
  • 插入元素 e 为新的栈顶元素
  • 把当前的栈顶元素赋值给新结点的直接后继,如图中 1
  • 将新的结点 s 赋值给栈顶指针,如图中 2

出栈操作

  • 假设变量 p 用来存储删除的栈顶结点,将栈顶执政下移一位,最后释放 p 即可
  • 将栈顶结点赋值给 p ,如图 3
  • 使得栈顶指针下移一位,指向后一节点,如图 4
  • 释放结点 p

链栈的进栈 push 和出栈 pop,时间复杂度均为 O(1)

顺序栈,需先确定一个固定的长度,可能会存在内存空间浪费问题;优势是存取时定位方便

链栈,要求每个元素都有指针域,这同时也增加了一些内存开销,但对于栈的长度无限制

选择

  • 如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈
  • 反之,如果他的变化在可控范围内,建议使用顺序栈会更好一些

 

6. 栈的作用

栈的引用简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心

 

7. 栈的应用——递归

递归定义

  • 一个直接调用自己或通过一系列的调用语句间接地调用自己的函数

  • 每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出

  • 迭代和递归的区别

    • 迭代使用的时循环结构,不需要反复调用函数和占用额外的内存
    • 递归能使程序更清晰,更简洁,更易理解,从而减少读懂代码的时间
    • 大量的递归调用会建立函数的副本,会耗费大量的时间和内存

 

8. 栈的应用——四则运算表达式求值

后缀(逆波兰)表示法定义

  • 一种不需要括号的后缀表示法
  • 所有的符号都是在要运算数字的后面出现
  • ”9+(3-1)*3+10/2“ 的后缀表达式:”9 3 1 - 3 * + 10 2 / +“

后缀表达式计算结果

  • 后缀表达式:9 3 1 - 3 * + 10 2 / +

  • 规则

    • 从左到右遍历表达式的每个数字和符号,遇到数字就进栈;遇到符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果

中缀表达式转后缀表达式

  • ”9+(3-1)*3+10/2“,平时所用的这种标准四则运算表达式,叫做中缀表达式

  • 中缀转后缀规则

    • 从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;
    • 若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除优先于加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止
  • 中缀转化为后缀,栈用来进出运算的符号

  • 后缀转化为中缀,栈用来进出运算的数字

 

9. 队列的定义

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

队列是一种先进先出(First In First Out)的线性表,简称FIFO

允许插入的一端称为队尾,允许删除的一端称为队头

 

10. 队列的抽象数据类型

ADT 队列(Queue)

Data
    同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。

Operation
    InitQueue(*Q):初始化操作,建立一个空队列Q。
    DestroyQueue(*Q):若队列Q存在,則销毀它。
    ClearQueue(*Q):将队列 Q 清空。
    QueueEmpty(Q):若队列Q为空,送回true,否則退回false。
    GetHead(Q, *e):若队列Q存在且非空,用e返因队列Q的队头元素。
    EnQueue(*Q,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素。 
    DeQueue(*Q, *e):刪除队列Q中队头元素,并用e返回其值。    
    QueueLength(Q):送回队列Q的元素个教。

endADT

 

11. 循环队列

不足

  • 为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针
  • front 指针指向队头元素,rear 指针指向队尾元素的下一个位置
  • 当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列

定义

  • 我们把队列的头尾相接的顺序存储结构称为循环队列

问题:当 front 等于 rear 时,如何判断此时队列是空还是满?

  • 办法一

    • 设置一个标志变量 flag,当 front == rear,且 flag = 0时为队列空,当 front == rear,且 flag = 1 时为队列满
  • 办法二

    • 当队列孔时,条件就是 front = rear,当队列满时,我们修改其条件,保留一个元素空间。
    • 也就是说,队列满时,数组中还有一个空闲单元
    • 若队列的最大长度为 QueueSize ,队列满的条件是:(reat + 1) % QueueSize == front

 

12. 队列的链式存储结构及实现

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,简称链队列

为了操作上的方便,我们将队头指针指向链队列的头结点

入队操作

  • 在链表尾部插入结点
  • 把拥有元素 e 新结点 s 赋值给原队尾结点的后继,如图 1
  • 把当前的 s 设置为队尾结点,rear 指向 s,如图 2

出队操作

  • 出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点
  • 若链表除头结点外只剩一个元素时,则需将 rear 指向头结点

原文地址:https://www.cnblogs.com/dc2019/p/13663631.html