【程式設計學習記錄】遞迴轉非遞迴
阿新 • • 發佈:2018-11-11
想要知道怎麼遞迴轉非遞迴,就得先弄明白遞迴函式呼叫和返回的步驟(來源於網課):
呼叫
- 儲存呼叫資訊(引數,返回地址)
- 分配資料區(區域性變數)
- 控制轉移給被調函式的入口
返回
- 儲存返回資訊
- 釋放資料區
- 控制轉移到上級函式
因為遞迴滿足LIFO,所以用棧來實現
遞迴轉非遞迴方法
最機械的轉換方法:
設定t+2個語句標號,t為函式遞迴呼叫的次數,令第0個標號為遞迴入口,1到t標記各個遞迴規則,t+1標記遞迴出口;通過入棧出棧來模擬實際遞迴時系統壓棧彈棧的過程。最最機械的部分,則是真的用label在各處做標記,然後用goto語句模擬實現遞迴過程。
比如,拿求n的階乘來舉例子:
正常遞迴寫法:
int factorial(int n)
{
if (n == 1){
return 1;
} else
return n*factorial(n-1);
}
可以看到其中只有一個遞迴規則,只有一處遞迴呼叫,我們稍微轉化一下可以得到:
void factorial(int n, int& f)
{
int u;
if (n == 1){
f = 1;
} else{
factorial(n-1, u);
f = n*u;
}
}
即:
開始設定標號:
- label 0 :第一個可執行語句
- label t+1 :設在函式體結束處
- label i (1<=i<=t): 第i個遞迴返回處
程式碼如下:
void factorial(int n, int& f) { fac x, tmp; x.rd = 2; x.pn = n; S.push(x);//壓入棧底作為監視哨 label0: if ((x = S.top()).pn == 1){//遞迴入口 S.pop(); x.pf = 1; S.push(x); goto label2; } //第一處也是本程式碼唯一一處遞迴 x.rd = 1; x.pn = x.pn-1; S.push(x); goto label0; label1: tmp = S.top(); S.pop(); x = S.top(); S.pop(); x.u = tmp.pf; x.pf = x.pn * x.u; S.push(x); //遞迴出口 label2: x = S.top(); switch (x.rd){ case 0: goto label0; break; case 1: goto label1; break; case 2: tmp = S.top(); S.pop(); f = tmp.pf; break; } }
但是會有一些冗餘的入棧操作,並且畢竟goto語句現在也不大推薦使用了,比較容易出問題,於是有了優化後的程式碼:
void factorial(int n, int& f)
{
fac x, tmp;
x.rd = 2;
x.pn = n;
S.push(x);//監視哨
do{
//入棧
while ((x = S.top()).pn > 1){
x.rd = 1;
x.pn = x.pn-1;
S.push(x);
}
x = S.top();
S.pop();
x.pf = 1;
S.push(x);
//開始上升
while ((x = S.top()).rd == 1){
tmp = S.top(); S.pop();
x = S.top(); S.pop();
x.pf = x.pn * tmp.pf;
S.push(x);
}
}while ((x = S.top()).rd != 2);
S.pop();
f = x.pf;
}
有兩處遞迴呼叫的程式也類似,可以選擇從某一個遞迴語句開始入棧,從另一個上升,大家自己去摸索吧!溜了溜了