算法设计核心
这个问题属于表达式解析的典型应用,需要处理操作符优先级、括号嵌套以及多种运算类型(加、减、乘、除)。实现的重点包括以下几点:
-
优先级的设计:
- 在常见的数学表达式中,括号优先级最高,接下来是乘法和除法,最后是加法和减法。
- 在解析过程中,我们使用
precedence(op)函数来确定当前操作符的优先级,从而决定是将操作符压入栈还是弹出并计算栈顶的表达式。
-
两栈法的设计:
- 操作数栈(
values) :用于存储当前解析到的数字。 - 操作符栈(
ops) :用于存储解析到的运算符以及括号。 - 每当需要计算时,从这两个栈中弹出对应的值和操作符,完成一次计算后将结果压回
values栈。
- 操作数栈(
-
括号的处理:
- 左括号
(表示一个新的优先级范围,当遇到右括号)时,弹出并计算所有操作,直到遇到对应的左括号(。 - 这部分处理是为了确保括号内的表达式优先计算。
- 左括号
-
数字解析:
- 输入字符串可能包含多位数(例如
123),因此需要一个循环解析完整的数字,而不仅仅是单个字符。
- 输入字符串可能包含多位数(例如
具体实现说明
代码的实现严格按照上述逻辑展开,每一部分都经过精心设计以确保正确性和效率:
-
优先级函数
precedence(op):- 通过简单的条件判断返回操作符的优先级,支持动态扩展。如果将来需要支持其他操作符(如幂运算
^),只需要在这里添加对应规则。
- 通过简单的条件判断返回操作符的优先级,支持动态扩展。如果将来需要支持其他操作符(如幂运算
-
运算函数
apply_op(a, b, op):- 该函数对两个操作数
a和b应用操作符op,实现加减乘除功能。 - 除法部分采用整除运算
//,符合题目要求。
- 该函数对两个操作数
-
栈顶计算
calculate():- 这是核心的计算函数,负责从
values和ops栈中弹出数据并完成一次运算,将结果重新压入values栈。
- 这是核心的计算函数,负责从
-
主循环的逻辑:
-
遍历输入字符串时,根据当前字符的类型(数字、操作符、括号)分别处理:
- 如果是数字,解析完整的数字并压入
values栈。 - 如果是操作符,比较优先级并决定是压栈还是弹栈计算。
- 如果是括号,按照括号的规则处理。
- 如果是数字,解析完整的数字并压入
-
-
最终计算:
- 当字符串遍历结束后,
ops栈中可能还存在未处理的操作符(例如1+2+3),需要继续弹栈计算,直到所有操作完成。
- 当字符串遍历结束后,
代码的扩展性
这个实现非常灵活,可以扩展支持更多功能:
-
支持更多操作符:
- 例如幂运算(
^),可以在precedence和apply_op中添加对应规则。 - 新增操作符时,只需确保优先级规则正确,主循环无需修改。
- 例如幂运算(
-
处理浮点数:
- 当前实现中,所有运算都是整数运算。如果需要支持小数,可以将数字解析为浮点数,并将整除运算
//替换为正常除法/。
- 当前实现中,所有运算都是整数运算。如果需要支持小数,可以将数字解析为浮点数,并将整除运算
-
空格处理:
- 如果输入字符串可能包含空格,可以在遍历时跳过空格,或预先将字符串中的空格移除。
-
错误检测:
- 如果输入表达式无效(如括号不匹配,或包含非法字符),可以在主循环中添加检查逻辑,抛出异常或返回错误信息。
时间和空间复杂度
-
时间复杂度:
- 每个字符最多进栈和出栈一次,因此时间复杂度为 O(n)O(n),其中 nn 是输入字符串的长度。
- 复杂操作(如计算括号内的表达式)受益于栈的结构,避免了多次重复计算。
-
空间复杂度:
- 空间复杂度为 O(n)O(n),主要由栈的深度决定。在最坏情况下(例如大量嵌套括号),栈的最大深度为字符串长度 nn。
样例详解
样例 1:1+1
-
遍历字符:
- 解析
1,压入values栈。 - 遇到
+,压入ops栈。 - 解析第二个
1,压入values栈。
- 解析
-
弹栈计算:
1 + 1 = 2,结果为 2。
样例 2:3+4*5/(3+2)
-
按优先级计算:
- 先计算括号内的
(3+2),结果为5。 - 再计算乘除部分
4*5/5,结果为4。 - 最后计算加法
3 + 4,结果为 7。
- 先计算括号内的
样例 5:2*(5+5*2)/3+(6+8*3)
-
按括号和优先级解析:
- 括号内计算
5 + 5*2,结果为15。 - 外部计算乘法和除法:
2*15/3,结果为10。 - 计算加法和乘法:
10 + 6 + 8*3,结果为 40。
- 括号内计算
通过以上解析和实现,代码实现了高效、正确的表达式求值功能,同时提供了良好的扩展性和稳定性。这种方法可以应用于更复杂的表达式解析问题,适用于多种场景。