返回顶部
首页 > 资讯 > 后端开发 > Python >Python数据结构与算法中的栈详解(3)
  • 162
分享到

Python数据结构与算法中的栈详解(3)

2024-04-02 19:04:59 162人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录前序、中序和后序表达式是什么?我们为什么要学习前/后序表达式?从中序向前序和后序转换用python实现从中序表达式到后序表达式的转换​计算后序表达式总结前序、中序和后序表达式是什

前序、中序和后序表达式是什么?

对于像B∗C 这样的算术表达式,可以根据其形式来正确地运算。在B∗C 的例子中,由于乘号出现在两个变量之间,因此我们知道应该用变量 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为例。如果把运算符放到两个操作数之前,就得到了前序表达式+AB 。同理,如果把运算符移到最后,会得到后序表达式AB+ 。这两种表达式看起来有点奇怪。​

通过改变运算符与操作数的相对位置,我们分别得到 前序表达式 和 后序表达式 。前序表达式要求所有的运算符出现在它所作用的两个操作数之前,后序表达式则相反。下表列出了一些例子。

中序表达式前序表达式后序表达式
A + B+ A BA B +
A + B * C+ A * B CA B C * +

 A+B∗C可以被重写为前序表达式 + A ∗ B C 

乘号出现在 B 和 C 之前,代表着它的优先级高于加号。加号出现在 A 和乘法结果之前。

A+B∗C对应的后序表达式是 A B C ∗ + 

运算顺序仍然得以正确保留,这是由于乘号紧跟 B 和 C 出现,意味着它的优先级比加号更高。

关于前序表达式和后序表达式,尽管运算符被移到了操作数的前面或者后面,但是运算顺序并没有改变。

再举一个例子:现在来看看中序表达式  (A+B)∗C

括号用来保证加号的优先级高于乘号。但是,当A+B被写成前序表达式时,只需将加号移到操作数之前,即+AB。于是,加法结果就成了乘号的第一个操作数。乘号被移到整个表达式的最前面,从而得到∗+ABC。​

同理,后序表达式 A B + A B+ AB+保证优先计算加法。乘法则在得到加法结果之后再计算。因此,正确的后序表达式为 AB+C∗。​

一些中序、前序与后序表达式对应示例:

中序表达式前序表达式后序表达式
A + B * C + D+ + A * B C DA B C * + D +
(A + B) * (C + D)* + A B + C DA B + C D + *
A * B + C * D+ * A B * C DA B * C D * +
A + B + C + D+ + + A B C DA B + C + D +

我们为什么要学习前/后序表达式?

在上面的中序表达式变为前/后序表达式的例子中,请注意一个非常重要的变化。在后两个表达式中,括号去哪里了?为什么前序表达式和后序表达式不需要括号?答案是,这两种表达式中的运算符所对应的操作数是明确的。只有中序表达式需要额外的符号来消除歧义。前序表达式和后序表达式的运算顺序完全由运算符的位置决定。​

前序表达式是一种十分有用的表达式,它将中序表达式转换为可以依靠简单的操作就能得到运算结果的表达式。例如, ( a + b ) ∗ ( c + d ) (a+b)*(c+d) (a+b)∗(c+d)转换为 ∗ + a b + c d *+ab+cd ∗+ab+cd。它的优势在于只用两种简单的操作,入栈和出栈就可以解决任何中序表达式的运算。——《百度百科关于前序表达式作用的解释》

从中序向前序和后序转换

到目前为止,我们使用了一种难以言明的方法来将中序表达式转换成对应的前序表达式和后序表达式。正如我们所想的那样,这其中一定存在通用的算法,可用于正确转换任意复杂度的表达式。

一个容易观察到的方法是替换括号法,但前提是使用完全括号表达式

如前所述,可以将A+B∗C写作(A+(B∗C)),以表示乘号的优先级高于加号。进一步观察后会发现,每一对括号其实对应着一个中序表达式(包含两个操作数以及其间的运算符)。 观察子表达式(B∗C)的右括号。如果将乘号移到右括号所在的位置,并且去掉左括号,就会得到BC∗,这实际上是将该子表达式转换成了对应的后序表达式。如果把加号也移到对应的右括号所在的位置,并且去掉对应的左括号,就能得到完整的后序表达式

