數據結構の學習(三):表達式解析樹的建立

對算法類的問題,最大的忌諱就是,想都不想直接寫代碼

如果你的這樣的程序猿,那麼狠抱歉,要麼就是你會花上數十倍的時間修改你的簡單STUPID錯誤,要麼就是你很短時間就能得到正確的結果,如果是這樣那麼恭喜你,你進化了!

說上述言論,筆者的區分點是你的目標究竟是一個碼農還是一個算法工程師。兩者的區別從工資上看不說你應該也懂:-)

(等不及的你可以迅速下拉到分界線以下尋找乾貨)

下面附上筆者的coding習慣。首先將思路以母語(中文可以但推薦英語)的形式寫在草稿紙上,覺得沒有問題之後,再將其用鉛筆轉換爲代碼,之後將其寫在你青睞的有調試功能的開發平臺上(對於筆者是Spyder,PyCharm)。如果你發現的開發平臺沒有調試功能,那麼你需要立刻更換!對於剛寫好的代碼,第一步永遠是調試,而不是直接運行。最後一步是不斷優化,考慮所有的情況,並且優化時間複雜度和空間複雜度,永遠不是喊着“哈利路亞”,然後發郵件告訴你老闆任務完成了。

                                                                              第一步:將算法寫在草稿紙上 

                                                                            第二步:將其轉換爲鉛字版 代碼

 

                                                               第三步:轉化爲實際的代碼,這一步基本上是調試


不幸的是,筆者的博客將不會有完整代碼(示範代碼除外),所有代碼均已上傳到碼雲上。如果時間和精力允許的話,強烈建議你根據思路,手動寫一遍,相信你會感覺到全身毛孔舒張而不是想砸電腦的快感。

術語:
運算符:進行數字運算的字符,如‘+’‘-’等
操作數:數字‘1’,‘2.5’等
棧: 存儲數據的結構,講究“先進後出”,即最先進棧的數據,最後出棧;有順序存儲和鏈式存儲兩種。
二叉樹:這是一種非常重要卻簡單美麗的數據結構。從每個樹節點最多可以引出兩個子節點,如果一個節點位於最上層,那麼這個節點稱爲根節點。一棵樹是通過根節點來索引。

正則表達式:通常被用來檢索、替換那些符合某個模式(規則)的文本。

如果你還在糾結於如何在嵌套列表裏看出一棵樹,那麼很遺憾,現在已經有了更棒的可視化樹的方法(如下圖)。就在筆者的可視化樹中。

我們在上期博客中介紹瞭如何給中綴表達式加括號。當然,即使你沒有看完這篇很複雜的文章也沒關係。我們可以用一個括號表達式完成表達式解析樹建立過程,現在我們需要把它變成一棵樹,比如'(5/6+1)/0.5+2',我們希望得到下圖:

 這看起來很直觀,不是嗎?而且這也爲我們後續遍歷和求值打下基礎。下面是思路:

我們依次遍歷表達式(列表)每個字符,如果是數字(如-66.6),則看作一個字符。如果你仔細觀察一下上面的樹,你會發現,所有數字都處在葉節點,而操作符(如+ - */ % ^)都處於樹幹,樹中永遠不會出現括號。你似乎發現了某種規律,,,loading

很不錯,你已經接近正確思路了。我們需要定義一個當前節點current_node:

(1)如果我們遇到操作符,則生成右子節點,當前節點移至右子節點。

(2)如果我們遇到數字,則當前節點返回至其父節點。那麼問題來了,我們如何知道父節點是誰呢。

對於樹這種結構,在內存中不是連續存儲,而是通過一個個指針串接在一起,於是你想,既然一個節點可以指向左子節點或右子節點,那麼它爲什麼不可以指向自己的父節點。

class Node:
    def __init__(self,val,left=None,right=None,parent=None):
        self.val = val
        self.left_child = left
        self.right_child = right
        self.parent = parent

僅僅加一句話就可以讓節點多一個功能。還有另外的思路,我們可以用棧Stack(),來存儲所有的current_node。我們將着重講這種思路。

(1)如果我們遇到操作符,當前節點val設爲操作符,則生成右子節點,同時將操作符壓入堆棧,當前節點移到右子節點。

(2)如果我們遇到一個'(',說明我們要優先處理括號裏的內容了,我們生成左子節點(空節點),【爲什麼是左子節點?這和我們閱讀習慣有關,我們總是從左到右看,當然你也可以進行完全相反的操作】

(3)如果我們遇到一個')',說明已經處理完一個括號了,我們只需要將棧彈出。

(4)如果我們遇到數字,我們將當前節點val設爲數字,同時將current_node向上移至父節點,也就是出棧。

請你實現上述邏輯:-}。。。


相信你已經在草稿紙上大顯身手,寫好了代碼。但是很快問題就出現了:-(

1)我們希望函數expression_parser_opt返回的是根節點,而且我們從根節點出發,那麼根節點也會成爲父節點,因此我們需要在遍歷之前,把根節點也壓入堆棧,當然這是個空節點。在鏈式存儲裏面,我們經常需要“製造”根節點或頭結點,然後把它複製一份,讓“複製品”完成所有操作,最後返回根節點或頭結點的“複製品”。

2) 你想到了多項的情況,比如(1+2)*(3/4)。這個時候你的代碼就指鹿爲馬了。這個時候不要心急,捋一捋思緒,看看哪一步出問題了。。。

思考。。。


Great,原來是這兒出了問題[Doge],如下圖一樣,你在草稿紙上畫一個圈,這是我們需要的部分。當處理完一個一個括號部分後,棧Stack爲空。這表示我們要處理下一個括號了,我們希望下一項出現在當前根節點的右上方。很簡單,我們生成一個新節點,其值val設爲操作符,其左節點設爲當前節點current_node,然後當前節點變爲其父節點。

 

【思考】我能否將遍歷中判斷數字的條件移到判斷括號或者操作符前面?

希望筆者的博客能對你有幫助,如果有問題,歡迎留言,或者email [email protected]! 當然你給我Gitee ,Fork&Star一下,筆者也是不勝感激的!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章