这里我们写一个Python的程序来实现将一个算术表达式(中缀表达式)如何转换成一个后缀表达式。由于初学Python,所以这里实现的算术符可能就只有基本的算式运算符,但是思想还是共同的,值得借鉴参考。
闲话不多说,对于没有学过《数据结构及算法》的朋友来说我们来科普一下什么是算术表达式,什么是后缀表达式(逆波兰表达式)。
中缀表达式指的是操作符是以中缀形式出现在操作数之间,这也就是我们平常使用的算术(逻辑)公式表示方法。举一个例子来说:3+4*5,这个式子就是一个中缀表达式。它的操作符是以中缀的形式出于操作数的中间。
后缀表达式,在专业术语中我们也可称作是逆波兰表达式。运算符放在两个运算对象的后面,所有操作符和操作数都严格的按照入栈出栈的顺序来进行运算操作,在这里就不考虑原有操作符的优先顺序。还是上面那个例子如果我们要写成后缀表达式的话因该写成这样345*+。我们可以稍微用笔来算算:
首先我们读到一个3
3 ————————- +
之后我们读到操作数4入栈
34 ———————— +
因为在这里”+”号的优先级没有“*”号高,所以+号不入栈而是继续读取数据。
345 ———————-+*
最后操作符入栈得到的结果就是
345*+
如何来具体计算一个后缀表达式:首先我们先建立一个栈,从左到右读这个表达式,如果读到操作数就将它压入栈中,如果读到N元操作符,则从栈顶自上而下的取出N元操作数来进行运算,然后再将运算结构代替原栈顶的N项中,压入栈顶。如果后缀表达式没有读完则重复上面的操作。
在计算机中如何实现中缀表达式转换为后缀表达式的算法思想:
1.建立一个堆栈开始扫描。
2.当读取到是操作数的时候,加入后缀表达式。
3.当读取到一个操作符的时候:
a.如果是”(“,则入栈;
b.如果是一个”)”,则依次把栈中的运算符加入到后缀表达式中,直到出现”(“,从栈中删除“(”。
c.若为除开括号之外的其他操作符,首先我们要考虑算符优先级,当其优先级高于除“(”意外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的算符优先级高和优先级相等的操作符,知道一个比他优先级低或者遇到了一个左括号为止。
下面附上如何使用Python来实现简单的中缀表达式转后缀表达式的方法。我们这里定义了一个栈(将列表作为基础数据结构),同时给这个栈实现了push,peek,pop,empty的方法。
push方法用于讲操作数,操作符压入堆栈。
peek方法查看当前的栈顶元素。
pop方法弹出栈顶元素,并且放回当前值。
empty方法判断栈是否为空。
class Stack(object):
'''
stack
这里是基于列表的栈(将列表作为基础数据结构)
push 往里面放
peek 查看栈顶元素
pop 弹出栈顶元素,并返回这个方法的返回值
empty 判断栈是否为空
***效率优化 栈长度保留
'''
def __init__(self):
self.datas = []
self.length = 0
print '初始化完成'
#===========================================================================
# put向栈顶插入一个元素
#@param data: 要插入的元素没有返回值
#===========================================================================
def push(self,data):
self.datas.append(data)
self.length += 1
#===========================================================================
# peek查看栈顶元素
#@return 栈顶元素
#===========================================================================
def peek(self):
return None if self.empty() else self.datas[len(self.datas) - 1]
#===========================================================================
# pop 弹出栈顶元素
#===========================================================================
def pop(self):
try:
return self.peek()
finally:
self.length -= 1
del self.datas[len(self.datas) - 1]
def empty(self):
#return not bool(self.datas)
return not bool(self.length)
def __str__(self):
print '-----------------------str called----------------------'
return ','.join([str(data) for data in self.datas])
if __name__ == '__main__':
stack = Stack()
stack.push('(')
stack.push('abc')
stack.push(1 + 2J)
stack.push(1.2)
print '%s' % stack
print str(stack.peek())
print '%s' % stack
print str(stack.pop())
print '%s' % stack
print str(stack.pop())
print '%s' % stack
print stack.empty()
print str(stack.pop())
print '%s' % stack
print stack.empty()
print str(stack.pop())
print '%s' % stack
print stack.empty()
相关的注释我觉得在代码里面写的够详细了,在这里我们做了一个优化。在初始化的时候我们设置了一个栈长度self.length = 0,在我们插入栈顶元素的时候就为其自加,这样的话当我们判断栈是否为空的时候就非常好判断了。有一点不好的地方就是当反复的对栈进行插入和弹出操作的时候其实这样是相当消耗时间的。当然对于我们这个算法暂时可以不用去考虑这些。定义好栈了之后我们想着要如何来实现算法操作了。
import re,stack
SUPPORT_SIGN = {'(': 99, '*': 3, '/': 3, '-': 1, '+': 1}
NUMBER_PATTERN = re.compile('\d') #\d匹配数字
sign_stack = None
number_stack = None
#===============================================================================
# init 初始化符号栈和数字栈
#在局部名称空间中,要使用全局名称空间中定义的变量,首先要使用关键字global 声明一下
#===============================================================================
def init():
global sign_stack
global number_stack
sign_stack = stack.Stack()
number_stack = stack.Stack()
#===============================================================================
# to_suffix 将中缀表达式转换成后缀表达式的形式
#@return: 方便操作返回一个列表,列表里面的元素要么是个操作数要么是个操作符
#===============================================================================
def to_suffix(expression):
#check type
result = []
pre_char = None #定义之前接收的一个字符
if not isinstance(expression, str):
raise TypeError, '表达式类型错误'
for char in expression:
if char in SUPPORT_SIGN:
pre_sign = sign_stack.peek()
while pre_sign and SUPPORT_SIGN[char] <= SUPPORT_SIGN[pre_sign]:
if pre_sign == '(':
break
result.append(sign_stack.pop())
pre_sign = sign_stack.peek()
sign_stack.push(char)
elif char == ')':
pre_sign = sign_stack.peek()
while pre_sign != '(':
pre_sign = sign_stack.pop()
result.append(pre_sign)#追加到result里面
pre_sign = sign_stack.peek()
sign_stack.pop()#左括号直接弹掉
elif NUMBER_PATTERN.match(char):
if pre_char and NUMBER_PATTERN.match(pre_char): #判断pre_char是否存在,同时上一个字符也是数字
result[len(result - 1)] += char #拿到最后一个元素
else:
result.append(char)
else:
raise TypeError, '不支持的符号'
pre_char = char
while not sign_stack.empty():
result.append(sign_stack.pop())
return result
#===============================================================================
# count 接口独立 调用后缀表达式
#在前面方法当中转换使用的是符号栈,现在计算的时候使用数字栈
#当前涉及的符号只有双目运算符
#===============================================================================
def count(suffix):
#check type
if not isinstance(suffix,list):
raise TypeError, '表达式类型错误'
for element in suffix:#eval得到的是int类型的值所以不能做字符串链接
if element in SUPPORT_SIGN:
number1 = number_stack.pop()
number2 = number_stack.pop()
eval_str = number2 + element + number1
number_stack.push(str(eval(eval_str))) #eval动态求值,用工厂函数转换
else:
try:
number_stack.push(element)
except TypeError, e:
print '后缀表达式中有不支持的字符'
exit()
return number_stack
def parse(expression):
return count(to_suffix(expression))
if __name__ == '__main__':
init()
print to_suffix('3+2*5*(1+2*3-4/(5-6))')
print count(to_suffix('3+2*5*(1+2*3-4/(5-6))'))
这里我们运行上面的实例得到的结果如下。
初始化完成
初始化完成
[‘3’, ‘2’, ‘5’, ‘‘, ‘1’, ‘2’, ‘3’, ‘‘, ‘+’, ‘4’, ‘5’, ‘6’, ‘-‘, ‘/’, ‘-‘, ‘*’, ‘+’]
———————–str called———————-
113
[‘3’, ‘4’, ‘5’, ‘*’, ‘+’]
这里话我们用字典来定义算术运算符,为什么写的是简单的中缀转后缀表达式呢,以为我们这里只定义了+,-,*,/,(,),这几个算术操作符。这里我想到的是用字典的形式来定义算符优先级,可以看到我取的数跨度有点大,这个是为了以后我再来添加新的运算的时候方便嘛。而且我现在考虑到的情况也只有二元运算的情况,没有考虑到三元运算。(偷懒?)然后顺带提一点非常重要的一点,学习编程的时候一定要有写注释的习惯,这真是为了自己也为了他人着想,当我写这个的时候真是深有体会,可能我一个月再打开看看没有注释看起来会变得非常费劲。在这个代码段里面我们的符号栈和数字栈是分开的,当时写的时候为的写判断的时候思路会清晰一些。这个代码只是实现了这个转化和计算的操作,我相信有更好更简洁的思路和代码段。写博客的过程中有任何错误和漏洞都欢迎指正。