在这里插入图片描述

如果将运算符移到左括号所在的位置,并且去掉对应的右括号,就能得到前序表达式,如下图所示。实际上,括号对的位置就是其包含的运算符的最终位置。

在这里插入图片描述

因此,若要将任意复杂的中序表达式转换成前序表达式后序表达式,可以先将其写作完全括号表达式, 然后将括号内的运算符移到 左括号处(前序表达式) 或者 右括号处(后序表达式)。

Python实现从中序表达式到后序表达式的转换​

我们需要开发一种将任意中序表达式转换成后序表达式算法。为了完成这个目标,让我们进一步观察转换过程。​

再一次研究A+B∗C这个例子。如前所示,其对应的后序表达式为 ABC∗+。操作数 A 、B 和 C 的相对位置保持不变,只有运算符改变了位置。再观察中序表达式中的运算符。从左往右看,第一个出现的运算符是 + 。但是在后序表达式中,由于 * 的优先级更高(写成完全括号表达式后乘法所在的括号先进行运算),因此 * 先于 + 出现。在本例中,中序表达式的运算符顺序与后序表达式的相反。​

在转换过程中,由于运算符右边的操作数还未出现(不知道运算符右边是否还有括号运算), 因此需要先将运算符保存在某处。同时,由于运算符有不同的优先级,因此可能需要反转它们的保存顺序。 本例中的加号与乘号就是这种情况。由于中序表达式中的加号先于乘号出现,但乘号的运算优先级更高,因此后序表达式需要反转它们的出现顺序。鉴于这种反转特性,使用栈来保存运算符就显得十分合理。​

那么对于(A+B)∗C,情况会如何呢?它对应的后序表达式为 AB+C∗。从左往右看,首先出现的运算符是 + 。不过,由于括号改变了运算符的优先级,因此当处理到 * 时,+ 已经被放入结果表达式中了。​

现在可以来总结转换算法: 当遇到左括号时,需要将左括号保存下来,以表示接下来的内容里会遇到高优先级的运算符(就算括号里出现的运算符本身的优先级低,但也因为在括号里而优先级高了起来);那个运算符需要等到左括号对应的右括号出现,才能确定转换为后序表达式之后它应该存在的位置 (回忆一下完全括号表达式的转换法);当右括号出现时,也就是确定了括号内运算符在后序表达式中的存在位置,便可以将左括号和左括号上面的其他运算符(可能有可能没有)从栈中取出来。 (用于完全括号表达式)​

用代码实现转换算法:

​假设中序表达式是一个以空格分隔的标记串。其中, 运算符标记有+−∗/ ,括号标记有“ ( ( (”和“ ) ) )” ,操作数标记有 A 、B 、C 等。下面的步骤会生成一个后序标记串。​

步骤:

1.创建用于保存运算符的空栈 opstack ,以及一个用于保存结果的空列表。

2.使用字符串方法 split 将输入的中序表达式转换成一个列表。

3.从左往右扫描这个标记列表

3.1 如果标记是操作数,将其添加到结果列表的末尾。

3.2 如果标记是左括号,将其压入 opstack 栈中。

3.3 如果标记是右括号,反复从 opstack 栈中移除元素,直到移除对应的左括号。将从栈中取出的每 一个运算符都添加到结果列表的末尾。

3.4 如果标记是运算符,将其压入 opstack 栈中。但是,在这之前,需要先从栈中取出优先级更高或相同的运算符,并将它们添加到结果列表的末尾。

4.当处理完输入表达式以后,检查 opstack 栈。将其中所有残留的运算符全部添加到结果列表的末尾。​

为了在Python中实现这一算法,我们使用一个叫作 prec 的字典来保存运算符的优先级值。该字典把每一个运算符都映射成一个整数。通过比较对应的整数,可以确定运算符的优先级(本例随意地使用了3、2、1)。左括号的优先级值最小。这样一来,任何与左括号比较的运算符都会被压入栈中。我们也将导入string 模块,它包含一系列预定义变量。本例使用一个包含所有大写字母的字符(string.ascii_uppercase )来代表所有可能出现的操作数。下述代码展示了完整的转换过程。

