写点什么

算法 | 使用栈计算表达式

作者:甜点cc
  • 2022-10-26
    河南
  • 本文字数:3009 字

    阅读完需:约 10 分钟

本篇采用 go 语言编写

1、计算表达式

输入一个表达式,返回计算结果。比如: 3 + 2 * 6 - 2 = 13


说明:会按照运算符的优先级自动进行正确的运算

2、算法设计思路

  1. 创建两个栈,numStack, operStack

  2. numStack 存放数,operStack 操作符

  3. index:=0,

  4. exp 计算表达式,是一个字符串

  5. 如果扫描发现是一个数字,则直接入 numstack

  6. 如果发现是一个运算符。

  7. (1)如果 operStack 是一个空栈,直接入栈

  8. (2)如果 operStack 不是一个空栈

  9. (2.1)如果发现 opertStack 栈顶的运算符的优先级大于等于当前准备入栈的运算符的优先级,就从符号栈 pop 出,并从数栈也 pop 两个数,进行运算,运算后的结果再重新入栈到数栈,符号再入符号栈

  10. (2.2)否则,运算符就直接入栈

  11. 如果扫描表达式完毕,依次从符号栈取出符号,然后从数栈取出两个数,运算后的结果,入数栈,直到符号栈为空

2.1、关键操作示意图

图一:



上面示意图中运算 “2x6” 之后,把运算结果“12”重新 push 到数栈,然后再把操作符“-”push 到符号栈,请看下图👇


图二:


2.2、其它需要注意的

  1. 定义数栈、符号栈,需要的变量

  2. 出入栈操作

  3. 利用 ASCII 码判断是否是运算符,以及比较运算符之间的优先级

  4. 多位数拼接处理

  5. 边界问题处理


核心思路清楚之后,写代码就会变得简单且非常有条理了😊,具体请看下文👇

3、代码实现

