棧的應用--遞迴與四則運算表示式求值
遞迴
將一個直接呼叫自己或通過一系列的呼叫語句間接的呼叫自己的函式,稱為遞迴函式。
每個遞迴定義必須至少有一個條件,滿足是遞迴將不再進行,即不再引起自身而是返回值退出。(即必須要有遞迴結束的條件,以數值返回就不再呼叫函式)
例子–斐波那契數列
Q:若兔子在出生兩個月後,就有繁殖能力,一對兔子每個月能生出一對兔子。假設所有兔子都不死,那麼一年以後可以繁殖多少對兔子呢?
分析: 第一個月–1對 ; 第二個月–1對 ; 第三個月–2對 ; 第四個月–3對 ; 第五個月–5對 ; 第六個月–8對 ……
斐波那契數列用數學函式來表示:
F0=0,F1=1,Fn=Fn−1+Fn−2(n>1)
下面先使用常規的迭代方法實現斐波那契數列:
int main(){
int i;
int a[40];
a[0]=1;
a[1]=1;
printf("%d and %d",a[0],a[1]);
for(i=2;i<40;i++){ //確定a[0]和a[1],逐步計算
a[i]=a[i-1]+a[i-2];
printf("%d",a[i]);
}
return 0;
}
接下來使用遞迴方法來實現(簡單幹淨):
/*斐波那契數列的遞迴實現*/
int Fbi(int i){
if(i<2) return i == 0 ? 0 : 1;
return Fbi(i-1)+Fbi(i-2); //遞迴呼叫函式本身
}
int main(){
int i;
for(i=0;i<40;i++)
printf("%d",Fbi(i));
return 0;
}
小結
迭代使用的是迴圈結構,而遞迴使用的是選擇結構。遞迴會讓程式碼的結構簡單、清晰,但是大量的遞迴呼叫會建立函式的副本,耗費大量的時間和記憶體。視情況選擇實現方法。
遞迴和棧的關係:遞迴過程中退回的順序是它前行順序的逆序,編譯器使用棧來實現遞迴操作。當然現在很多高階語言已經系統封裝了遞迴,可直接使用。
四則運算表示式求值
瞭解–字尾(逆波蘭)表示式法
之前也已經有根據括號匹配來完成四則運算,但是還不夠,除了括號還要考慮到四則運算的先後順序,先乘除後加減,所以,後來提出一種不需要括號的字尾表達法,也被稱為逆波蘭表示—–所有的符號都要在運算數字的後面出現。
中綴表示式–字尾表示式法
將中綴表示式“9+(3-1)3+10/2”轉化為字尾表示式“9 3 1 - 3*+10 2 / +”。
規則:從左到右遍歷中綴表示式的每個數字和符號,若是數字就輸出,即成為字尾表示式的一部分;若是符號,則判斷其與棧頂元素符號的優先順序,是右括號或優先順序不高於棧頂符號(乘除優先加減),則棧頂元素依次出棧並輸出,並將當前符號進棧,一直到最終輸出字尾表示式為止。
步驟:
1. 初始化空棧,用於符號的進出棧使用;
2. 首先是數字9,輸出9;後面是符號“+”,進棧;
3. 第三個符號是(,符號,還未配對,所以進棧;接下來是數字3,輸出;
4. 接下來是符號“–”,因前面是左括號,遇到右括號才出棧,所以,入棧;
5. 數字1,輸出;遇到右括號“)”,彈出左括號上方的所有符號,所以”–”出棧;
6. 符號”*”,比“+”的優先順序高,所以入棧;數字3,輸出,這時輸出為9 3 1 – 3;
7. 符號”+”,不高於”*”和“+”符號,所以兩個符號都彈出,然後將“+”進棧;
8. 數字10,輸出;”/”的優先順序高於“+”,所以入棧,此時輸出為9 3 1 – 3 * + 10;
9. 數字2,輸出;到了表示式末尾,所有符號彈棧,最後輸出 9 3 1 – 3 * + 10 2 / +。
字尾表示式法計算結果
字尾表示式“9 3 1 - 3**+10 2 / +”,計算其最終結果。
規則:從左到右遍歷表示式的每個數字和符號,遇到數字就進棧,暈倒是符號,就將處於棧頂兩個元素出棧,進行運算,運算結果進棧,一直到最終獲得結果。
步驟:
1. 初始化空棧,用於表示式中數字的進出棧使用;
2. 首先是數字9,3,1,進棧;遇到‘–’號,將數字1和3彈出棧,3-1 運算完成,將數字2入棧;
3. 數字3,入棧;遇到‘*’號,將數字3和2彈出棧,2*3運算完成後,將6壓入棧;
4. 接下來,符號‘+’,將數字6和9彈出棧,9+6運算完成後,將15壓入棧;
5. 數字10和2,入棧;符號‘/’,將2和10彈出棧,10/2運算完成後,將5入棧;
6. 符號‘+’,將數字5和15彈出棧,15+5運算完成後,將20壓入棧;
7. 表示式結尾,將結果20彈出棧,棧為空。
小結
四則表示式求值主要分為兩個步驟:首先將中綴表示式變為字尾表示式,這個過程中,將符號出入棧;然後將字尾表示式進行運算,這個過程,將數字出入棧。具體參考上述。