2021-02-25 栈实现计算器操作(中缀表达式、逆波兰表达式、波兰表达式)

栈实现计算器操作(中缀表达式、逆波兰表达式、波兰表达式)

好久没有在leetcode上做题,今天随手发现一道经典的数据结构题目,思路很简单,但是实现起来遇到了几处bug,修修改改花了不少时间,也反映了出思考不完善和语法不熟练的问题,在此记录一下。

中缀表达式

中缀记法是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:3 + 4),中缀表达式是人们常用的算术表示方法。
与前缀表达式(例:+ 3 4)或后缀表达式(例:3 4 +)相比,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。
与前缀或后缀记法不同的是,中缀记法中括号是必需的。计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。
指路:Leetcode 面试题 16.26. 计算器

思路

本题目中没有涉及到括号的运算,简化了难度,只涉及到四个运算符号和空格(注意存在空格!!虽然感觉很没必要…)四个运算符号存在优先级,加减运算级别低于乘除运算,我们首先定义一个优先级函数,用于决定是否弹栈。
接下来考虑几种情景:

  1. 3+4+…
  2. 3+4*…
  3. 3*4-…

1,2,3分别对应的是优先级不变、优先级上升、优先级下降的几种情况。通过观察我们可以发现,如果将数字和运算符号分别存于两个栈中,当3、4和中间的运算符号分别入栈后,如果优先级不变或者下降,我们可以弹出3、4和运算符号,计算后再入栈;然而在2中,优先级上升,4不可以与3进行计算,而是与后面的表达式计算。
从而,我们可以设计如下思路:

  • 设置两个栈:数字栈num和符号栈operator
  • 遇到数字则压入数字栈
  • 遇到符号先判断,如果符号栈顶元素优先级大于或者等于该符号,则开始进行符号栈弹栈,直到栈顶元素小于目前弹出的元素/栈为空

符号栈弹栈的操作为:
弹出数字栈栈顶两个元素和符号栈的栈顶元素,进行计算后将值压入数字栈(注意弹出的两个元素运算的顺序!!! 尤其是减法和除法操作)
进行到这一步,可以发现目前符号栈内最多有两个符号,且优先级从低到高排列。这时候我们再进行又一轮的符号栈弹栈操作,弹出数字栈栈顶元素即可。

遇到的问题

思路看起来很简单,可是实现起来遇到两个考虑不周的问题:

  1. 空格
    不知为何,题目中要求表达式仅包含非负整数,+, - ,*,/ 四种运算符和 空格,一直没有认真审题所以代码总是报错…无语子
  2. 输入的数字不是个位数,有n位
    测试用例:“42“,按照错误的代码,输出的是2…问题出在一直默认是进行10以内加减乘除= =
    我的解决方法是设置一个prev变量,记录上一个输入的字符,如果prev是运算符号,则本次输入的数字直接压栈;否则,弹出数字栈顶元素a,将(a*10+本次输入的数字)压栈。
    实现起来很简单,在循环开始前设置prev=+(减乘除也可,运算符号就可以),然后循环结束前令prev=i.

代码

一段繁琐且耗时长的python代码实现…

class Solution:
    def calculate(self, s: str) -> int:
        def priority(n):
            if n in ['+','-']:
                return 1
            else:
                return 2
        stack_num=[]
        stack_operator=[]
        prev='+'
        for i in s:
            if i==' ':
                continue
            else:
                if i in ['+','-','*','/']:
                    if stack_operator==[]:
                        stack_operator.append(i)
                    elif priority(stack_operator[-1])>=priority(i):
                        while stack_operator and priority(stack_operator[-1])>=priority(i):
                            a=stack_num.pop()
                            b=stack_num.pop()
                            operator=stack_operator.pop()
                            if operator=='+':
                                c=a+b
                            if operator=='-':
                                c=b-a
                            if operator=='*':
                                c=a*b
                            if operator=="/":
                                c=int(b/a)
                            stack_num.append(c)
                        stack_operator.append(i)
                    else:
                        stack_operator.append(i)
                else:
                    if prev in ['+','-','*','/']:
                        stack_num.append(int(i))
                    else:
                        a=stack_num.pop()
                        stack_num.append(a*10+int(i))
            prev=i
        while stack_operator:
            a=stack_num.pop()
            b=stack_num.pop()
            operator=stack_operator.pop()
            if operator=='+':
                c=a+b
            if operator=='-':
                c=b-a
            if operator=='*':
                c=a*b
            if operator=="/":
                c=int(b/a)
            stack_num.append(c)
        return stack_num[-1]

后记

这一解法的时间复杂度较高,因为每次遇到优先级不变或较低的元素都要进行弹栈/压栈操作,但空间复杂度很低—因为符号栈中最多保留两个元素,如何降低空间复杂度呢?其实这种解法利用的思想可以用中缀表达式转后缀表达式,再根据后缀表达式计算两步来实现。接下来介绍一下逆波兰表达式的转化及计算。

猜你喜欢

转载自blog.csdn.net/weixin_48711154/article/details/114054308