概述

表达式一般由操作数(Operand)、运算符(Operator)组成,例如算术表达式中,通常把运算符放在两个操作数的中间,
这称为中缀表达式(Infix Expression),如A+B。
波兰数学家Jan Lukasiewicz提出了另一种数学表示法,它有两种表示形式:
把运算符写在操作数之前,称为波兰表达式(Polish Expression)或前缀表达式(Prefix Expression),如+AB;
把运算符写在操作数之后,称为逆波兰表达式(Reverse Polish Expression)或后缀表达式(Suffix Expression),如AB+;
其中,逆波兰表达式在编译技术中有着普遍的应用。

算法

一、 将中缀表达式转换成后缀表达式算法:

1、从左至右扫描一中缀表达式。
2、若读取的是操作数,则判断该操作数的类型,并将该操作数存入操作数堆栈
3、若读取的是运算符
(1) 该运算符为左括号”(“,则直接存入运算符堆栈。
(2) 该运算符为右括号”)”,则输出运算符堆栈中的运算符到操作数堆栈,直到遇到左括号为止。
(3) 该运算符为非括号运算符:
(a) 若运算符堆栈栈顶的运算符为括号,则直接存入运算符堆栈。
(b) 若比运算符堆栈栈顶的运算符优先级高或相等,则直接存入运算符堆栈。
(c) 若比运算符堆栈栈顶的运算符优先级低,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈。
4、当表达式读取完成后运算符堆栈中尚有运算符时,则依序取出运算符到操作数堆栈,直到运算符堆栈为空。

二、逆波兰表达式求值算法:

1、循环扫描语法单元的项目。
2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
4、如果扫描的项目是一个一元运算符,则对栈的最顶上操作数执行该运算。
5、将运算结果重新压入堆栈。
6、重复步骤2-5,堆栈中即为结果值。

实现

将中缀表达式转后缀表达式

将人们熟悉的中缀表达式转换为后缀表达式,便于计算机的计算,这里需要用到栈数据结构。

  1. // 定义符号优先级
  2. var symbolMap = map[string]int{
  3. "+": 1,
  4. "-": 1,
  5. "*": 2,
  6. "/": 2,
  7. "(": 3,
  8. ")": 3,
  9. }
  10. func main() {
  11. // 待解析的表达式
  12. var str string = "1+1+(2+3)+3-3+4"
  13. // 检查括号是否对齐
  14. bracketCheck(str)
  15. suffixExp := trans2SuffixExp(str)
  16. fmt.Println("后缀表达式:", suffixExp)
  17. // 计算
  18. ret := parseExpression(str)
  19. fmt.Printf("表达式[%s]=%d", str, ret)
  20. }
  21. func trans2SuffixExp(exp string) string {
  22. var resultStr = ""
  23. var stack = InitStack(20)
  24. for _, e := range exp {
  25. ele := string(e)
  26. if unicode.IsNumber(e) {
  27. resultStr += ele
  28. continue
  29. }
  30. // 操作符
  31. if stack.isEmpty() {
  32. stack.Push(ele)
  33. continue
  34. }
  35. // 将栈顶元素比当前元素优先级高的出栈,追加到表达式后面
  36. topEleStr := fmt.Sprintf("%v", stack.TopEle())
  37. if ele != ")" && symbolMap[topEleStr] < symbolMap[ele] {
  38. stack.Push(ele)
  39. continue
  40. }
  41. // 栈顶是左括号直接入栈
  42. if "(" == topEleStr {
  43. stack.Push(ele)
  44. continue
  45. }
  46. if ")" == ele {
  47. // 弹出栈顶元素,直到遇到(
  48. for {
  49. topEleStr := fmt.Sprintf("%v", stack.Pop())
  50. if topEleStr == "(" {
  51. break
  52. }
  53. resultStr += topEleStr
  54. }
  55. } else {
  56. // 弹出栈顶优先级大于等于当前元素的符号
  57. for {
  58. topEleStr = fmt.Sprintf("%v", stack.TopEle())
  59. if symbolMap[topEleStr] < symbolMap[ele] {
  60. break
  61. }
  62. // 追加
  63. resultStr += fmt.Sprintf("%v", stack.Pop())
  64. }
  65. // 当前符号入栈
  66. stack.Push(ele)
  67. }
  68. }
  69. // 弹出栈内所有元素
  70. for !stack.isEmpty() {
  71. resultStr += fmt.Sprintf("%v", stack.Pop())
  72. }
  73. return resultStr
  74. }

计算后缀表达式的结果