package main
import ( "errors" "fmt" "strconv")
type Stack struct { MaxTop int // 栈的容量 Top int // 栈顶下标 默认 -1 arr [20]int // 数组模拟栈}
// 入栈func (stack *Stack) Push(val int) (err error) { // 栈满 if stack.Top == stack.MaxTop-1 { fmt.Println("stack full") return errors.New("stack full") } stack.Top++ // 放入数据 stack.arr[stack.Top] = val return}
// 出栈func (stack *Stack) Pop() (val int, err error) { if stack.Top == -1 { fmt.Println("空栈") return 0, errors.New("空栈") } // 取出数据 val = stack.arr[stack.Top] stack.Top-- return val, nil}
// 遍历栈func (stack *Stack) List() { if stack.Top == -1 { fmt.Println("空栈") return }
for i := stack.Top; i >= 0; i-- { fmt.Printf("arr[%d] = %v \n", i, stack.arr[i]) }
}
// 判断是运算符的函数 + - * / , 利用 ASCII 码func (stack *Stack) IsOper(val int) bool { if val == 42 || val == 43 || val == 45 || val == 47 { return true } else { return false }}
// 运算的方法func (stack *Stack) Cal(num1 int, num2 int, oper int) int { // 注意运算的顺序 res := 0 switch oper { case 42: res = num2 * num1 case 43: res = num2 + num1 case 45: res = num2 - num1 case 47: res = num2 / num1 default: fmt.Println("运算符错误") } return res}
// 返回运算符的优先级: * / => 1 ; + - => 0func (stack *Stack) Priority(oper int) int { res := 0 if oper == 42 || oper == 47 { res = 1 } else if oper == 43 || oper == 45 { res = 0 } return res}
func main() { // 数栈 numStack := &Stack{ MaxTop: 20, Top: -1, }
// 符号栈 operStack := &Stack{ MaxTop: 20, Top: -1, }
// 表达式 exp := "33+2*6/1-2" // 字符串本身也是切片
// 定义需要的变量 num1 := 0 num2 := 0 oper := 0 keepNum := "" // 字符串 用于拼接多位数
// 定义索引,扫描表达式 index := 0 for { ch := exp[index : index+1] // 每次拿到一个字符, 如果数字大于1位就会出问题, 在非符号字符入栈时处理 temp := int([]byte(ch)[0]) // 字符对应的 ASCII 码
if operStack.IsOper(temp) { // 符号 if operStack.Top == -1 { // 1. 符号栈为空 operStack.Push(temp) } else { // 2. 符号栈不为空 // 2.1 栈中的符号优先级大于等于即将入栈的符号 if operStack.Priority(operStack.arr[operStack.Top]) >= operStack.Priority(temp) { num1, _ = numStack.Pop() num2, _ = numStack.Pop() oper, _ = operStack.Pop() result := operStack.Cal(num1, num2, oper) numStack.Push(result) operStack.Push(temp) } else { // 2.2 栈中的符号优先级小于即将入栈的符号 operStack.Push(temp) } } } else { // 数字
// 多位数拼接 keepNum += ch // 判断扫描的下一位是不是运算符, 边界问题(表达式最后, 预防溢出) if index == len(exp)-1 { // 字符 转数字 val, _ := strconv.Atoi(keepNum) // Atoi是ParseInt(s, 10, 0)的简写。 返回的就是 int 类型 numStack.Push(val) } else { // 向后看一位 if operStack.IsOper(int([]byte(exp[index+1 : index+2])[0])) { val, _ := strconv.Atoi(keepNum) // Atoi是ParseInt(s, 10, 0)的简写。 返回的就是 int 类型 numStack.Push(val) keepNum = "" } // 其它情况不做处理,index++, 判断下一个字符 }
/* // 字符 转数字 // val, _ := strconv.ParseInt(ch, 10, 0) // 返回 int64, 需要转 int(val) val, _ := strconv.Atoi(ch) // Atoi是ParseInt(s, 10, 0)的简写。 返回的就是 int 类型 numStack.Push(val) */ }
// 判断扫描位置 if index+1 == len(exp) { fmt.Println("扫描到最后了") break } index++ }
// 扫描完毕,之后对数栈和符号栈剩余元素处理 for { if operStack.Top == -1 { fmt.Println("没有运算符了, 数栈中的值就是计算结果") break } num1, _ = numStack.Pop() num2, _ = numStack.Pop() oper, _ = operStack.Pop() result := operStack.Cal(num1, num2, oper) numStack.Push(result) }
// 如果计算正确,数栈中只剩下一个元素,就是计算结果 res, _ := numStack.Pop() fmt.Printf("表达式: %s = %v \n", exp, res)}
复制代码

4、输出结果

扫描到最后了没有运算符了, 数栈中的值就是计算结果表达式: 33+2*6/1-2 = 43 
复制代码

5、小结

  1. 处理这个问题,就是要让程序像人脑一样思维,会有一个“前后看一下”然后比对的效果。

  2. 因为代码是顺序执行的,所以需要把依次读取到的表达式元素按照数和符号分开保存起来,按符号优先级进行数的运算

  3. 数据结构采用——栈,利用 “栈” 的特点:一个先入后出(FILO-First In Last Out)的有序列表。最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除



我是 甜点 cc


热爱前端开发,也喜欢专研各种跟本职工作关系不大的技术,技术、产品兴趣广泛且浓厚。本号主要致力于分享个人经验总结,希望可以给一小部分人一些微小帮助。


希望能和大家一起努力营造一个良好的学习氛围,为了个人和家庭、为了我国的互联网物联网技术、数字化转型、数字经济发展做一点点贡献。数风流人物还看中国、看今朝、看你我。

发布于: 刚刚阅读数: 3
用户头像

甜点cc

关注

看见另一种可能 2020-04-30 加入

欢喜勇猛

评论

发布
暂无评论
算法 | 使用栈计算表达式_Go_甜点cc_InfoQ写作社区