问题来源于清华大学出版社1992年第二版《数据结构》,3.2节 表达式求值。书中采用了“算符优先法”并提供了算法的伪代码,本文是伪码的Python实现。
问题的简要描述(具体可参考《数据结构》)
本文仅讨论个位数的包含加减乘除和小括号的表达式,并规定表达式以‘#’结束。
表达式实例:3*(7-2)#
不讨论表达式语法错误的情况
问题分析(具体可参考《数据结构》)
运算符和界限符统称为算符,我们将它们构成的集合命名为OP。
两个相继出现的运算符θ₁和θ₂的优先关系为:
θ₁\θ₂ | + | - | * | / | ( | ) | # |
---|---|---|---|---|---|---|---|
+ | > | > | < | < | < | > | > |
- | > | > | < | < | < | > | > |
* | > | > | > | > | < | > | > |
\ | > | > | > | > | < | > | > |
( | < | < | < | < | < | = | |
) | > | > | > | > | > | > | |
# | < | < | < | < | < | = |
由于表达式以‘#’结束,为了算法简洁,在表达式的最左边虚设一个‘#’,构成一对。
为实现算符优先算法,可以使用两个工作栈。OPTR寄存运算符,OPND寄存操作数和运算结果。
算法的基本思想为:
1.置操作数栈为空栈,表达式起始符‘#’为运算符栈的栈底元素;
2.依次读入表达式中的字符,若是操作数则进OPND栈;若是运算符,则和OPTR栈的栈顶运算符比较优先权后,进行相应操作,直至表达式求值完毕。
算法的伪代码表示
FUNC exp_reduced:operandtype;
{OPTR:运算符栈,OPND:操作数栈,OP:运算符集合}
INISTACK(OPTR);
PUSH(OPTR,’#’);
INSTACK(OPND);
{栈初始化,并在运算符栈的栈底压入表达式左端的虚设字符‘#’}
read(w);{从终端接收一个字符}
WHILE NOT ((w=’#’) AND (GETTOP(OPTR)=’#’)) DO
IF w NOT IN op THEN PUSH(OPND,w)
ELSE CASE precede(GETTOP(OPTR),w) OF {比较优先权}
‘<’:[PUSH(OPTR,w); read(w)];{栈顶元素优先权低}
‘=’:[x:=POP(OPTR); read(w)];{脱括弧并接收下一个字符}
'‘>’:[theta:=POP(OPTR); b=POP(OPND); a:=POP(OPND); PUSH(OPND,operate(a,theta,b))]
{退栈并将运算结果入栈}
ENDC;
RETURN(GETTOP(OPND))
ENDF; {exp_reduced}
Python实现
import numpy
def is_int(x):
try:
int(x)
return True
except ValueError:
return False
#自定义函数,用于判断字符是否为数字
def calculate(expression):
change = {'+': 0, '-': 1, '*': 2, '/': 3, '(': 4, ')': 5, '#': 6}
#将符号转换为数字,用于对应二维数组
prior = numpy.array([
[1, 1, -1, -1, -1, 1, 1],
[1, 1, -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, 2],
[1, 1, 1, 1, 2, 1, 1],
[-1, -1, -1, -1, -1, 2, 0]
])
#用二维数组表示符号间的优先关系,即上文中的表格。
#其中1为>;0为=;-1为<;2用于占位,也可以用于判断表达式错误。在代码中没有讨论表达式错误的情况。
operator = ['#']
number = []
#operator为伪码中的OPTR栈,即运算符栈;number为OPND栈,即操作数栈。
i = 0
#通过下标遍历字符串
while not (expression[i] == '#' and operator[-1] == '#'):
if is_int(expression[i]):
number.append(expression[i])
i += 1
elif prior[change[operator[-1]]][change[expression[i]]] == -1:
operator.append(expression[i])
i += 1
elif prior[change[operator[-1]]][change[expression[i]]] == 0:
operator.pop()
i += 1
elif prior[change[operator[-1]]][change[expression[i]]] == 1:
op = operator.pop()
b = number.pop()
a = number.pop()
number.append(str(eval(a + op + b)))
#计算a和b通过op运算符后的结果。我直接使用了eval函数,如果不用,可以写四个判断进行计算。
#需要注意的是eval接收的参数为字符串,要将计算得到的结果转化为字符再压入栈内
return int(number[-1])
#number内存放的是字符,需要转化为数字
while True:
string = input("input:")
if string == "quit":
break
else:
print(calculate(string))
#测试
个人想法
我在看到算符优先关系的表格的时候挺迷惑的,带入一个例子模拟计算步骤就比较好理解了。
对于算法的基础实现,感觉Python代码量少的优势完全没有体现出来,C++也是差不多这样写的。不过如果直接用eval的话,Python一行就可以出结果了。
参考资料
《数据结构》第二版 严蔚敏 吴伟民 编著 清华大学出版社 1992年6月 P45-47