import string
class Stack:
    def __init__(self):
        self.items = []
    def isEmpty(self):
        return self.items == []
    def pop(self):
        return self.items.pop()
    def push(self, item):
        self.items.append(item)
    def peek(self):
        return self.items[len(self.items) - 1]
def infixToPostfix(infixexpr):
    prec = {
        "*" : 3,
        "/" : 3,
        "+" : 2,
        "-" : 2,
        "(" : 1
        }
    opStack = Stack()  # 创建栈
    postfixList = []  # 创建结果列表
    tokenList = infixexpr.split()  # 分割算式为列表
    for token in tokenList:                   # 遍历算式
        if token in string.ascii_uppercase:   # 如果当前元素是操作数
            postfixList.append(token)         # 直接放入结果列表
        elif token == "(":                    # 如果当前元素是 左括号
            opStack.push(token)               # 左括号入栈
        elif token == ")":                    # 如果当前元素是 右括号
            topToken = opStack.pop()          # 从栈中取元素
            while topToken != "(":            # 取到的元素 不是右括号 循环执行
                postfixList.append(topToken)  # 元素放入结果列表
                topToken = opStack.pop()      # 从栈中取元素
        else:                                 # 遍历到的元素是 运算符
            while (not opStack.isEmpty()) and (prec[opStack.peek()] >= prec[token]):
                # (栈非空)并且(栈顶的运算符优先级大于等于当前运算符优先级)循环执行:
                #  栈顶元素入结果列表
                postfixList.append(opStack.pop())
            # 运算符入栈
            opStack.push(token)
    # 把栈里剩下的运算符全放入结果列表
    while not opStack.isEmpty():
        postfixList.append(opStack.pop())
    # 返回拼接后的后序表达式字符串
    return " ".join(postfixList)
print(infixToPostfix("A + B * C"))
# A B C * +
print(infixToPostfix("( A + B ) * ( C + D )"))
# A B + C D + *

计算后序表达式

最后一个关于栈的例子是计算后序表达式。在这个例子中,栈再一次成为适合选择的数据结构。不过,当扫描后序表达式时,需要保存的是操作数,而不是运算符。换一个角度来说,当遇到一个运算符时,需要用离它最近的两个操作数来计算。​

为了进一步理解该算法,考虑后序表达式 4 5 6 * + 。当从左往右扫描该表达式时,首先会遇到操作数 4 和 5 。在遇到下一个符号之前,我们并不确定要对它们进行什么运算。故而将它们都保存在中,便可以在需要时取用。​

在本例中,紧接着 4 和 5 后出现的符号又是一个操作数。因此,将 6 也压入栈中,并继续检查后面的符号。​

现在遇到了运算符 * ,这意味着需要将最近遇到的两个操作数相乘。通过执行两次出栈操作,可以得到相应的操作数,然后进行乘法运算 5 ∗ 6 5*6 5∗6(本例的结果是30)。​

接着,将结果压入栈中。这样一来,当遇到后面的运算符时,它就可以作为操作数。当处理完最后一个运算符之后,栈中就只剩一个值。然后就可以将这个值取出来,并作为表达式的结果返回。​

过程如图所示:

在这里插入图片描述

需要特别注意的是:

处理除法运算时需要非常小心。由于后序表达式只改变运算符的位置,因此操作数的位置与在中序表达式中的位置相同。当从栈中依次取出除号的操作数时,它们的顺序是颠倒的,因此我们要必须保证操作数的顺序不能颠倒。​

用代码实现后序表达式计算过程:

假设后序表达式是一个以空格分隔的标记串。其中, 运算符标记有 ∗ / + − * / + - ∗/+−,操作数标记是一位的整数值。结果是一个整数。​

步骤:

1.创建空栈 operandStack

2.使用字符串方法 split 将输入的后序表达式转换成一个列表

3.从左往右扫描这个标记列表:

(1) 如果标记是操作数,将其转换成整数(因为当前是字符)并且压入 operandStack 栈中

