簡單算術表達式求值

參考: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
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容