算法 | 使用栈计算表达式

本文使用go语言编写

1、计算表达式

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

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

2、算法设计思路

  1. 创建两个栈,numStack, operStack

  2. numStack存放数,operStack操作符

  3. index:=0,

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

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

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

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

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

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

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

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

2.1、关键操作示意图

图一:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBcnPPkj-1666755089110)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c637c26491c34ba3a7ddae5b82adf225~tplv-k3u1fbpfcp-watermark.image?)]

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

图二:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-un0AGqE5-1666755089111)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f10c4f4bcbb14330a09162ffaf51a151~tplv-k3u1fbpfcp-watermark.image?)]

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 ; + - => 0
func (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

微信公众号:【看见另一种可能】

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

甜点cc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值