上小结已经得到了表达式的后缀表达式,那么我们可以直接利用后缀表达式计算结果了,这里也需要用到栈数据结构

  1. func calSuffixExp(exp string) interface{} {
  2. // 根据后缀表达式计算
  3. numStack := InitStack(20)
  4. for _, e := range exp {
  5. if unicode.IsNumber(e) {
  6. num, _ := strconv.ParseInt(string(e), 0, 64)
  7. numStack.Push(num)
  8. continue
  9. }
  10. // 符号
  11. cal(string(e), numStack)
  12. }
  13. result := numStack.Pop()
  14. return result
  15. }
  16. func cal(symbol string, stack *Stack) {
  17. sec, _ := stack.Pop().(int64)
  18. first, _ := stack.Pop().(int64)
  19. if "+" == symbol {
  20. stack.Push(first + sec)
  21. } else if "-" == symbol {
  22. stack.Push(first - sec)
  23. } else if "*" == symbol {
  24. stack.Push(first * sec)
  25. } else if "/" == symbol {
  26. stack.Push(first / sec)
  27. }
  28. }

测试

  1. func main() {
  2. // 待解析的表达式
  3. var str string = "1+1+(2+3)+3-3+4"
  4. fmt.Println("中缀表达式:", str)
  5. // 检查括号是否对齐
  6. bracketCheck(str)
  7. suffixExp := trans2SuffixExp(str)
  8. fmt.Println("后缀表达式:", suffixExp)
  9. result := calSuffixExp(suffixExp)
  10. fmt.Println("后缀表达式计算结果:", result)
  11. }

输出

中缀表达式: 1+1+(2+3)+3-3+4 后缀表达式: 11+23++3+3-4+ 后缀表达式计算结果: 11

通过中缀表达式一步计算

上文我们通过先将中缀表达式转后缀表达式完成后再计算,这里用到了两个栈帧,一个符号栈,一个数字栈,我们如果结合两个栈空间同样可以一步计算到位(过程中有转后缀及计算的含义)

  1. func parseExpression(str string) Element {
  2. symbolStack := InitStack(20)
  3. numStack := InitStack(20)
  4. var currNum = ""
  5. for _, ch := range str {
  6. ele := string(ch)
  7. if unicode.IsNumber(ch) {
  8. currNum += ele
  9. continue
  10. }
  11. // 操作数入栈
  12. if currNum != "" {
  13. num, _ := strconv.ParseInt(currNum, 0, 32)
  14. numStack.Push(num)
  15. currNum = ""
  16. }
  17. if symbolStack.isEmpty() || "(" == ele {
  18. // 第一个操作符直接入栈
  19. symbolStack.Push(ele)
  20. continue
  21. }
  22. topSymbol := symbolStack.TopEle()
  23. var symbolStr = fmt.Sprintf("%v", topSymbol)
  24. // 栈顶操作符的优先级更低,当前操作符直接入栈
  25. if symbolMap[symbolStr] < symbolMap[ele] && ")" != ele {
  26. symbolStack.Push(ele)
  27. continue
  28. }
  29. if "(" == symbolStr {
  30. symbolStack.Push(ele)
  31. continue
  32. }
  33. // 当前操作符优先级较高
  34. if ")" == ele {
  35. for {
  36. // 弹出栈顶操作符直到遇到“(”
  37. symbolStr = fmt.Sprintf("%v", symbolStack.Pop())
  38. if symbolStr == "(" {
  39. break
  40. }
  41. cal(symbolStr, numStack)
  42. }
  43. } else {
  44. for {
  45. symbolStr = fmt.Sprintf("%v", symbolStack.Pop())
  46. if symbolStr == "<nil>" {
  47. break
  48. } else if symbolMap[symbolStr] < symbolMap[ele] {
  49. symbolStack.Push(symbolStr)
  50. break
  51. }
  52. cal(symbolStr, numStack)
  53. }
  54. }
  55. // 当前操作符入栈
  56. if ")" != ele {
  57. symbolStack.Push(ele)
  58. }
  59. //
  60. }
  61. if currNum != "" {
  62. num, _ := strconv.ParseInt(currNum, 0, 32)
  63. numStack.Push(num)
  64. currNum = ""
  65. }
  66. // 计算剩余表达式
  67. for !symbolStack.isEmpty() {
  68. symbolStr := fmt.Sprintf("%v", symbolStack.Pop())
  69. cal(symbolStr, numStack)
  70. }
  71. // 操作数栈中栈顶元素就为结果:
  72. ret := numStack.Pop()
  73. return ret
  74. }
  75. func cal(symbol string, stack *Stack) {
  76. sec, _ := stack.Pop().(int64)
  77. first, _ := stack.Pop().(int64)
  78. if "+" == symbol {
  79. stack.Push(first + sec)
  80. } else if "-" == symbol {
  81. stack.Push(first - sec)
  82. } else if "*" == symbol {
  83. stack.Push(first * sec)
  84. } else if "/" == symbol {
  85. stack.Push(first / sec)
  86. }
  87. }