實際上,編譯器就是經過兩個棧來實現的。其中一個保存操做數的棧,另外一個是保存運算符的棧。咱們從左向右遍歷表達式,當遇到數字,咱們就直接壓入操做數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較。java
若是比運算符棧頂元素的優先級高,就將當前運算符壓入棧;若是比運算符棧頂元素的優先級低或者相同,從運算符棧中取棧頂運算符,從操做數棧的棧頂取 2 個操做數,而後進行計算,再把計算完的結果壓入操做數棧,繼續比較。數據結構
要想寫出沒有 bug 的循環隊列的實現代碼,我我的以爲,最關鍵的是,肯定好隊空和隊滿的斷定條件。函數
編寫遞歸代碼的關鍵是,只要遇到遞歸,咱們就把它抽象成一個遞推公式,不用想一層層的調用關係,不要試圖用人腦去分解遞歸的每一個步驟調試
遞歸代碼要警戒堆棧溢出日誌
我在 「棧」 那一節講過,函數調用會使用棧來保存臨時變量。每調用一個函數,都會將臨時變量封裝爲棧幀壓入內存棧,等函數執行完成返回時,纔出棧。系統棧或者虛擬機棧空間通常都不大。若是遞歸求解的數據規模很大,調用層次很深,一直壓入棧,就會有堆棧溢出的風險。code
爲了不重複計算,咱們能夠經過一個數據結構(好比散列表)來保存已經求解過的 f (k)。當遞歸調用到 f (k) 時,先看下是否已經求解過了。若是是,則直接從散列表中取值返回,不須要重複計算,這樣就能避免剛講的問題了。遞歸
public int f(int n) { if (n == 1) return 1; if (n == 2) return 2; // hasSolvedList 能夠理解成一個 Map,key 是 n,value 是 f(n) if (hasSolvedList.containsKey(n)) { return hasSovledList.get(n); } int ret = f(n-1) + f(n-2); hasSovledList.put(n, ret); return ret; }
遞歸弊端: 棧溢出、重複計算、函數調用耗時多、空間複雜度高等,因此,在編寫遞歸代碼的時候,必定要控制好這些反作用。
調試遞歸: 1. 打印日誌發現遞歸值。2. 結合條件斷點進行調試。隊列