(2) 如果标记是运算符,从 operandStack 栈中取出两个操作数。第一次取出右操作数,第二次取出左操作数。进行相应的算术运算,然后将运算结果压入 operandStack 栈中。

4.当处理完输入表达式时,栈中的值就是结果。将其从栈中返回。​

为了方便运算,我们定义了辅助函数 doMath 。它接受一个运算符和两个操作数,并进行相应的运算。

import string

class Stack:
    def __init__(self):
        self.items = []
    def isEmpty(self):
        return self.items == []
    def pop(self):
        return self.items.pop()
    def push(self, item):
        self.items.append(item)
    def peek(self):
        return self.items[len(self.items) - 1]

def postfixEval(postfixExpr):
    operandStack = Stack()                             # 创建存放元素的栈
    tokenList = postfixExpr.split()                    # 分割算式字符串
    for token in tokenList:                            # 遍历算式元素
        if token in "0123456789":                      # 如果 元素 是 操作数
            operandStack.push(int(token))              # 转化为整型 入栈
        else:                                          # 元素不是操作数 是运算符
            operand2 = operandStack.pop()              # 从栈顶取 操作数2
            operand1 = operandStack.pop()              # 从栈顶取 操作数1
            result = doMath(token,operand1,operand2)   # 调用doMath进行运算
            operandStack.push(result)                  # 运算结果 入栈
    return operandStack.pop()                          
    # 返回栈内元素(遍历结束后这个唯一的元素就是整个算式的结果)
def doMath(op,op1,op2):
    if op == "*":
        return op1 * op2
    elif op == "/":
        return op1 / op2
    elif op == "+":
        return op1 + op2
    else:
        return op1 - op2

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容! 

--结束END--

本文标题: Python数据结构与算法中的栈详解(3)

