逆波兰式

逆波兰式(Reverse Polish notation, RPN),又称后缀表达式。
要想了解逆波兰式的特点及作用,那要从我们最常见的中缀表达式说起。

中缀表达式

定义:通用的算术或逻辑公式表示方法。
特点:操作符在操作数之间。
例子:1 - (2 + 3)
没错,中缀表达式就是我们人类常常书写一个算术表达式的形式,但是这种写法适合人进行运算却不适合计算机运算。因此科学家们设计了前缀表达式和后缀表达式,两者都可以很方便地让计算机实现一些基础运算。

前缀表达式

特点:操作符在操作数之前。
例子:1 - (2 + 3) 的前缀表达式为 - 1 + 2 3
当计算机处理时,利用栈,从后往前扫描,遇到操作数就入栈,遇到操作符就弹出两个操作数进行运算,并将运算结果重新入栈,最终计算结果在栈顶(此时栈只有 1 个元素,就是计算结果)。

  1. 遇到 3 入栈,遇到 2 入栈;
  2. 遇到 + 弹出 2 和 3,计算 2 + 3 = 5,将 5 入栈;
  3. 遇到 1 入栈;
  4. 遇到 - 弹出 1 和 5,计算 1 - 5 = -4,将 -4 入栈,扫描完毕,-4 就是计算结果。

后缀表达式

特点:操作符在操作数之后。
例子:1 - (2 + 3) 的前缀表达式为 1 2 3 + -
当计算机处理时,利用栈,从前往后扫描,遇到操作数就入栈,遇到操作符就弹出两个操作数进行运算,并将运算结果重新入栈,最终计算结果在栈顶(此时栈只有 1 个元素,就是计算结果)。

  1. 遇到 1, 2, 3 分别入栈;
  2. 遇到 + 弹出 3 和 2,计算 2 + 3 = 5,将 5 入栈;
  3. 遇到 - 弹出 5 和 1,计算 1 - 5 = -4,将 -4 入栈,扫描完毕, -4 就是计算结果。

为什么选择逆波兰式

既然前缀表达式和后缀表达式都可以实现简单的四则运算,为什么多采用后缀表达式呢?
我个人觉得是因为在利用后缀表达式计算时,是从前往后扫描,比较符合大多数人的逻辑。
但是这里要注意:采用后缀表达式求值时,遇到操作符 ops 时,要先后弹出两个操作数 x 和 y,但其实正确的运算顺序是 y ops x,即先弹出的 x 是第二个操作数,后弹出的 y 是第一个操作数。
而前缀表达式却正好相反,先弹出的就是第一个操组作数,自己看上面两个例子就可以发现了。

构建逆波兰式

假设中缀表达式只含有 +, -, *, /, % 操作符和括号 (),操作数都是 0-9 的整数,并且保证表达式有效。
假设中缀表达式以字符串的形式给出,如 s = “1-(2+3)”
根据中缀表达式构建后缀表达式的过程如下:

  1. 借助一个字符串数组和一个栈:操作数数组 RPN 和操作符栈 st。
  2. 从左往右扫描 s:
    1. 若遇到操作数则加入 RPN 末尾;
    2. 若遇到左括号则直接压入 st;
    3. 若遇到右括号,则依次把栈顶放入 RPN 数组末尾,直至遇到左括号停止。
    4. 遇到操作符 ops 若栈空则直接压入;否则先与 st 的栈顶 st.peek() 的操作符比较优先级,若 逆波兰式 - 图1,则直接将 ops 压入 st;否则循环将 st.peek() 放入 RPN 数组末尾,再把 ops 压入 st。
    5. 重复以上操作直至扫描完毕,把栈中剩余元素倒入 RPN 数组。

一个图示过程如下:
image.png
代码实现如下:

  1. func priority(x byte) int {
  2. if x == '(' || x == ')' {
  3. return 0
  4. } else if x == '+' || x == '-' {
  5. return 1
  6. } else if x == '*' || x == '/' || x == '%' {
  7. return 2
  8. } else {
  9. return -1
  10. }
  11. }
  12. // @s 中缀表达式
  13. func buildRPN(s string) []byte {
  14. RPN := make([]byte, 0, len(s))
  15. stack := make([]byte, 0, len(s) / 2)
  16. for i := range s {
  17. // 操作数
  18. if priority(s[i]) == -1 {
  19. RPN = append(RPN, s[i])
  20. continue
  21. }
  22. // 操作符
  23. for {
  24. // 栈空 或 左括号 或 优先级比栈顶高
  25. if len(stack) == 0 || s[i] == '(' ||
  26. (priority(s[i]) > priority(stack[len(stack)-1])) {
  27. stack = append(stack, s[i])
  28. break
  29. }
  30. // 右括号 或 优先级比栈顶低
  31. tmp := stack[len(stack)-1]
  32. stack = stack[:len(stack)-1]
  33. if tmp == '(' {
  34. break
  35. } else {
  36. RPN = append(RPN, tmp)
  37. }
  38. }
  39. }
  40. // stack 剩余元素倒入 RPN
  41. for len(stack) > 0 {
  42. RPN = append(RPN, stack[len(stack)-1])
  43. stack = stack[:len(stack)-1]
  44. }
  45. return RPN
  46. }

