$簡單算術表達式求值


Refer: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. 核心算法:

(1) 創建一個空棧,名為numstack,用於存放運算數。

(2) 用python字符串的split函數將輸入的后綴表達式(postfix)分割為列表,將該列表記為input。

(3) 從左到右遍歷input的每一個元素token:

  • 3.1:若token為運算數,將其轉換為整數並push進numstack;

  • 3.2:若token為運算符,則將numstack pop兩次,將第一次pop得到的數作為運算符的右操作數,將第二次pop得到的數作為運算符的左操作數,然后求出運算結果,並將結果push進numstack;

(4)遍歷完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. 核心算法

(1)創建一個空棧opstack,用於存放運算符,創建一個空列表output用於保存輸出結果。

(2)使用python字符串的split函數將輸入的中綴表達式(infix)字符串分割成列表並存入input列表中。

(3)從左到右遍歷input列表的每個元素token:

  • 3.1:若token是運算數,直接append到output中;

  • 3.2:若token是運算符,先判斷它與opstack棧頂元素的運算優先級(注:小括號的優先級約定為最低),若:token的優先級小於等於棧頂元素優先級,則先從opstack中pop出棧頂元素並append到output,再將token push進opstack;否則直接將token push進opstack;

  • 3.3:若token是左括號,直接將其push進opstack;

  • 3.4:若token是右括號,依次pop出opstack中的元素並依次append到output,直到遇到左括號,將左括號繼續pop出(但不append到output)。

(4)當遍歷完成input,將opstack中所有的剩余元素pop出並依次append到output。

(5)將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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM