參考:http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html
本文主要探討簡單的數學算術表達式求值算法的原理和實現。
1. 約束
本文只是探討簡單的算術表達式的求值算法,為了將主要精力放在算法思想的探討和實現上,避免陷入對其他不是直接相關的細節的過多思考,所以提前做如下約束:
本文所討論的算術表達式字符串中每個運算數、運算符之間都有空白符分隔開(方便后面用python字符串的split函數分割處理成列表)。
算術表達式中參與運算的運算數都為1位整數。
表達式中的運算符都為二元運算符(即一個運算符需要兩個運算數),不會出現其他元的運算符(如一元運算符負號:
-
)。運算的中間結果和最終結果也都為整數,且都不會產生異常(如除數為0等)。
暫且只支持如下幾種運算符:
+ - \* / ( )
2. 中綴表達式與后綴表達式
算術表達式,根據運算符和運算數的相對位置不同,可以分為三種:前綴表達式(prefix)、中綴表達式(infix)和后綴表達式(postfix),其中后綴表達式又稱為逆波蘭式,在本文中只討論中綴和后綴表達式。
中綴表達式:就是我們平時常見的算術表達式,如
1 + 2 \* 3
,( 1 + 2 ) \* 3
這樣的運算符在運算數中間的表達式,中綴表達式的特點是符合人的理解習慣,并且可以加小括號改變運算的先后順序。但缺點是如果用編程來求值的話比較困難。后綴表達式:是將中綴表達式進行變換后得到的表達式,如
1 2 3 \* +
,1 2 + 3 \*
這樣的運算符在運算數后面的表達式,后綴表達式的特點是雖然不符合人的理解習慣,但編程來求值卻很方便,且沒有括號的煩惱。
后綴表達式因為不需要括號,所以編程求值起來比較方便,下面將先從如何對后綴表達式求值講起。
3. 后綴表達式求值
1. 核心算法:
創建一個空棧,名為numstack,用于存放運算數。
用python字符串的split函數將輸入的后綴表達式(postfix)分割為列表,將該列表記為input。
從左到右遍歷input的每一個元素token:
若token為運算數,將其轉換為整數并push進numstack;
若token為運算符,則將numstack pop兩次,將第一次pop得到的數作為運算符的右操作數,將第二次pop得到的數作為運算符的左操作數,然后求出運算結果,并將結果push進numstack;
遍歷完input后,numstack僅剩下一個元素,這就是表達式的最終求值結果,pop出這個元素,算法結束。
2. 舉例
比如求4 5 6 \* +
這樣一個后綴表達式的值(注:其前綴表達式為:4 + 5 \* 6
,值為34),按照上述算法,過程如下:
No. | operator | numstack |
---|---|---|
1 | 4 | |
2 | 4 5 | |
3 | 4 5 6 | |
4 | * | 4 5 6 |
5 | 4 30 | |
6 | + | 4 30 |
7 | 34 |
所以最終的表達式求值結果為:34
3. 代碼實現
# 準備工作:創建一個棧類
class Stack():
def __init__(self):
self.data = []
def __str__(self):
return str(self.data)
__repr__ = __str__
def pop(self):
if len(self.data) != 0:
return self.data.pop()
return None
def push(self,e):
self.data.append(e)
def clear(self):
del self.data[:]
# 獲取棧頂元素,但不彈出此元素
def peek(self):
if len(self.data) != 0:
return self.data[-1]
return None
# 判斷棧是否為空
def empty(self):
return len(self.data) == 0
# 求值函數
def get_value(num1,op,num2):
if op == '+':
return num1 + num2
elif op == '-':
return num1 - num2
elif op == '*':
return num1 * num2
elif op == '/':
return num1 / num2
else:
raise ValueError('invalid operator!')
# 后綴表達式求值函數
def get_postfix_value(postfix):
# 1. 創建一個運算數棧
numstack = Stack()
# 2. 分割postfix
inPut = postfix.strip().split() # 注:因為'input'是內置函數名,所以用'inPut';strip函數的作用是去掉字符串的開始和結尾的空白字符
# 3. 遍歷inPut
for token in inPut:
# 3.1 如果token為運算數
if token.isdigit():
numstack.push(int(token))
# 3.2 如果token是運算符
else:
num2 = numstack.pop()
num1 = numstack.pop()
numstack.push(get_value(num1,token,num2))
# 4. 輸出numstack的最后一個元素
return numstack.pop()
# 后綴表達式
# 注:對應的中綴表達式為:(1+2)*(3+4),運算結果為:21
postfix = '1 2 + 3 4 + *'
print '【Output】'
print get_postfix_value(postfix)
【Output】
21
4. 中綴表達式轉后綴表達式
1. 核心算法
創建一個空棧opstack,用于存放運算符,創建一個空列表output用于保存輸出結果。
使用python字符串的
split
函數將輸入的中綴表達式(infix)字符串分割成列表并存入input列表中。從左到右遍歷input列表的每個元素token:
若token是運算數,直接append到output中;
若token是運算符,先判斷它與opstack棧頂元素的運算優先級(注:小括號的優先級約定為最低),若:token的優先級小于等于棧頂元素優先級,則先從opstack中pop出棧頂元素并append到output,再將token push進opstack;否則直接將token push進opstack;
若token是左括號,直接將其push進opstack;
若token是右括號,依次pop出opstack中的元素并依次append到output,直到遇到左括號,將左括號繼續pop出(但不append到output)。
當遍歷完成input,將opstack中所有的剩余元素pop出并依次append到output。
將output轉換為字符串,即為最終求得的后綴表達式。
2. 舉例
比如將(A+B)\*C
這樣一個中綴表達式轉換為后綴表達式(其中A,B,C表示整數),按照上述算法,轉換過程如下:
No. | opstack | output |
---|---|---|
1 | ( | |
2 | ( | A |
3 | (+ | A |
4 | (+ | A B |
5 | A B + | |
6 | * | A B + |
7 | * | A B + C |
8 | A B + C * |
所以最終求得的后綴表達式為:A B + C *
3. 代碼實現
# 準備工作:創建一個棧類
class Stack():
def __init__(self):
self.data = []
def __str__(self):
return str(self.data)
__repr__ = __str__
def pop(self):
if len(self.data) != 0:
return self.data.pop()
return None
def push(self,e):
self.data.append(e)
def clear(self):
del self.data[:]
# 獲取棧頂元素,但不彈出此元素
def peek(self):
if len(self.data) != 0:
return self.data[-1]
return None
# 判斷棧是否為空
def empty(self):
return len(self.data) == 0
# 求值函數
def get_value(num1,op,num2):
if op == '+':
return num1 + num2
elif op == '-':
return num1 - num2
elif op == '*':
return num1 * num2
elif op == '/':
return num1 / num2
else:
raise ValueError('invalid operator!')
# 將中綴表達式轉換為后綴表達式的函數
def infix2postfix(infix):
# 1. 創建運算符棧和輸出結果列表
opstack = Stack()
output = []
# 準備一個運算符優先級字典,其中左小括號的優先級最低
priority = {'(' : 0,'+' : 3,'-' : 3,'*' : 4,'/' : 4}
# 2. 分割infix
inPut = infix.strip().split()
# 3. 遍歷inPut
for token in inPut:
# 3.1 若token是運算數
if token.isdigit():
output.append(token)
# 3.2 若token是運算符
elif token in ['+','-','*','/']:
if not opstack.empty() and priority[token] <= priority[opstack.peek()]:
output.append(opstack.pop())
opstack.push(token)
# 3.3 若token是左括號
elif token == '(':
opstack.push(token)
# 3.4 若token是右括號
elif token == ')':
while opstack.peek() != '(':
output.append(opstack.pop())
# 彈出左括號
opstack.pop()
else:
raise ValueError('invalid token:{0}'.format(token))
# 4. 將opstack中剩余元素append到output
while not opstack.empty():
output.append(opstack.pop())
# 5. 將output轉換為字符串(每個元素用空格隔開)并輸出
return ' '.join(output)
infix = '( 1 + 2 ) * ( 3 + 4 )'
print '【Output】'
print infix2postfix(infix)
【Output】
1 2 + 3 4 + *
5. 整理:中綴表達式求值
1. 核心算法
經過前面的討論,那么現在求中綴表達式的值就很簡單了,分為兩步:第1步,將中綴表達式轉換為對應的后綴表達式;第2步,對后綴表達式求值。
2. 完整代碼實現
# 準備工作:創建一個棧類
class Stack():
def __init__(self):
self.data = []
def __str__(self):
return str(self.data)
__repr__ = __str__
def pop(self):
if len(self.data) != 0:
return self.data.pop()
return None
def push(self,e):
self.data.append(e)
def clear(self):
del self.data[:]
# 獲取棧頂元素,但不彈出此元素
def peek(self):
if len(self.data) != 0:
return self.data[-1]
return None
# 判斷棧是否為空
def empty(self):
return len(self.data) == 0
# 求值函數
def get_value(num1,op,num2):
if op == '+':
return num1 + num2
elif op == '-':
return num1 - num2
elif op == '*':
return num1 * num2
elif op == '/':
return num1 / num2
else:
raise ValueError('invalid operator!')
# 將中綴表達式轉換為后綴表達式的函數
def infix2postfix(infix):
# 1. 創建運算符棧和輸出結果列表
opstack = Stack()
output = []
# 準備一個運算符優先級字典,其中左小括號的優先級最低
priority = {'(' : 0,'+' : 3,'-' : 3,'*' : 4,'/' : 4}
# 2. 分割infix
inPut = infix.strip().split()
# 3. 遍歷inPut
for token in inPut:
# 3.1 若token是運算數
if token.isdigit():
output.append(token)
# 3.2 若token是運算符
elif token in ['+','-','*','/']:
if not opstack.empty() and priority[token] <= priority[opstack.peek()]:
output.append(opstack.pop())
opstack.push(token)
# 3.3 若token是左括號
elif token == '(':
opstack.push(token)
# 3.4 若token是右括號
elif token == ')':
while opstack.peek() != '(':
output.append(opstack.pop())
# 彈出左括號
opstack.pop()
else:
raise ValueError('invalid token:{0}'.format(token))
# 4. 將opstack中剩余元素append到output
while not opstack.empty():
output.append(opstack.pop())
# 5. 將output轉換為字符串(每個元素用空格隔開)并輸出
return ' '.join(output)
# 后綴表達式求值函數
def get_postfix_value(postfix):
# 1. 創建一個運算數棧
numstack = Stack()
# 2. 分割postfix
inPut = postfix.strip().split() # 注:因為'input'是內置函數名,所以用'inPut';strip函數的作用是去掉字符串的開始和結尾的空白字符
# 3. 遍歷inPut
for token in inPut:
# 3.1 如果token為運算數
if token.isdigit():
numstack.push(int(token))
# 3.2 如果token是運算符
else:
num2 = numstack.pop()
num1 = numstack.pop()
numstack.push(get_value(num1,token,num2))
# 4. 輸出numstack的最后一個元素
return numstack.pop()
# 中綴表達式求值函數
def get_infix_value(infix):
postfix = infix2postfix(infix)
return get_postfix_value(postfix)
infix = '( 1 + 2 ) * ( 3 + 4 )'
print '【Output】'
print get_infix_value(infix)
【Output】
21