線性結構之棧的應用(下)

表達式轉換

中綴表達式

我們通常看到的表達式像這樣:A*B,很容易知道這是A乘以B,這種操作符介於操作數中間的表示法,稱爲“中綴”表示法。

但有時候,中綴表示法會引起混淆,如“A+B*C”,是A+B然後再乘以C呢?還是B乘以C再加A呢?

爲了解決上面的問題,人們引入了操作符“優先級”的概念來消除混淆,規定高優先級的操作符先計算,相同優先級的操作符從左到右依次計算,這樣A+B*C就沒有疑義,是A加上B與C的乘積。

同時引入了括號來表示強制優先級,括號的優先級最高,而且在嵌套的括號中,內層的優先級更高。這樣(A+B)*C就是A與B的和再乘以C。

雖然人們已經習慣了這種表示法,但計算機處理最好是能明確規定所有的計算順序,這樣無需處理複雜的優先規則。

前綴和後綴表達式

接上面的問題,人們又引入了全括號表達式,即在所有的表達式項兩邊都加上括號,A+B*C+D,應表示爲((A+(B * C))+D)。

那麼可否將表達式中操作符的位置稍微移動一下呢?

例如中綴表達式A+B,我們將操作符移到前面,變爲“+AB”,或者將操作符移動到最後,變爲“AB+”。這樣我們就得到了表達式的另外兩種表示法:“前綴”和“後綴”表示法。(以操作符相對於操作數的位置來定義)

這樣A+BC的前綴表達式爲“+ABC”,後綴表達式爲“ABC*+”。(爲了幫助理解,子表達式加了中劃線)。

再來看中綴表達式“(A+B)C”,按照轉換的規則,前綴表達式是“ * + ABC”,後綴表達式是“AB+C”。神器的事情發生了,在中綴表達式裏必須的括號,在前綴和後綴表達式中消失了?

在前綴和後綴表達式中,操作符的次序完全決定了運算的次序,不再有混淆,所以在很多情況下,表達式的計算機表示都避免用複雜的中綴形式。

目前爲止,我們僅手工轉換了幾個中綴表達式到前綴和後綴的形式,一定得有個算法來轉換任意複雜的表達式,爲了分解算法的複雜度,我們從“全括號”中綴表達式入手。我們看A+BC,如果寫成全括號形式:(A+(B * C)),顯示錶達了計算次序,我們注意到每一對括號,都包含了一組完整的操作符和操作數,看子表達式(B * C)的右括號,如果把操作符 移動到右括號的位置,替代它,在刪除左括號,得到BC*,這個正好把子表達式轉換爲後綴形式,進一步再把更多的操作符移動到相應的右括號處替代之,再山區左括號,那麼整個表達式就完成了後綴表達式的轉換。

同樣的,如果我們把操作符移動到左括號的位置取代之,然後刪除所有的右括號,也就得到了前綴表達式。

所以說沒無論表達式多複雜,需要轉換爲前綴或者後綴,只需要兩步,將中綴表達式轉換爲全括號形式,將所有的操作符移動到子表達式所在的左括號(前綴)或右括號(後綴)處,再刪除所有的括號。

通用的中綴轉後綴算法

首先我們來看中綴表達式A+B * C,其對應的後綴表達式是ABC*+,操作數ABC的順序沒有改變,操作符的出現順序,在後綴表達式中反轉了,由於 * 的優先級比 + 高,所以後綴表達式中操作符的出現順序與運算次序一致。

在中綴表達式轉換後綴形式的吹過程中,操作符比操作數要晚輸出,所以在掃描到對應的第二個操作數之前,需要把操作符先保存起來,而這些暫存的操作符,由於優先級的規則,還有可能需要反轉次序輸出,在A + B * C中, + 雖然先出現,但優先級比後面這個* 要低,所以它要等 * 處理完,才能處理。

這種反轉特性,我們可以考慮用棧來保存暫時爲處理的操作符。

再看看(A + B)* C,對應的後綴形式是 AB + C*,這裏+的輸出要比 * 要早,主要是因爲括號使得 + 的優先級提升,高於括號之外的 *。回顧“全括號”表達式,後綴表達式中操作符應該出現在左括號對應的右括號位置,所以遇到左括號,要標記下來,其後出現的操作符的優先級提升了,一單掃描到對應的右括號,就可以馬上輸出這個操作符。

