[入门必看]数据结构3.3:栈和队列的应用

[入门必看]数据结构3.3:栈和队列的应用

  • 第三章 栈、队列和数组
  • 3.3 栈和队列的应用
    • 知识总览
      • 3.3.1_栈在括号匹配中的应用
      • 3.3.2_1_栈在表达式求值中的应用(上)
      • 3.3.2_2_栈在表达式求值中的应用(下)
      • 3.3.3_栈在递归中的应用
      • 3.3.4+3.3.5_队列的应用
    • 3.3.1_栈在括号匹配中的应用
      • 算法实现
    • 3.3.2_1_栈在表达式求值中的应用(上)
      • 表达式
      • 中缀、后缀、前缀表达式
      • 中缀表达式转后缀表达式(手算)
      • 后缀表达式的计算(手算)
      • 后缀表达式的计算(机算)
      • 中缀表达式转前缀表达式(手算)
      • 前缀表达式的计算(机算)
    • 3.3.2_2_栈在表达式求值中的应用(下)
      • 中缀表达式转后缀表达式(机算)
        • 例1:
        • 例2(带有界限符):
        • 例3(练习):
      • 中缀表达式的计算(用栈实现)
        • 例:
    • 3.3.3_栈在递归中的应用
      • 函数调用背后的过程
      • 栈在递归中的应用
        • 例1
        • 例2
    • 3.3.4+3.3.5_队列的应用
      • 树的层次遍历
      • 图的广度优先遍历
      • 队列在操作系统中的应用
  • 知识回顾与重要考点
    • 3.3.1_栈在括号匹配中的应用
    • 3.3.2_1_栈在表达式求值中的应用(上)
    • 3.3.2_2_栈在表达式求值中的应用(下)
    • 3.3.3_栈在递归中的应用
    • 3.3.4+3.3.5_队列的应用


第三章 栈、队列和数组

小题考频:23
大题考频:4


3.3 栈和队列的应用

难度:☆☆☆

知识总览

3.3.1_栈在括号匹配中的应用

3.3.2_1_栈在表达式求值中的应用(上)

[入门必看]数据结构3.3:栈和队列的应用_第1张图片

3.3.2_2_栈在表达式求值中的应用(下)

[入门必看]数据结构3.3:栈和队列的应用_第2张图片

3.3.3_栈在递归中的应用

3.3.4+3.3.5_队列的应用


3.3.1_栈在括号匹配中的应用

[入门必看]数据结构3.3:栈和队列的应用_第3张图片

  • 括号都是成双成对出现的;
    左括号和右括号在数量上必须匹配,形状上也必须匹配。

