目录
先吐槽一下:学习的时候以为实现逻辑蛮清晰简单的。代码写起来好几百行...可能是自己的代码能力太弱了。
2.4.1 问题描述:
表达式中出现单个减号,按照2.2中的运算逻辑不会有什么问题。但是如果出现两个连续减号如(7-4/2-3),其逻辑是:
(1)第一个减号与除号比较,减号入栈,除号与下一个运算符比较;
(2)遍历到第二个减号,比较后除号运算,第二个减号入栈,此时括号部分的表达式顺序变成了(7-2-3);
(3)遍历遇到右括号),按照逻辑,循环出栈符号进行运算,此时就会先计算2-3=1,再计算7-1=6。计算结果错误。
2.4.2 解决办法:
可以声明一个标记变量,标记符号是否为减号。如果遇到了减号,将减号改为加号入栈,且下一个数字前添加负号再入栈,然后再将标记还原。
此逻辑处理后,上述例子就会变成(7+(-2)+(-3)),(-2)+(-3)=-5,7+(-5)=2。计算结果正确。
以上将中缀表达式转换为后缀表达式计算的时间复杂度可以达到O(n)
考虑到表达式可能长度无法预估,和确保操作的便利性。我选择栈的链式存储结构-链栈来实现。
1.链栈的定义
- type LinkStack struct {
- data interface{}
- next *LinkStack
- }
2.栈的初始化、入栈、出栈
- //链栈初始化
- func initLinkStack() (linkStack *LinkStack) {
- //初始化链栈,新建链栈头节点
- linkStack = &LinkStack{
- data: "stackHeader",
- next: nil,
- }
- return linkStack
- }
-
- //链栈-入栈
- func (s *LinkStack) push(v interface{}) {
- //链栈可先不考虑栈满,因为目前没有对栈做限制
-
- pushNode := &LinkStack{
- data: v,
- next: nil,
- }
-
- pushNode.next = s.next //入栈节点的next指向头节点的next节点
- s.next = pushNode //头节点指向入栈节点
- }
-
- //链栈-出栈
- func (s *LinkStack) pop() (interface{}, error) {
- //判断栈是否为空
- if s.next == nil {
- return "", errors.New("error: 栈为空")
- }
-
- tmpTop := s.next
- v := tmpTop.data //出栈,获取节点的元素值
-
- s.next = tmpTop.next //头节点指向原栈顶节点的下一个节点
- tmpTop.next = nil //原栈顶节点指向nil
-
- return v, nil
- }
1.判断运算符优先级
- //判断运算符号优先级
- func judgmentType(symbol string) int {
- switch symbol {
- case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
- return 0
- case "+", "-":
- return 1
- case "*", "/":
- return 2
- case "(":
- return 3
- case ")":
- return 4
- default:
- return -1
- }
- }
2.判断运算符后对数字进行计算
- //判断运算符号并对数值进行计算
- func calculatedFormula(num1 int, num2 int, operator string) int {
- switch operator {
- case "+":
- fmt.Println("提示: 计算num1 + num2")
- return num1 + num2
- case "-":
- fmt.Println("提示: 计算num1 - num2")
- return num1 - num2
- case "*":
- fmt.Println("提示: 计算num1 * num2")
- return num1 * num2
- case "/":
- fmt.Println("提示: 计算num1 / num2")
- return num1 / num2
- default:
- fmt.Println("提示: 运算符号没有对应计算方法")
- return -9999
- }
- }
1.循环-遇到右括号),或遍历完表达式后,循环出栈&计算数值
- //循环出栈symbolLinkStack中的符号,并进行运算。如遇到左括号(或栈为空时结束循环。
- func loopCalculationStack(numLinkStack *LinkStack, symbolLinkStack *LinkStack) {
- for {
- if symbolLinkStack.next == nil {
- fmt.Println("栈中无运算符号")
- break
- }
- tmpSymbol, _ := symbolLinkStack.pop()
- preSymbol := tmpSymbol.(string)
- fmt.Println("-旧符号出栈-", preSymbol)
-
- if preSymbol == "(" {
- fmt.Println("提示: 遇到(, (出栈, 结束遍历符号栈的计算操作。")
- break
- } else {
- //两数根据符号进行运算,并入栈
- calculatedNum(numLinkStack, preSymbol)
- }
- }
- }
2.单次-符号出栈&数字化计算
- //两数根据符号进行运算,并入栈
- func calculatedNum(numLinkStack *LinkStack, symbol string) {
- v2, err := numLinkStack.pop()
- if err != nil {
- fmt.Println(err)
- }
- v1, err := numLinkStack.pop()
- if err != nil {
- fmt.Println(err)
- }
-
- num1 := v1.(int)
- num2 := v2.(int)
- fmt.Printf("-旧数字出栈- num2: %v; num1: %v\n", num2, num1)
-
- calculatedValue := calculatedFormula(num1, num2, symbol)
- //计算数值入栈
- fmt.Println("-计算结果入栈-", calculatedValue)
- numLinkStack.push(calculatedValue)
- }
3.遍历表达式后的出入栈主体逻辑
实际代码
- func calculationFormulaResults(symbol string, numLinkStack *LinkStack, symbolLinkStack *LinkStack) (int, error) {
- fmt.Println("---遍历公式---")
- symbolTag := -1 //符号标记。判断数字前面符号是否为-,如果是减号,则将减号存为加号+,且数字取本身的负数。
- numTag := -1 //数字标记。判断是否为多位数
- symbolLoopTimes := 0 //记录公式循环到第几次
- for _, s := range symbol {
- symbolLoopTimes++
- fmt.Printf("----%v----\n", symbolLoopTimes)
- sType := judgmentType(string(s))
-
- if sType == 0 {
- //公式中的数字直接入栈。如果遍历连续数字,说明是多位数,需要拼接多位数。
- var num int
- if numTag == 1 {
- prenumTmp, _ := numLinkStack.pop()
- prenumI := prenumTmp.(int)
- prenumA := strconv.Itoa(prenumI)
- fmt.Println(prenumA + string(s))
- num, _ = strconv.Atoi(prenumA + string(s))
- } else {
- num, _ = strconv.Atoi(string(s))
- }
-
- //如果数字前的符号为减号,则改为加号,且数字变为负数。
- if symbolTag == 1 {
- symbolLinkStack.pop() //减号出栈
- symbolLinkStack.push("+") //加号入栈
- fmt.Println("提示:数字前面为负号,执行加号入栈、数字加负号")
- num = -num //数字添加负号
- symbolTag = -1 //操作结束,还原符号标记为-1
- }
-
- fmt.Println("-新数字入栈-", num)
- numLinkStack.push(num)
-
- numTag = 1
- } else if sType == 1 || sType == 2 {
- numTag = -1
- //符号如果为符号,修改符号标记为1
- if string(s) == "-" {
- symbolTag = 1
- }
-
- if symbolLinkStack.next == nil {
- fmt.Println("-新符号入栈-", string(s))
- symbolLinkStack.push(string(s))
- } else {
- tmpSymbol := symbolLinkStack.next.data //查看栈顶符号
- preSymbol, _ := tmpSymbol.(string)
- nowSymbol := string(s)
-
- //获取当前符合和前一个符号的优先级,如果符号是(,则(优先级最低
- var prePriority int
- if preSymbol == "(" {
- fmt.Println("-前一个符号是(-")
- prePriority = 0
- } else {
- prePriority = judgmentType(preSymbol)
- }
- nowPriority := judgmentType(nowSymbol)
-
- if prePriority >= nowPriority { //如果前一个运算符优先级大于后一个运算符优先级
- fmt.Printf("提示: 栈顶符号%v优先级大于或等于当前符号%v优先级\n", preSymbol, nowSymbol)
- fmt.Println("-旧符号出栈-", preSymbol)
- symbolLinkStack.pop() //栈顶旧运算符弹出
- fmt.Println("-新符号入栈-", nowSymbol)
- symbolLinkStack.push(nowSymbol) //新运算符入栈
-
- //两数根据符号进行运算,并入栈
- calculatedNum(numLinkStack, preSymbol)
-
- } else if prePriority < nowPriority { //如果前一个运算符优先级小于后一个运算符优先级
- fmt.Printf("提示: 栈顶符号%v优先级小于当前符号%v优先级\n", preSymbol, nowSymbol)
- fmt.Println("-新符号入栈-", nowSymbol)
- symbolLinkStack.push(nowSymbol)
- }
- }
- } else if sType == 3 {
- fmt.Println("-左括号直接入栈-", string(s))
- symbolLinkStack.push(string(s))
- symbolTag = -1 //左括号前如有减号,还原符号标记为-1
- } else if sType == 4 {
- fmt.Println("-右括号不入栈-", string(s))
- //遇到),循环出栈所有栈中运算符并计算。遇到(时停止
- loopCalculationStack(numLinkStack, symbolLinkStack)
- } else {
- return -1, errors.New("公式格式不合法")
- }
- }
-
- // 遍历完公式后,计算栈内剩余的元素
- fmt.Println("---公式遍历结束,计算栈中剩余元素---")
- loopCalculationStack(numLinkStack, symbolLinkStack)
-
- //所有计算完成后,结果被存入numLinkStack,将结果出栈
- resultTmp, err := numLinkStack.pop()
- result := resultTmp.(int)
- if err != nil {
- fmt.Println("获取计算公式最终返回值时报错:", err)
- }
- return result, nil
- }
伪代码解释
- func calculationFormulaResults(表达式, 存数字的栈, 存符号的栈) (计算结果, 报错信息) {
- #初始化一些必要的标记和变量
-
- for 循环遍历表达式 {
- if 如果遍历的是数字 {
- 数字直接入栈
- #1.考虑是否为多为数字
- #2.考虑数字前是否为减号
- }else if 如果遍历的是加、减、乘、除号 {
- if 如果符号栈是空的 {
- 第一个符号就直接入栈
- }else 栈不是空 {
- 1.通过工具函数judgmentType,获取符号的优先级
- #如果出栈的前一个符号是左括号(,那么左括号的优先级比所有符号都低。
-
- 2.比较前一个符号和后一个符号的优先级
- if 前面符号的优先级 >= 当前符号的优先级 {
- 1.用前一个符号计算两数的数值,然后数值入栈
- 2.当前符号入栈
- } else if 前面符号的优先级 < 当前符号的优先级{
- 1.当前符号入栈,等待与后面符号比较优先级
- }
- }
- }else if 如果遍历的是左括号( {
- 1.左括号直接入栈
- }else if 如果遍历的是右括号 {
- #右括号不入栈
- 1.遍历出栈并计算栈中的数字和符号,直到遇到左括号(
- }else {
- 如果是其他字符,则报错公式格式不合法。
- }
- }
-
- #for循环遍历表达式结束
- 1.遍历数字和字符栈中剩余的所有元素,并进行计算。
- 2.retrun 计算结果
- }
- package main
-
- import (
- "errors"
- "fmt"
- "strconv"
- )
-
- type LinkStack struct {
- data interface{}
- next *LinkStack
- }
-
- func main() {
- //初始化链栈
- fmt.Println("---初始化链栈---")
- numLinkStack := initLinkStack()
- symbolLinkStack := initLinkStack()
-
- //定义一条四则运算
- // formula := "1+3*(4/2+1)+3*3+3/(7-6/3-2)*2"
- formula := "(43-40)*s(2-(8+3))"
- fmt.Println("计算公式: ", formula)
-
- //计算
- result, err := calculationFormulaResults(formula, numLinkStack, symbolLinkStack)
- if err != nil {
- fmt.Println("error!!!: ", err)
- } else {
- fmt.Printf("\n\n---最终计算结果---\n")
- fmt.Println("计算公式: ", formula)
- fmt.Println("公式计算结果: ", result)
- }
-
- }
-
- func initLinkStack() (linkStack *LinkStack) {
- //初始化链栈,新建链栈头节点
- linkStack = &LinkStack{
- data: "stackHeader",
- next: nil,
- }
- return linkStack
- }
-
- func (s *LinkStack) push(v interface{}) {
- //链栈可先不考虑栈满,因为目前没有对栈做限制
-
- pushNode := &LinkStack{
- data: v,
- next: nil,
- }
-
- pushNode.next = s.next //入栈节点的next指向头节点的next节点
- s.next = pushNode //头节点指向入栈节点
- }
-
- func (s *LinkStack) pop() (interface{}, error) {
- //判断栈是否为空
- if s.next == nil {
- return "", errors.New("error: 栈为空")
- }
-
- tmpTop := s.next
- v := tmpTop.data //出栈,获取节点的元素值
-
- s.next = tmpTop.next //头节点指向原栈顶节点的下一个节点
- tmpTop.next = nil //原栈顶节点指向nil
-
- return v, nil
- }
-
- //判断运算符号优先级
- func judgmentType(symbol string) int {
- switch symbol {
- case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
- return 0
- case "+", "-":
- return 1
- case "*", "/":
- return 2
- case "(":
- return 3
- case ")":
- return 4
- default:
- return -1
- }
- }
-
- //判断运算符号并对数值进行计算
- func calculatedFormula(num1 int, num2 int, operator string) int {
- switch operator {
- case "+":
- fmt.Println("提示: 计算num1 + num2")
- return num1 + num2
- case "-":
- fmt.Println("提示: 计算num1 - num2")
- return num1 - num2
- case "*":
- fmt.Println("提示: 计算num1 * num2")
- return num1 * num2
- case "/":
- fmt.Println("提示: 计算num1 / num2")
- return num1 / num2
- default:
- fmt.Println("提示: 运算符号没有对应计算方法")
- return -9999
- }
- }
-
- func calculationFormulaResults(symbol string, numLinkStack *LinkStack, symbolLinkStack *LinkStack) (int, error) {
- fmt.Println("---遍历公式---")
- symbolTag := -1 //符号标记。判断数字前面符号是否为-,如果是减号,则将减号存为加号+,且数字取本身的负数。
- numTag := -1 //数字标记。判断是否为多位数
- symbolLoopTimes := 0 //记录公式循环到第几次
- for _, s := range symbol {
- symbolLoopTimes++
- fmt.Printf("----%v----\n", symbolLoopTimes)
- sType := judgmentType(string(s))
-
- if sType == 0 {
- //公式中的数字直接入栈。如果遍历连续数字,说明是多位数,需要拼接多位数。
- var num int
- if numTag == 1 {
- prenumTmp, _ := numLinkStack.pop()
- prenumI := prenumTmp.(int)
- prenumA := strconv.Itoa(prenumI)
- fmt.Println(prenumA + string(s))
- num, _ = strconv.Atoi(prenumA + string(s))
- } else {
- num, _ = strconv.Atoi(string(s))
- }
-
- //如果数字前的符号为减号,则改为加号,且数字变为负数。
- if symbolTag == 1 {
- symbolLinkStack.pop() //减号出栈
- symbolLinkStack.push("+") //加号入栈
- fmt.Println("提示:数字前面为负号,执行加号入栈、数字加负号")
- num = -num //数字添加负号
- symbolTag = -1 //操作结束,还原符号标记为-1
- }
-
- fmt.Println("-新数字入栈-", num)
- numLinkStack.push(num)
-
- numTag = 1
- } else if sType == 1 || sType == 2 {
- numTag = -1
- //符号如果为符号,修改符号标记为1
- if string(s) == "-" {
- symbolTag = 1
- }
-
- if symbolLinkStack.next == nil {
- fmt.Println("-新符号入栈-", string(s))
- symbolLinkStack.push(string(s))
- } else {
- tmpSymbol := symbolLinkStack.next.data //查看栈顶符号
- preSymbol, _ := tmpSymbol.(string)
- nowSymbol := string(s)
-
- //获取当前符合和前一个符号的优先级,如果符号是(,则(优先级最低
- var prePriority int
- if preSymbol == "(" {
- fmt.Println("-前一个符号是(-")
- prePriority = 0
- } else {
- prePriority = judgmentType(preSymbol)
- }
- nowPriority := judgmentType(nowSymbol)
-
- if prePriority >= nowPriority { //如果前一个运算符优先级大于后一个运算符优先级
- fmt.Printf("提示: 栈顶符号%v优先级大于或等于当前符号%v优先级\n", preSymbol, nowSymbol)
- fmt.Println("-旧符号出栈-", preSymbol)
- symbolLinkStack.pop() //栈顶旧运算符弹出
- fmt.Println("-新符号入栈-", nowSymbol)
- symbolLinkStack.push(nowSymbol) //新运算符入栈
-
- //两数根据符号进行运算,并入栈
- calculatedNum(numLinkStack, preSymbol)
-
- } else if prePriority < nowPriority { //如果前一个运算符优先级小于后一个运算符优先级
- fmt.Printf("提示: 栈顶符号%v优先级小于当前符号%v优先级\n", preSymbol, nowSymbol)
- fmt.Println("-新符号入栈-", nowSymbol)
- symbolLinkStack.push(nowSymbol)
- }
- }
- } else if sType == 3 {
- fmt.Println("-左括号直接入栈-", string(s))
- symbolLinkStack.push(string(s))
- symbolTag = -1 //左括号前如有减号,还原符号标记为-1
- } else if sType == 4 {
- fmt.Println("-右括号不入栈-", string(s))
- //遇到),循环出栈所有栈中运算符并计算。遇到(时停止
- loopCalculationStack(numLinkStack, symbolLinkStack)
- } else {
- return -1, errors.New("公式格式不合法")
- }
- }
-
- // 遍历完公式后,计算栈内剩余的元素
- fmt.Println("---公式遍历结束,计算栈中剩余元素---")
- loopCalculationStack(numLinkStack, symbolLinkStack)
-
- //所有计算完成后,结果被存入numLinkStack,将结果出栈
- resultTmp, err := numLinkStack.pop()
- result := resultTmp.(int)
- if err != nil {
- fmt.Println("获取计算公式最终返回值时报错:", err)
- }
- return result, nil
- }
-
- //循环出栈symbolLinkStack中的符号,并进行运算。如遇到左括号(或栈为空时结束循环。
- func loopCalculationStack(numLinkStack *LinkStack, symbolLinkStack *LinkStack) {
- for {
- if symbolLinkStack.next == nil {
- fmt.Println("栈中无运算符号")
- break
- }
- tmpSymbol, _ := symbolLinkStack.pop()
- preSymbol := tmpSymbol.(string)
- fmt.Println("-旧符号出栈-", preSymbol)
-
- if preSymbol == "(" {
- fmt.Println("提示: 遇到(, (出栈, 结束遍历符号栈的计算操作。")
- break
- } else {
- //两数根据符号进行运算,并入栈
- calculatedNum(numLinkStack, preSymbol)
- }
- }
- }
-
- //两数根据符号进行运算,并入栈
- func calculatedNum(numLinkStack *LinkStack, symbol string) {
- v2, err := numLinkStack.pop()
- if err != nil {
- fmt.Println(err)
- }
- v1, err := numLinkStack.pop()
- if err != nil {
- fmt.Println(err)
- }
-
- num1 := v1.(int)
- num2 := v2.(int)
- fmt.Printf("-旧数字出栈- num2: %v; num1: %v\n", num2, num1)
-
- calculatedValue := calculatedFormula(num1, num2, symbol)
- //计算数值入栈
- fmt.Println("-计算结果入栈-", calculatedValue)
- numLinkStack.push(calculatedValue)
- }

由于我的代码中增加了很多输出提示类信息,所以感兴趣代码运行过程的话(数字、运算符出入栈和计算过程) ,可以看一下的详细输出。
- ---初始化链栈---
- 计算公式: (43-40)*(2-(8+3))
- ---遍历公式---
- ----1----
- -左括号直接入栈- (
- ----2----
- -新数字入栈- 4
- ----3----
- 43
- -新数字入栈- 43
- ----4----
- -前一个符号是(-
- 提示: 栈顶符号(优先级小于当前符号-优先级
- -新符号入栈- -
- ----5----
- 提示:数字前面为负号,执行加号入栈、数字加负号
- -新数字入栈- -4
- ----6----
- -40
- -新数字入栈- -40
- ----7----
- -右括号不入栈- )
- -旧符号出栈- +
- -旧数字出栈- num2: -40; num1: 43
- 提示: 计算num1 + num2
- -计算结果入栈- 3
- -旧符号出栈- (
- 提示: 遇到(, (出栈, 结束遍历符号栈的计算操作。
- ----8----
- -新符号入栈- *
- ----9----
- -左括号直接入栈- (
- ----10----
- -新数字入栈- 2
- ----11----
- -前一个符号是(-
- 提示: 栈顶符号(优先级小于当前符号-优先级
- -新符号入栈- -
- ----12----
- -左括号直接入栈- (
- ----13----
- -新数字入栈- 8
- ----14----
- -前一个符号是(-
- 提示: 栈顶符号(优先级小于当前符号+优先级
- -新符号入栈- +
- ----15----
- -新数字入栈- 3
- ----16----
- -右括号不入栈- )
- -旧符号出栈- +
- -旧数字出栈- num2: 3; num1: 8
- 提示: 计算num1 + num2
- -计算结果入栈- 11
- -旧符号出栈- (
- 提示: 遇到(, (出栈, 结束遍历符号栈的计算操作。
- ----17----
- -右括号不入栈- )
- -旧符号出栈- -
- -旧数字出栈- num2: 11; num1: 2
- 提示: 计算num1 - num2
- -计算结果入栈- -9
- -旧符号出栈- (
- 提示: 遇到(, (出栈, 结束遍历符号栈的计算操作。
- ---公式遍历结束,计算栈中剩余元素---
- -旧符号出栈- *
- -旧数字出栈- num2: -9; num1: 3
- 提示: 计算num1 * num2
- -计算结果入栈- -27
- 栈中无运算符号
-
-
- ---最终计算结果---
- 计算公式: (43-40)*(2-(8+3))
- 公式计算结果: -27