總結下,在從左到右掃描逐個字符掃描中綴表達式的過程中,採用一個棧來暫存未處理的操作符。這樣,棧頂的操作符就是最近暫存進去的,當遇到一個新的操作符,就需要跟棧頂的操作符比較下優先級,再進行處理。

通用的中綴轉後綴算法流程

在後面的算法描述中,約定中綴表達式是由空格隔開的一系列單詞(token)構成,操作符單詞包括* / + - (),而操作數單詞則是單字母標識符A、B、C等。

首先,創建空棧s用於暫存操作符,空表li用於保存後綴表達式,將中綴表達式轉換爲單詞列表,從左到右掃描中綴表達式單詞列表。

如果單詞是操作符,則直接添加到後綴表達式列表的末尾,如果單詞是左括號“(”,則壓入s棧頂,如果單詞是右括號“)”,則反覆彈出s棧頂,操作符加入到輸出列表末尾,直到碰到左括號,如果單詞是操作符“* / + -”,則壓入s棧頂,但是在壓入之前,要比較其與棧頂操作符的優先級。

中卓表示單詞列表掃描結束後,把s棧中所有剩餘操作符依次彈出,添加到輸出列表末尾,把輸出列表再用join方法合併成後綴表達式字符串,算法結束。

class Stack:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def push(self, item):
        return self.items.append(item)

    def pop(self):
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items)-1]

    def size(self):
        return len(self.items)
    
def infixToPostfix(infixexpr):
    # 記錄操作符的優先級
    p = {}
    p["*"] = 2
    p["/"] = 2
    p["+"] = 1
    p["-"] = 1
    p["("] = 0

    s = Stack()  # 創建棧
    li = []  # 用於保存後綴表達式列表
    tokenList = infixexpr.split()  # 將中綴表達式轉換爲單詞列表
    print(tokenList)
    for token in tokenList:
        if token in "ABCDEFGHIJKMNLOPQRSTUVWXWY" or token in "0123456789":
            li.append(token)
        elif token == "(":
            s.push(token)
        elif token == ")":
            topToken = s.pop()
            while topToken != "(":
                li.append(topToken)
                topToken = s.pop()
        else:
            while not s.isEmpty() and p[s.peek()] >= p[token]:
                li.append(s.pop())
            s.push(token)
    while not s.isEmpty():
        li.append(s.pop())
    return " ".join(li)

後綴表達式求值

作爲棧結構的結束,我們來討論“後綴表達式求值”問題,跟中綴轉換爲後綴問題不同,在對後綴表達式從左到右掃描過程中,由於操作在操作數的後面,所以要暫存操作數,在碰到操作符的時候,再將暫存的兩個操作數進行實際的計算,仍然是棧的特性:操作符只作用於離它最近的兩個操作數。

如“4 5 6 * +”,我們先掃描到4,5兩個操作數,但還不知道對這兩個操作數能做什麼計算,需要繼續掃描後面的符號才能知道,繼續掃描右碰到6,還是不知道如何計算,繼續暫存入棧,直到“ * ”,現在知道是棧頂兩個操作數5、6做乘法。

我們彈出兩個操作數,計算得到結果爲30,需要注意,先彈出的是右操作數,後彈出的是做操作數,這個對於-/很重要。

爲了繼續後續的計算,需要把這個中間結果30壓入棧頂,繼續掃描後面的符號,當所有操作符都處理完畢,棧中只留下一個操作數,就是表達式的值。

def postfixEval(postfixExpr):
    s = Stack()
    li = postfixExpr.split()

    for l in li:
        if l in "0123456789":
            s.push(int(l))
        else:
            op2 = s.pop()
            op1 = s.pop()
            result = doMath(l, op1,op2)
            s.push(result)
    return s.pop()


def doMath(op, op1, op2):
    if op == "*":
        return op1 * op2
    elif op == "/":
        return op1 / op2
    elif op == "+":
        return op1 + op2
    else:
        return op1 -op2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章