| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> Python数据结构与算法_3_栈_强化提升 -> 正文阅读 |
|
[数据结构与算法]Python数据结构与算法_3_栈_强化提升 |
前情提要:Python数据结构与算法_1_栈_基础知识前情提要:Python数据结构与算法_2_栈_知识巩固前序、中序和后序表达式是什么?对于像
B
?
C
B*C
B?C 这样的算术表达式,可以根据其形式来正确地运算。在
B
?
C
B*C
B?C 的例子中,由于乘号出现在两个变量之间,因此我们知道应该用变量 B 乘以变量 C 。 因为运算符出现在两个操作数的中间 ,所以这种表达式被称作中序表达式 。 来看另一个中序表达式的例子:
A
+
B
?
C
A + B * C
A+B?C。虽然运算符 “ + ” 和 “ * ” 都在操作数之间,但是存在一个问题:它们分别作用于哪些操作数? “ + ” 是否作用于 A 和 B ?“ * ” 是否作用于 B 和 C ?这个表达式看起来存在歧义。 事实上,我们经常读写这类表达式,并且没有遇到任何问题。这是因为,我们知道运算符的特点。每一个运算符都有一个优先级 。在运算时,高优先级的运算符先于低优先级的运算符参与运算。唯一能够改变运算顺序的就是括号。乘法和除法的优先级高于加法和减法。 尽管这些规律对于人来说显而易见,计算机却需要明确地知道以何种顺序进行何种运算。一种杜绝歧义的写法是完全括号表达式 。这种表达式对每一个运算符都添加一对括号。由括号决定运算顺序,没有任何歧义,并且不必记忆任何优先级规则。 比如,
A
+
B
?
C
+
D
A + B * C + D
A+B?C+D可以被重写成
(
(
A
+
(
B
?
C
)
)
+
D
)
((A + (B * C)) + D)
((A+(B?C))+D), 以表明乘法优先,然后计算左边的加法表达式。由于加法运算从左往右结合,因此
A
+
B
+
C
+
D
A + B + C + D
A+B+C+D可以被重写成
(
(
(
A
+
B
)
+
C
)
+
D
)
(((A + B) + C) + D)
(((A+B)+C)+D)。 我们已经了解了中序表达式的原理, 还有另外两种重要的表达式,也许并不能一目了然地看出它们的含义,那就是前序和后序表达式。 以中序表达式
A
+
B
A + B
A+B为例。如果把运算符放到两个操作数之前,就得到了前序表达式
+
A
B
+ AB
+AB 。同理,如果把运算符移到最后,会得到后序表达式
A
B
+
A B +
AB+ 。这两种表达式看起来有点奇怪。 通过改变运算符与操作数的相对位置,我们分别得到 前序表达式 和 后序表达式 。前序表达式要求所有的运算符出现在它所作用的两个操作数之前,后序表达式则相反。下表列出了一些例子。
A
+
B
?
C
A + B * C
A+B?C可以被重写为前序表达式
+
A
?
B
C
+ A * B C
+A?BC。
A
+
B
?
C
A + B * C
A+B?C对应的后序表达式是
A
B
C
?
+
A B C * +
ABC?+。 关于前序表达式和后序表达式,尽管运算符被移到了操作数的前面或者后面,但是运算顺序并没有改变。 再举一个例子:现在来看看中序表达式 ( A + B ) ? C (A + B) * C (A+B)?C。 括号用来保证加号的优先级高于乘号。但是,当
A
+
B
A + B
A+B被写成前序表达式时,只需将加号移到操作数之前,即
+
A
B
+ A B
+AB。于是,加法结果就成了乘号的第一个操作数。乘号被移到整个表达式的最前面,从而得到
?
+
A
B
C
* + A B C
?+ABC。 同理,后序表达式
A
B
+
A B+
AB+保证优先计算加法。乘法则在得到加法结果之后再计算。因此,正确的后序表达式为
A
B
+
C
?
A B + C *
AB+C?。 一些中序、前序与后序表达式对应示例:
我们为什么要学习前/后序表达式?在上面的中序表达式变为前/后序表达式的例子中,请注意一个非常重要的变化。在后两个表达式中,括号去哪里了?为什么前序表达式和后序表达式不需要括号?答案是,这两种表达式中的运算符所对应的操作数是明确的。只有中序表达式需要额外的符号来消除歧义。前序表达式和后序表达式的运算顺序完全由运算符的位置决定。
从中序向前序和后序转换到目前为止,我们使用了一种难以言明的方法来将中序表达式转换成对应的前序表达式和后序表达式。正如我们所想的那样,这其中一定存在通用的算法,可用于正确转换任意复杂度的表达式。 一个容易观察到的方法是替换括号法,但前提是使用完全括号表达式。 如前所述,可以将
A
+
B
?
C
A + B * C
A+B?C写作
(
A
+
(
B
?
C
)
)
(A + (B * C))
(A+(B?C)),以表示乘号的优先级高于加号。进一步观察后会发现,每一对括号其实对应着一个中序表达式(包含两个操作数以及其间的运算符)。 观察子表达式
(
B
?
C
)
(B * C)
(B?C)的右括号。如果将乘号移到右括号所在的位置,并且去掉左括号,就会得到
B
C
?
B C *
BC?,这实际上是将该子表达式转换成了对应的后序表达式。如果把加号也移到对应的右括号所在的位置,并且去掉对应的左括号,就能得到完整的后序表达式。 如果将运算符移到左括号所在的位置,并且去掉对应的右括号,就能得到前序表达式,如下图所示。实际上,括号对的位置就是其包含的运算符的最终位置。 因此,若要将任意复杂的中序表达式转换成前序表达式或后序表达式,可以先将其写作完全括号表达式, 然后将括号内的运算符移到 左括号处(前序表达式) 或者 右括号处(后序表达式)。 用Python实现从中序表达式到后序表达式的转换? 我们需要开发一种将任意中序表达式转换成后序表达式的算法。为了完成这个目标,让我们进一步观察转换过程。 再一次研究
A
+
B
?
C
A + B * C
A+B?C这个例子。如前所示,其对应的后序表达式为
A
B
C
?
+
A B C * +
ABC?+。操作数 A 、B 和 C 的相对位置保持不变,只有运算符改变了位置。再观察中序表达式中的运算符。从左往右看,第一个出现的运算符是 + 。但是在后序表达式中,由于 * 的优先级更高(写成完全括号表达式后乘法所在的括号先进行运算),因此 * 先于 + 出现。在本例中,中序表达式的运算符顺序与后序表达式的相反。 在转换过程中,由于运算符右边的操作数还未出现(不知道运算符右边是否还有括号运算), 因此需要先将运算符保存在某处。同时,由于运算符有不同的优先级,因此可能需要反转它们的保存顺序。 本例中的加号与乘号就是这种情况。由于中序表达式中的加号先于乘号出现,但乘号的运算优先级更高,因此后序表达式需要反转它们的出现顺序。鉴于这种反转特性,使用栈来保存运算符就显得十分合理。 那么对于
(
A
+
B
)
?
C
(A + B) * C
(A+B)?C,情况会如何呢?它对应的后序表达式为
A
B
+
C
?
A B + C *
AB+C?。从左往右看,首先出现的运算符是 + 。不过,由于括号改变了运算符的优先级,因此当处理到 * 时,+ 已经被放入结果表达式中了。 现在可以来总结转换算法: 当遇到左括号时,需要将左括号保存下来,以表示接下来的内容里会遇到高优先级的运算符(就算括号里出现的运算符本身的优先级低,但也因为在括号里而优先级高了起来);那个运算符需要等到左括号对应的右括号出现,才能确定转换为后序表达式之后它应该存在的位置 (回忆一下完全括号表达式的转换法);当右括号出现时,也就是确定了括号内运算符在后序表达式中的存在位置,便可以将左括号和左括号上面的其他运算符(可能有可能没有)从栈中取出来。 (用于完全括号表达式) 用代码实现转换算法:? 步骤:
? 为了在Python中实现这一算法,我们使用一个叫作 prec 的字典来保存运算符的优先级值。该字典把每一个运算符都映射成一个整数。通过比较对应的整数,可以确定运算符的优先级(本例随意地使用了3、2、1)。左括号的优先级值最小。这样一来,任何与左括号比较的运算符都会被压入栈中。我们也将导入string 模块,它包含一系列预定义变量。本例使用一个包含所有大写字母的字符(string.ascii_uppercase )来代表所有可能出现的操作数。下述代码展示了完整的转换过程。
计算后序表达式最后一个关于栈的例子是计算后序表达式。在这个例子中,栈再一次成为适合选择的数据结构。不过,当扫描后序表达式时,需要保存的是操作数,而不是运算符。换一个角度来说,当遇到一个运算符时,需要用离它最近的两个操作数来计算。 为了进一步理解该算法,考虑后序表达式 4 5 6 * + 。当从左往右扫描该表达式时,首先会遇到操作数 4 和 5 。在遇到下一个符号之前,我们并不确定要对它们进行什么运算。故而将它们都保存在栈中,便可以在需要时取用。 在本例中,紧接着 4 和 5 后出现的符号又是一个操作数。因此,将 6 也压入栈中,并继续检查后面的符号。 现在遇到了运算符 * ,这意味着需要将最近遇到的两个操作数相乘。通过执行两次出栈操作,可以得到相应的操作数,然后进行乘法运算
5
?
6
5*6
5?6(本例的结果是30)。 接着,将结果压入栈中。这样一来,当遇到后面的运算符时,它就可以作为操作数。当处理完最后一个运算符之后,栈中就只剩一个值。然后就可以将这个值取出来,并作为表达式的结果返回。 过程如图所示: 需要特别注意的是: 用代码实现后序表达式计算过程: 步骤:
? 为了方便运算,我们定义了辅助函数 doMath 。它接受一个运算符和两个操作数,并进行相应的运算。
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/27 2:41:33- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |