極客時間課程《數據結構與算法之美》筆記04 - 棧、隊列、遞歸

棧、隊列、遞歸

兩個棧實現表達式求值

實際上,編譯器就是經過兩個棧來實現的。其中一個保存操做數的棧,另外一個是保存運算符的棧。咱們從左向右遍歷表達式,當遇到數字,咱們就直接壓入操做數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較。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. 結合條件斷點進行調試。隊列

相關文章
相關標籤/搜索