本文链接: https://lsjlt.com/news/141905.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python数据结构与算法中的栈详解(3)
    目录前序、中序和后序表达式是什么?我们为什么要学习前/后序表达式?从中序向前序和后序转换用Python实现从中序表达式到后序表达式的转换​计算后序表达式总结前序、中序和后序表达式是什...
    99+
    2024-04-02
  • Python数据结构与算法中的栈详解
    目录0. 学习目标1. 栈的基本概念1.1 栈的基本概念1.2 栈抽象数据类型1.3 栈的应用场景2. 栈的实现2.1 顺序栈的实现2.1.1 栈的初始化2.1.2 求栈长2.1.3...
    99+
    2024-04-02
  • Python数据结构与算法中的栈详解(1)
    目录什么是栈构建一个栈总结什么是栈 栈有时也被称作“下推栈”。它是有序集合,添加操作和移除操作总发生在同一端,即栈的 “顶端&rdquo...
    99+
    2024-04-02
  • Python数据结构与算法中的栈详解(2)
    目录匹配括号匹配符号总结匹配括号 接下来,我们使用栈解决实际的计算机科学问题。​ 比如我们都写过这样所示的算术表达式, ( 5 + 6 ) ∗ ( 7 + 8 ) / ...
    99+
    2024-04-02
  • JavaScript数据结构与算法之栈详解
    目录1.认识栈2.面向过程方法源码编写栈2.1思考2.2需要实现的方法2.3源码实现,并调用类3.用面向对象的方法来源码书写3.1思考3.2需要实现的方法3.3源码及使用类4.总结1...
    99+
    2024-04-02
  • Python的数据结构与算法的队列详解(3)
    目录模拟打印机任务队列过程主要模拟步骤:​构建队列程序模拟打印程序模拟打印过程(有注释)总结模拟打印机任务队列过程 计算机科学中也有众多的队列例子。比如计算机实验室有10台计算机,它...
    99+
    2024-04-02
  • python数据结构与算法(3)
    Python内置类型性能分析 timeit模块timeit模块可以⽤来测试⼀⼩段Python代码的执⾏速度。class timeit.Timer(stmt='pass', setup='pass', ...
    99+
    2023-01-31
    数据结构 算法 python
  • Python数据结构与算法中的栈怎么构建
    本篇内容主要讲解“Python数据结构与算法中的栈怎么构建”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python数据结构与算法中的栈怎么构建”吧!什么是栈栈有时也被称作“下推栈”。它是有序集...
    99+
    2023-06-29
  • Python数据结构与算法中的栈怎么实现
    这篇文章主要介绍“Python数据结构与算法中的栈怎么实现”,在日常操作中,相信很多人在Python数据结构与算法中的栈怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python数据结构与算法中的栈怎...
    99+
    2023-06-29
  • Python数据结构之栈详解
    目录0. 学习目标1. 栈的基本概念1.1 栈的基本概念1.2 栈抽象数据类型1.3 栈的应用场景2. 栈的实现2.1 顺序栈的实现2.1.1 栈的初始化2.2 链栈的实现2.3 栈...
    99+
    2024-04-02
  • Python数据结构与算法中的队列详解(1)
    目录什么是队列?构建一个队列总结什么是队列? 队列,与栈类似,是有序集合。添加操作发生在 “尾部”,移除操作只发生在 “头部&...
    99+
    2024-04-02
  • Python数据结构与算法中的队列详解(2)
    传土豆 队列的一个典型方法是模拟需要以 FIFO 方式管理数据的真实场景。考虑这样一个游戏:传土豆。在这个游戏中,成员们围成一圈,并依次尽可能快地传递一个土豆。在某个时刻,大家停止传...
    99+
    2024-04-02
  • 详解Python数据结构与算法中的顺序表
    目录0. 学习目标1. 线性表的顺序存储结构1.1 顺序表基本概念1.2 顺序表的优缺点1.3 动态顺序表2. 顺序表的实现2.1 顺序表的初始化2.2 获取顺序表长度2.3 读取指...
    99+
    2024-04-02
  • JS数据结构与算法中的队列结构详解
    目录队列结构一.认识队列二.队列的应用三.队列类的创建四.队列的常见操作五.击鼓传花六.优先级队列七.优先级队列的实现队列结构 一.认识队列 受限的线性结构:我们已经学习了一种受限的...
    99+
    2022-11-13
    JS数据结构与算法 JS队列结构
  • Python数据结构与算法之算法分析详解
    目录0. 学习目标1. 算法的设计要求1.1 算法评价的标准1.2 算法选择的原则2. 算法效率分析2.1 大O表示法2.2 常见算法复杂度2.3 复杂度对比3. 算法的存储空间需求...
    99+
    2024-04-02
  • 详解python数据结构之栈stack
    前言 栈(Stack)是一种运算受限的线性表。 按照先进后出(FILO,First In Last Out)的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶。栈只能在一端进行插入和删除操作。 文章内容包含: ...
    99+
    2022-06-02
    python 栈stack python数据结构
  • Python数据结构与算法之跳表详解
    目录0. 学习目标1. 跳表的基本概念1.1 跳表介绍1.2 跳表的性能1.3 跳表与普通链表的异同2. 跳表的实现2.1 跳表结点类2.2 跳表的初始化2.3 获取跳表长度2.4 ...
    99+
    2024-04-02
  • Python数据结构与算法的双端队列详解
    目录什么是双端队列​​用Python实现双端队列运用双端队列构建回文检测器总结什么是双端队列​ 双端队列是与队列类似的有序集合。它有一前、一后两端,元素在其中保持自己的位置。与队列不...
    99+
    2024-04-02
  • java数据结构之栈的详解
    目录一、栈1.栈的应用1.1括号匹配1.2后缀表达式1.3用栈实现队列1.4最小栈1.5栈的压入和弹出序列总结一、栈 栈的特性就是先进后出,常用方法是入栈(push()),出栈(po...
    99+
    2024-04-02
  • Python数据结构-----栈1.0(栈的介绍与操作)
    目录 前言: 栈的介绍 Python栈的操作 1.创建栈 2.判断栈是否为满  3.判断栈是否为空  4.压栈 5.出栈 6.展示栈数据 7.获取到栈顶的数据 8.获取到栈的数据总数 第三方模块实现栈 下载模块: 导入模块:  使用示例: ...
    99+
    2023-10-18
    数据结构 链表 java python 高级编程
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作