中缀转后缀表达式的计算器的实现过程

  • 中缀表达式是最常用的算术表达式形式——运算符在运算数中间。但运算时需要考虑运算符优先级。
    1 + 2 * 3

  • 后缀表达式是计算机容易运算的表达式,也叫逆波兰表达式,运算符在运算数后面,从左到右进行运算,无需考虑优先级,运算呈线性结构。
    1 2 3 * +

  • 中缀表达式转后缀表达式
    1.基础情况:
    遇到 操作数,直接存储/输出

    遇到 操作符
    1.栈为空或者该操作符的优先级比栈顶的操作符的优先级高 → 将该操作符压栈
    2.该操作符的优先级比栈顶的操作符优先级低 或者相同 → 弹出栈顶操作符存储,并将该操作符压栈
    3.遍历结束后将栈里的操作符依次全部弹出,依次存储在末尾(或者直接输出)

  • 在这里插入图片描述

2.原理分析:
我们以 n1 <O1> n2 <O2> n3 <O3> n4…… 为例加以说明,其中 ni 表示操作数,Oi 表示操作符。
如果 O1 的优先级高于 O2,那么毫无疑问,n2 会先与n1运算。
如果 O1 的优先级低于 O2,那么可以让 n2 与右操作数 n3 发生运算吗?答案是否定的!因为n3 能不能先与 n2 运算还取决于 O2 O3 运算符优先级的高低。但是目前我们可以确定 O2 运算符肯定在 O1 运算符前被使用。
O1操作符先进入,但是在情况二中因为优先级低,只能后运算。能够存储这种先进后出模式的数据结构,就是栈了

3.对于括号怎么处理:
我们不难发现,先出栈的运算符一定比后出栈的运算符先运算。而括号作为优先级最高的运算符,我们如何体现出它的“优越性”呢?
其实处理的办法很简单,进入左括号后遇到的第一个运算符无脑压栈,不管优先级高低。这样就可以保证优先运算。
可以将括号里的式子看成一个个独立的式子,式子的结束就是遇到右括号的时候。在结束时,我们需要把这个独立子式中的操作符依次全部弹出(左括号之上的操作符,之下的可不归你管)

4.中缀转后缀参考代码

// 操作符优先级
var precedence = map[string]int{
	"+": 1,
	"-": 1,
	"*": 2,
	"/": 2,
}

// 判断是否为操作符
func isOperator(token string) bool {
	return token == "+" || token == "-" || token == "*" || token == "/"
}

// 判断是否为数字
func isNumber(token string) bool {
	_, err := strconv.ParseFloat(token, 64)
	return err == nil
}

// 中缀转后缀
func infixToPostfix(infix []string) ([]string, error) {
	var postfix []string   // 存储后缀表达式的结果
	var operators []string // 存储操作符的栈
	for _, token := range infix {
		if isNumber(token) { // 如果元素是数字
			postfix = append(postfix, token) // 将数字直接添加到后缀表达式中
		} else if isOperator(token) { // 如果元素是操作符
			// 当栈不为空且栈顶操作符的优先级大于或等于当前操作符的优先级时
			if len(operators) > 0 && precedence[operators[len(operators)-1]] >= precedence[token] {
				// 将栈顶操作符移到后缀表达式中,并从栈中弹出
				postfix, operators = append(postfix, operators[len(operators)-1]), operators[:len(operators)-1]
			}
			// 将当前操作符推入栈中
			operators = append(operators, token)
		} else if token == "(" { // 如果元素是左括号
			// 将左括号推入栈中
			operators = append(operators, token)
		} else if token == ")" { // 如果元素是右括号
			// 当栈不为空且栈顶不是左括号时,将栈顶操作符移到后缀表达式中
			if len(operators) > 0 && operators[len(operators)-1] != "(" {
				postfix, operators = append(postfix, operators[len(operators)-1]), operators[:len(operators)-1]
			}
			// 如果栈为空,则括号不匹配
			if len(operators) == 0 {
				return nil, fmt.Errorf("mismatched parentheses")
			}
			// 弹出左括号
			operators = operators[:len(operators)-1]
		} else { // 如果元素既不是数字也不是操作符,也不是括号
			return nil, fmt.Errorf("invalid token: %s", token) // 返回错误
		}
	}

	// 将栈中剩余的操作符移到后缀表达式中
	for len(operators) > 0 {
		// 如果栈顶是左括号,则括号不匹配
		if operators[len(operators)-1] == "(" {
			return nil, fmt.Errorf("mismatched parentheses")
		}
		// 将操作符移到后缀表达式中,并从栈中弹出
		postfix, operators = append(postfix, operators[len(operators)-1]), operators[:len(operators)-1]
	}

	// 返回后缀表达式和nil错误
	return postfix, nil
}

5.进行求解

func evaluatePostfix(postfix []string) (float64, error) {
	var stack []float64 // 创建一个栈用于存储操作数
	for _, token := range postfix {
		// 尝试将元素解析为浮点数
		if num, err := strconv.ParseFloat(token, 64); err == nil {
			// 如果成功,将数字推入栈中
			stack = append(stack, num)
		} else {
			// 如果元素不是数字,则它必须是操作符
			// 检查栈中是否有足够的操作数
			if len(stack) < 2 {
				// 如果操作数不足,返回错误
				return 0, fmt.Errorf("invalid expression")
			}
			// 从栈中弹出两个操作数
			right := stack[len(stack)-1] // 右操作数
			stack = stack[:len(stack)-1] // 弹出右操作数
			left := stack[len(stack)-1]  // 左操作数
			stack = stack[:len(stack)-1] // 弹出左操作数

			// 根据操作符执行相应的计算
			switch token {
			case "+":
				// 加法
				stack = append(stack, left+right)
			case "-":
				// 减法
				stack = append(stack, left-right)
			case "*":
				// 乘法
				stack = append(stack, left*right)
			case "/":
				// 除法,检查除数是否为零
				if right == 0 {
					return 0, fmt.Errorf("division by zero")
				}
				stack = append(stack, left/right)
			default:
				// 如果操作符无效,返回错误
				return 0, fmt.Errorf("invalid operator: %s", token)
			}
		}
	}

	// 计算完成后,栈中应该只有一个元素,即表达式的结果
	if len(stack) != 1 {
		// 如果栈中元素不唯一,则表达式无效
		return 0, fmt.Errorf("invalid expression")
	}

	// 返回栈中的唯一元素,即表达式的计算结果
	return stack[0], nil
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值