计算逆波兰表达式

计算过程如下:

  1. 借助一个栈 stack
  2. 从左往右扫描 RPN 数组,若遇到的是操作数则直接入栈。
  3. 若遇到的是操作符,则依次出栈两个操作数进行相应计算,把计算结果入栈。

一个图示过程如下:
image.png
代码实现如下:

func eval(RPN []byte) int {
    stack := make([]int, 0, len(RPN))

    for i := range RPN {
        if RPN[i] >= '0' && RPN[i] <= '9' {            
            stack = append(stack, int(RPN[i]-'0'))
        } else {
            size := len(stack)
            operand1, operand2 := stack[size-2], stack[size-1]
            stack = stack[:size-2]
            ans := 0
            if RPN[i] == '+' {
                ans = operand1 + operand2
            } else if RPN[i] == '-' {
                ans = operand1 - operand2
            } else if RPN[i] == '*' {
                ans = operand1 * operand2
            } else if RPN[i] == '/' {
                ans = operand1 / operand2
            } else if RPN[i] == '%' {
                ans = operand1 % operand2
            }
            stack = append(stack, ans)
        }
    }

    return stack[0]
}

上述代码实现的很简单,而且输入限制的很死,如果放宽条件限制,要处理的细节会变的非常多,比如:

  • 输入是什么?如果是 string 字符串,那么需要根据输入分割操作数和操作符,分成一个 []string 则方便进行 RPN 的构建?
  • 如果 RPN 采用了 []string 类型,那么计算时如何快速把 “123” 转成数字 123 进行运算呢?如何快速判断 RPN[i] 是操作数还是操作符呢?
  • 除法操作若不能除整,处理规则是什么呢?除数为 0 怎么处理呢?
  • 计算过程中或结果是否会溢出呢?
  • ……

下面给出一个另外一个简陋的代码,它放宽了对表达式的限制:

  • 表达式可以包含空格;
  • 表达式中整数的位数可以超过 1 位;

但是仍然有其他要求:

  • 计算过程中,中间结果和最终结果都不能超过 int32 的存储范围。 ```go func buildRPN(s string) []string { RPN := make([]string, 0) stack := list.New() var builder strings.Builder

    for i := 0; i < len(s); i++ {

      // 空格
      if s[i] == ' ' {
          continue
      } 
      // 操作数
      if s[i] >= '0' && s[i] <= '9' {
          for ; i < len(s) && s[i] >= '0' && s[i] <= '9'; i++ {
              builder.WriteByte(s[i])    
          }
          i--
          RPN = append(RPN, builder.String())
          builder.Reset()
          continue
      }
      // 操作符 或 括号
      for {
          // 栈空 或 左括号 或 优先级比栈顶高
          if stack.Len() == 0 || s[i] == '(' || (priority(s[i]) > priority(stack.Back().Value.(byte))) {
              stack.PushBack(s[i])
              break
          }
          // 右括号 或 优先级比栈顶低
          tmp := stack.Back()
          stack.Remove(tmp)
          if tmp.Value.(byte) == '(' {
              break
          } else {
              RPN = append(RPN, string(tmp.Value.(byte)))
          }
      }
    

    }

    // 栈剩余元素倒入 RPN for stack.Len() > 0 {

      e := stack.Back()
      RPN = append(RPN, string(e.Value.(byte)))
      stack.Remove(e)
    

    }

    return RPN }

func calcalateRPN(RPN []string) int { stack := list.New()

for i := 0; i < len(RPN); i++ {
    if RPN[i][0] >= '0' && RPN[i][0] <= '9' {    // 操作数
        number, _ := strconv.Atoi(RPN[i])
        stack.PushBack(number)
    } else {    // 操作符
        e1, e2 := stack.Back().Prev(), stack.Back()
        operand1, operand2 := e1.Value.(int), e2.Value.(int)
        stack.Remove(e1)
        stack.Remove(e2)
        tmp := 0
        if RPN[i] == "+" {
            tmp = operand1 + operand2
        } else if RPN[i] == "-" {
            tmp = operand1 - operand2
        } else if RPN[i] == "*" {
            tmp = operand1 * operand2
        } else if RPN[i] == "/" {
            tmp = operand1 / operand2
        } else if RPN[i] == "%" {
            tmp = operand1 % operand2
        }
        stack.PushBack(tmp)
    }
}

return stack.Back().Value.(int)

} ```