[入门必看]数据结构3.3:栈和队列的应用_第4张图片

  • 最后出现的左括号最先被匹配(LIFO
    ——后进先出

将这些左括号依次压入栈中,最后入栈的左括号最先被弹出(匹配)

[入门必看]数据结构3.3:栈和队列的应用_第5张图片

每当出现一个右括号,就“消耗”一个左括号

  • 当我们遇到左括号时,就把它压入栈中,当我们遇到右括号时,就把栈顶的那个左括号给弹出,检查是否匹配。

Eg1.

[入门必看]数据结构3.3:栈和队列的应用_第6张图片

所有的左右括号都能匹配,匹配成功

Eg2.

[入门必看]数据结构3.3:栈和队列的应用_第7张图片

扫描的右括号与栈顶的左括号不匹配,匹配失败

Eg3.

[入门必看]数据结构3.3:栈和队列的应用_第8张图片

扫描到右括号但是栈已经空了——右括号单身,匹配失败

[入门必看]数据结构3.3:栈和队列的应用_第9张图片

处理完所有括号但是栈非空——右括号单身,匹配失败


算法实现

处理流程:

[入门必看]数据结构3.3:栈和队列的应用_第10张图片
代码实现:
[入门必看]数据结构3.3:栈和队列的应用_第11张图片

传入:
一个字符型数组 - 用于存放括号;数组长度
 
初始化一个栈
 
扫描:
如果是一个左括号,压入栈中;
如果是一个右括号,首先判断栈是否为空:

  • 栈空则失败;
    栈不空则栈顶元素出栈,并判断与所扫描元素是否匹配
     

检索完全部括号后,栈空说明匹配成功

  • 总结:
    用栈实现括号匹配
    依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。
     
    匹配失败情况:
    ①左括号单身②右括号单身③左右括号不匹配

3.3.2_1_栈在表达式求值中的应用(上)

[入门必看]数据结构3.3:栈和队列的应用_第12张图片


表达式

——大家熟悉的算数表达式

[入门必看]数据结构3.3:栈和队列的应用_第13张图片

由三个部分组成:操作数、运算符、界限符

界限符是必不可少的,反映了计算的先后顺序

Reference: Wikipedia
——Reverse Polish notation

一个灵感:可以不用界限符也能无歧义地表达运算顺序

Reverse Polish notation(逆波兰表达式=后缀表达式)
Polish notation(波兰表达式=前缀表达式)


中缀、后缀、前缀表达式

[入门必看]数据结构3.3:栈和队列的应用_第14张图片

后缀、前缀表达式的顺序中的操作数顺序不能变。
 
Eg.
中缀表达式:a + b - c,可以看做(a+b)-c
——用后缀表达式:(ab+)c- ,其中ab+看为整体;
当然!也能看做a + (b - c)
——用后缀表达式:a(bc-)+ ,其中bc-看为整体;
 
一个中缀表达式可能会转换成多种不一样的后缀、前缀表达式


中缀表达式转后缀表达式(手算)

中缀转后缀手算方法
①确定中缀表达式中各个运算符的运算顺序
②选择下一个运算符,按照「左操作数 -> 右操作数 -> 运算符」的方式组合成一个新的操作数
③如果还有运算符没被处理,就继续②
[入门必看]数据结构3.3:栈和队列的应用_第15张图片

  • 但是中缀表达式中的运算顺序不唯一,后缀表达式也不唯一。

遵守左优先原则:
[入门必看]数据结构3.3:栈和队列的应用_第16张图片

不遵守左优先原则:

[入门必看]数据结构3.3:栈和队列的应用_第17张图片

  • 遵守“左优先”原则,不要FreeStyle,保证手算和机算结果相同
  • “左优先”原则:只要左边的运算符能先计算,就优先算左边的
    ——后缀表达式中运算符出现的顺序中缀表达式中的运算顺序是一样的

客观来看两种都正确,只是“机算”结果是前者
算法的确定性:同样的输入只能得到同样的输出

Eg.

  • “左优先”原则:可保证运算顺序唯一

[入门必看]数据结构3.3:栈和队列的应用_第18张图片

可以让最左边的加法先生效。


后缀表达式的计算(手算)

[入门必看]数据结构3.3:栈和队列的应用_第19张图片
后缀表达式的手算方法:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数

注意:两个操作数的左右顺序

Eg1.
[入门必看]数据结构3.3:栈和队列的应用_第20张图片
Eg2.
[入门必看]数据结构3.3:栈和队列的应用_第21张图片

特点:最后出现的操作数先被运算
== LIFO(后进先出)==
!!!


后缀表达式的计算(机算)

用栈实现后缀表达式的计算:
①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①

Step1:
[入门必看]数据结构3.3:栈和队列的应用_第22张图片
Step2:
[入门必看]数据结构3.3:栈和队列的应用_第23张图片
Step3:
[入门必看]数据结构3.3:栈和队列的应用_第24张图片
Step4:
[入门必看]数据结构3.3:栈和队列的应用_第25张图片
Step5:
[入门必看]数据结构3.3:栈和队列的应用_第26张图片
Step6:
[入门必看]数据结构3.3:栈和队列的应用_第27张图片
Step7:
[入门必看]数据结构3.3:栈和队列的应用_第28张图片
Step8:
[入门必看]数据结构3.3:栈和队列的应用_第29张图片

注意:先出栈的是“右操作数”

[入门必看]数据结构3.3:栈和队列的应用_第30张图片

后缀表达式适用于基于栈的编程语言(stack-oriented programming language),如:Forth、PostScript

  • 思考:后缀表达式怎么转中缀表达式?

中缀表达式转前缀表达式(手算)

中缀转前缀手算方法
①确定中缀表达式中各个运算符的运算顺序
②选择下一个运算符,按照「运算符 -> 左操作数 -> 右操作数」的方式组合成一个新的操作数
③如果还有运算符没被处理,就继续②
“右优先”原则:只要右边的运算符能先计算,就优先算右边

Eg. 没有遵循右优先
[入门必看]数据结构3.3:栈和队列的应用_第31张图片
Eg. 遵循右优先原则
[入门必看]数据结构3.3:栈和队列的应用_第32张图片

练习:

中缀转后缀:“左优先”
[入门必看]数据结构3.3:栈和队列的应用_第33张图片
中缀转前缀:“右优先”
[入门必看]数据结构3.3:栈和队列的应用_第34张图片


前缀表达式的计算(机算)

用栈实现前缀表达式的计算:
从右往左扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①

[入门必看]数据结构3.3:栈和队列的应用_第35张图片

注意:先出栈的是“左操作数”


3.3.2_2_栈在表达式求值中的应用(下)

[入门必看]数据结构3.3:栈和队列的应用_第36张图片

在这里插入图片描述

中缀表达式转后缀表达式(机算)

初始化一个栈,用于保存暂时还不能确定运算顺序的运算符
从左到右处理各个元素,直到末尾。可能遇到三种情况:
①遇到操作数。直接加入后缀表达式。
②遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
③遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到“(”或栈空则停止。之后再把当前运算符入栈。

* /优先级高于+ -

按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。


例1:

Step1:
[入门必看]数据结构3.3:栈和队列的应用_第37张图片

操作数A,加入到后缀表达式中

Step2:
[入门必看]数据结构3.3:栈和队列的应用_第38张图片

运算符 + ,栈空,入栈

Step3:
[入门必看]数据结构3.3:栈和队列的应用_第39张图片

操作数B,加入到后缀表达式中

Step4:
[入门必看]数据结构3.3:栈和队列的应用_第40张图片

运算符 - ,栈非空,依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,即弹出 + 号,再入栈 - 号

扫描到 - 号时,可以确定,他和 + 号中间夹住的操作数B,既要进行加法运算,也要进行减法运算,由于他们的优先级是相等的,那么可以根据左优先原则,可以让操作数B,先执行左边的运算符 + 运算,即弹出 + 号,让加法先生效

Step5:
在这里插入图片描述

操作数C,加入后缀表达式

Step6:
[入门必看]数据结构3.3:栈和队列的应用_第41张图片

运算符 * ,此时栈顶元素是 - 法,优先级低于当前运算符,同时也不能直接运算乘法(如果后面有括号就不能确定能否先运行),先压入栈中

Step7:
[入门必看]数据结构3.3:栈和队列的应用_第42张图片

操作数D,加入后缀表达式

Step8:
[入门必看]数据结构3.3:栈和队列的应用_第43张图片

运算符 / ,此时栈顶元素是 * ,优先级等于当前运算符,弹出 * 加入后缀表达式;此时栈顶元素时 - ,优先级低于当前运算符,但 / 号无法判断,入栈 / 号

Step9:
[入门必看]数据结构3.3:栈和队列的应用_第44张图片

操作数E,加入后缀表达式

Step10:
[入门必看]数据结构3.3:栈和队列的应用_第45张图片

运算符 + ,此时栈顶元素是 / 号,优先级高于当前运算符,弹出 / 加入后缀表达式;此时栈顶元素是 - 号,优先级与当前运算符相等,弹出 - 号加入后缀表达式;入栈 + 号

Step11:
[入门必看]数据结构3.3:栈和队列的应用_第46张图片

操作数F,加入后缀表达式

Step12:
[入门必看]数据结构3.3:栈和队列的应用_第47张图片

处理完所有字符,将栈中剩余运算符依次弹出


例2(带有界限符):

Step1:
[入门必看]数据结构3.3:栈和队列的应用_第48张图片
Step2:
[入门必看]数据结构3.3:栈和队列的应用_第49张图片
Step3:
[入门必看]数据结构3.3:栈和队列的应用_第50张图片
Step4:
[入门必看]数据结构3.3:栈和队列的应用_第51张图片
Step5:
[入门必看]数据结构3.3:栈和队列的应用_第52张图片

界限符 - 左括号“ ( ”,直接入栈

遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。

Step6:
[入门必看]数据结构3.3:栈和队列的应用_第53张图片
Step7:
[入门必看]数据结构3.3:栈和队列的应用_第54张图片

运算符 - ,此时需要依次弹出栈中优先级更高或相等的所有运算符,碰到“ ( ”停止,入栈 - 号

碰到“ ( ”,即左边的操作数旁边有一个“ ( ”,此时不可以确定 - 号是否立即生效,确定不了运算顺序,压入栈中

Step8:
[入门必看]数据结构3.3:栈和队列的应用_第55张图片
Step9:
[入门必看]数据结构3.3:栈和队列的应用_第56张图片

界限符 - 右括号“ ) ”,依次弹出栈中的运算符,直到弹出“ ( ”为止
——左括号“ ( ”不加入后缀表达式
优先生效括号内的部分

Step10:
[入门必看]数据结构3.3:栈和队列的应用_第57张图片
Step11:
[入门必看]数据结构3.3:栈和队列的应用_第58张图片
Step12:
[入门必看]数据结构3.3:栈和队列的应用_第59张图片
Step13:
[入门必看]数据结构3.3:栈和队列的应用_第60张图片
Step14:
[入门必看]数据结构3.3:栈和队列的应用_第61张图片


例3(练习):

[入门必看]数据结构3.3:栈和队列的应用_第62张图片


中缀表达式的计算(用栈实现)

中缀转后缀 + 后缀表达式求值
——两个算法的结合

用栈实现中缀表达式的计算:
初始化两个栈,操作数栈运算符栈
若扫描到操作数,压入操作数栈
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈

  • 上节后缀表达式的计算(机算)中,
    ——栈用于存放当前暂时还不能确定运算次序的操作数
  • 本节中缀表达式转后缀表达式(计算)中,
    ——栈用于用于存放当前暂时还不能确定运算次序的运算符

两个算法结合,设置两个栈,即可实现中缀表达式的计算

例:

Step1:

[入门必看]数据结构3.3:栈和队列的应用_第63张图片

操作数A,入栈

Step2:
[入门必看]数据结构3.3:栈和队列的应用_第64张图片

运算符 + ,栈空,入栈

Step3:
[入门必看]数据结构3.3:栈和队列的应用_第65张图片

操作数B,入栈

Step4:
[入门必看]数据结构3.3:栈和队列的应用_第66张图片

运算符 - ,
① 栈顶 + 优先级相等,弹出 + 号,
② 并弹出两个栈顶操作数,
③ 进行运算后,压入操作数栈
类似“中缀转后缀”

Step5:
[入门必看]数据结构3.3:栈和队列的应用_第67张图片

操作数C ,入栈

Step6:
[入门必看]数据结构3.3:栈和队列的应用_第68张图片

运算符 * ,栈顶 - 优先级低于其,入栈 * 号

Step7:
[入门必看]数据结构3.3:栈和队列的应用_第69张图片

操作数D ,入栈

Step8:
[入门必看]数据结构3.3:栈和队列的应用_第70张图片

运算符 / ,栈顶 * 优先级相等,弹出 * ,弹出两个操作数,运算后压如操作数栈;
栈顶 - 低于其,入栈 / 号

Step9:
[入门必看]数据结构3.3:栈和队列的应用_第71张图片

操作数E ,入栈

Step10:
[入门必看]数据结构3.3:栈和队列的应用_第72张图片

运算符 + ,栈顶 / 优先级高于其,弹出 / ,弹出两个操作数,运算后压入操作数栈;
栈顶 - 优先级相等,弹出 - ,弹出两个操作数,运算后压入操作数栈

Step11:
[入门必看]数据结构3.3:栈和队列的应用_第73张图片

操作数F ,入栈

Step12:
[入门必看]数据结构3.3:栈和队列的应用_第74张图片

扫描完所有,将运算符栈剩余运算符弹出,并弹出对应的操作数进行计算

  • WHY?
    Q:搞这么复杂的意义是什么?
    A:将复杂的问题拆分成一个个最简单的加减乘除运算,拆分成让CPU能够执行的最基本的指令。把算数表达式编译成与其等价的机器指令时,就需要用到这种中缀表达式计算的思想和方法。

3.3.3_栈在递归中的应用

函数调用背后的过程

[入门必看]数据结构3.3:栈和队列的应用_第75张图片
函数调用的特点:最后被调用的函数最先执行结束(LIFO)- 栈

函数调用时,需要用一个栈存储:
①调用返回地址
②实参
③局部变量

系统会创建函数调用栈,用来保存各个函数在调用过程中必须保存的信息。
[入门必看]数据结构3.3:栈和队列的应用_第76张图片
比如:

  1. 运行main函数时,会把main函数中必要的信息压入栈中:局部变量a,b,c;
  2. main函数会调用func1函数,把func1执行结束之后,应该继续执行的代码的存储地址压入栈中(#1),函数调用的两个参数也会压入栈中(a,b),局部变量(x)也压入栈中;
    ——所以函数中修改a,b的值,修改的只是内存中func1的a,b的值,不会影响到main的a,b值
  3. func1函数会调用func2函数,记录func2执行结束之后我们应该回到哪一句继续执行,把该句代码的存储地址(#2)压入栈中,调用的实参(x)和局部变量(m,n)也存入栈中;
  4. func2执行结束后,由栈顶的信息得知,应该往后执行#2句代码,然后把和func2相关的代码弹出栈,释放空间;
    同样的,func1执行结束后,由栈顶信息得知,应该往后执行#1句代码,将func1相关信息删除,接着#1句代码继续执行。

其实在main函数之前,还需要把某些不知道的信息压入栈底。

IDE中:
[入门必看]数据结构3.3:栈和队列的应用_第77张图片


栈在递归中的应用

适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题

例1

计算正整数的阶乘n!
[入门必看]数据结构3.3:栈和队列的应用_第78张图片

把n!转换成n*(n-1)!,问题规模变小了,且属性相同

[入门必看]数据结构3.3:栈和队列的应用_第79张图片
递归调用时,函数调用栈可称为“递归工作栈”
每进入一层递归,就将递归调用所需信息压入栈顶
每退出一层递归,就从栈顶弹出相应信息

在这里插入图片描述

问题规模逐渐收敛,直到n = 1,开始逐层返回到第一层的调用n = 10,那么factorial(n - 1)的调用返回值应该是9!,最后一层,再乘上n = 10,即10!返回给main函数的192行代码,将factorial(10)的值赋给x

缺点:太多层递归可能会导致栈溢出,内存资源是有限的

IDE中:
[入门必看]数据结构3.3:栈和队列的应用_第80张图片


例2

求斐波那契数列
[入门必看]数据结构3.3:栈和队列的应用_第81张图片
[入门必看]数据结构3.3:栈和队列的应用_第82张图片

[入门必看]数据结构3.3:栈和队列的应用_第83张图片

传入4,在196行调用Fib3和Fib2;
首先调用Fib3(第2层#196,n=3),进入更深层递归,在196行调用Fib2和Fib1;
首先调用Fib2(第3层#196,n=2),进入更深层递归,在196行调用Fib1和Fib0;
首先调用Fib1(第4层#196,n=1),在194行return 1,弹出栈顶元素(第4层#196,n=1);
在Fib2层还需要调用Fib0(第4层#196,n=0),在192行return 0,弹出栈顶元素(第4层#196,n=0),Fib2相关的调用都有返回值了,Fib2可以向上一层返回(第3层#196,n=2);
……
……

[入门必看]数据结构3.3:栈和队列的应用_第84张图片

Fib(2),Fib(1),Fib(0)被重复了多次,效率低

总结:

函数调用的特点:最后被调用的函数最先执行结束(LIFO)

函数调用时,需要用一个“函数调用栈”存储:
①调用返回地址
②实参
③局部变量

递归调用时,函数调用栈可称为“递归工作栈”
每进入一层递归,就将递归调用所需信息压入栈顶
每退出一层递归,就从栈顶弹出相应信息

缺点:效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算

可以自定义栈将递归算法改造成非递归算法


3.3.4+3.3.5_队列的应用

树的层次遍历

[入门必看]数据结构3.3:栈和队列的应用_第85张图片

注:在“树”章节中会详细学习

树是分层的,层次遍历:即一层一层的遍历,就需要队列的辅助。

新建一个队列,从根节点出发按层次遍历各个结点

Step1:遍历1号结点时,把1号结点的左右两个子节点2、3,都放到队列的队尾;

①->②->③

Step2:遍历完1号结点,出队;

②->③

Step3:遍历队头2号结点,把2号结点的左右两个子节点4、5,加入队尾;

②->③->④->⑤

Step4:遍历完2号结点,出队;

③->④->⑤

Step5:遍历队头3号结点,把3号结点的左右两个子节点6、7,加入队尾;

③->④->⑤->⑥->⑦

Step6:遍历完3号结点,出队;

④->⑤->⑥->⑦

Step7:遍历队头4号结点,其没有子节点,直接出队;

⑤->⑥->⑦

Step8:遍历队头5号结点,把5号结点的左右两个子节点8、9,加入队尾;

⑤->⑥->⑦->⑧->⑨

Step9:遍历完5号结点,出队;

⑥->⑦->⑧->⑨

Step10:遍历队头6号结点,其没有子节点,直接出队;

⑦->⑧->⑨

Step11:遍历队头7号结点,把7号结点的左右两个子节点10、11,加入队尾;

⑦->⑧->⑨->⑩->⑪

Step12:遍历完7号结点,出队;

⑧->⑨->⑩->⑪

Step13:⑧⑨⑩⑪号结点都没有子节点,直接出队


图的广度优先遍历

[入门必看]数据结构3.3:栈和队列的应用_第86张图片

注:在“图”章节中会详细学习

实现思想和树类似

新建一个队列,每遍历一个结点,要检查和这个结点相邻的其他结点有没有被遍历过。

Step1:遍历1号结点,其相邻结点2、3都没有被遍历过,加入队尾,遍历完1号结点后让其出队

①->②->③
②->③

Step2:遍历2号结点,其相邻结点4没有被遍历过,加入队尾,遍历完2号结点后让其出队

②->③->④
③->④

Step3:遍历3号结点,其相邻结点5、6没有被遍历过,加入队尾,遍历完3号结点后让其出队

③->④->⑤->⑥
④->⑤->⑥

Step4:遍历4号结点,其相邻结点都被遍历过,直接出队

⑤->⑥

Step5:遍历5号结点,其相邻结点7、8没有被遍历过,加入队尾,遍历完5号结点后让其出队

⑤->⑥->⑦->⑧
⑥->⑦->⑧

Step6:⑥⑦⑧号结点的相邻结点都被遍历过,直接出队


队列在操作系统中的应用

多个进程争抢着使用有限的系统资源时,FCFS(First Come First Service,先来先服务)是一种常用策略。

FCFS:可用队列实现
——先申请的,先分配资源

  • Eg1:CPU资源的分配

[入门必看]数据结构3.3:栈和队列的应用_第87张图片
Step1:多个进程排成就绪队列;
[入门必看]数据结构3.3:栈和队列的应用_第88张图片
[入门必看]数据结构3.3:栈和队列的应用_第89张图片

Step2:选择队头元素,上CPU执行一个短的时间片,迅速下CPU进入队尾;

[入门必看]数据结构3.3:栈和队列的应用_第90张图片
Step3:重复Step2,选择队头元素,上CPU执行一个短的时间片,迅速下CPU进入队尾

这样所有进程都轮流得到了CPU处理。

  • Eg2:打印数据缓冲区

场景:去学校打印店打印论文,多个同学用同一台打印机打印,打印的先后顺序如何?
[入门必看]数据结构3.3:栈和队列的应用_第91张图片
系统开辟了一片缓冲区用于存放打印机此时暂时不能处理的数据,排成队列依次打印。


知识回顾与重要考点

3.3.1_栈在括号匹配中的应用

用栈实现括号匹配
依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。

匹配失败情况:
①左括号单身②右括号单身③左右括号不匹配


3.3.2_1_栈在表达式求值中的应用(上)

[入门必看]数据结构3.3:栈和队列的应用_第92张图片

  • 一个中缀表达式可以对应多个后缀、前缀表达式;
    加入了“左优先”和“右优先”原则,使一个中缀表达式只对应一个后缀表达式、一个前缀表达式;
    ——确保算法的“确定性
  • 中缀转后缀,先弹出的元素是“右操作数”;
    中缀转前缀,先弹出的元素时“左操作数”
  • “左优先”、“右优先”、“左操作数”、“右操作数”是DIY的说法,方便理解

3.3.2_2_栈在表达式求值中的应用(下)

[入门必看]数据结构3.3:栈和队列的应用_第93张图片

  • 用栈实现中缀转后缀
  • 用栈实现后缀表达式的计算
  • 用栈实现中缀表达式的计算(结合前两个算法)

3.3.3_栈在递归中的应用

  • 函数调用的特点:最后被调用的函数最先执行结束(LIFO)

  • 函数调用时,需要用一个“函数调用栈”存储:
    ①调用返回地址
    ②实参
    ③局部变量

  • 递归调用时,函数调用栈可称为“递归工作栈”

  • 每进入一层递归,就将递归调用所需信息压入栈顶

  • 每退出一层递归,就从栈顶弹出相应信息

缺点:效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算

可以自定义栈将递归算法改造成非递归算法


3.3.4+3.3.5_队列的应用

你可能感兴趣的:(#,第3章,栈,队列和数组,数据结构,开发语言,c++)