问题引出
逆波兰式的递归定义如下
如果算术表达式
E = (E')
,则RPN(E) = E'
如果算术表达式E = E1 OPERAND E2
,则RPN(E) = RPN(E1) RPN(E2) OPERAND
注:RPN 表示 Reverse Polish notation
其中问题求解的难点在于,对于第二条规则如何分割子串。编译原理课上老师回避了这个问题,而使用了常见的算符栈的方法求解,但仔细思考,可以通过程序方法找到分割位置。
解决方案
通过初步观察可知,分割点算符常常比左右相邻算符优先级低,我们可以利用算符栈算法中的算符优先矩阵得到一条表达式的优先关系,但为了编程方便,我们在表达式两端加入‘#’,并规定‘#’的优先级无限大。例:
表达式a + b * c
在两端加上‘#’得到# a + b * c #
,计算优先级可得# > + < * < #
。所以‘+’为优先级的最低点,从‘+’处分割子串。
进一步考虑括号的问题,我们得出结论,由于括号的优先级永远是最高的,分割点一定不再括号之中。于是我们可以巧妙的设置一个标志变量bracket,在扫描到左括号时bracket -= 1
,在扫描到右括号时,bracket += 1
。只有在bracket == 0
时才判断是否为分割点。若找不到分割点则说明括号不匹配,表达式语法出错。
另外考虑逆波兰式定义的第一条,在括号在表达式两端匹配时,应该去除括号。
由此,我们得到了递归法求解算术表达式的完整算法:
(1) 如果
len(E) == 1
则返回int(E)
(2) 如果算术表达式形如E = (E')
则E = E'
(3) 初始化:OP = i = bracket = 0
, 表达式两端加‘#’
(4) 循环:
(4.1) 如果E[i] == '('
则bracket -= 1
(4.2) 否则E[i] == ')'
则bracket += 1
(4.3) 如果bracket == 0
且E[i]的优先级小于E[OP]
则OP = i
(4.4)i += 1
(5) 返回RPN(E[0:OP]) RPN(E[OP + 1:]) E[OP]
Python实现
注意本代码求值,而上述伪代码求逆波兰式,两者在一些细节上略有不同
def trim_bracket(exp):
while (exp[0], exp[-1]) == ('(', ')'):
exp = exp[1: -1]
return exp
def calculate(exp, prior_matrix):
if len(exp) == 1:
return int(exp)
exp = "#" + exp + "#"
op = i = bracket = 0
while i < len(exp):
bracket += {'(': -1, ')': 1}.get(exp[i], 0)
if bracket == 0 and prior_matrix[exp[op]].get(exp[i], -1) > 0:
op = i
i += 1
val_1 = calculate(trim_bracket(exp[1: op]), prior_matrix)
val_2 = calculate(trim_bracket(exp[op + 1: -1]), prior_matrix)
return val_1 + val_2 if exp[op] == '+' else val_1 - val_2 if exp[op] == '-' else \
val_1 * val_2 if exp[op] == '*' else val_1 / val_2
def calculate_expression(exp):
prior_matrix = {
'+': {'+': 1, '-': 1, '*':-1, '/':-1, '#':-1},
'-': {'+': 1, '-': 1, '*':-1, '/':-1, '#':-1},
'*': {'+': 1, '-': 1, '*': 1, '/': 1, '#':-1},
'/': {'+': 1, '-': 1, '*': 1, '/': 1, '#':-1},
'#': {'+': 1, '-': 1, '*': 1, '/': 1, '#': 0},
}
print(exp[1: -1], "=", calculate(exp.replace(' ', ''), prior_matrix))
if __name__ == '__main__':
calculate_expression("1 + 2 + 5 * (2 - 8 * 3) - (2 - 1)")