對於如下類型的void型遞歸函數:(主要特徵是遞歸調用的地方上下文無關)
void Fun(type a1,type a2......)
{
//0號程序段-起
//0號程序段-止
Fun(b1,b2,.....);
//1號程序段-起 -----注意0,1,...號程序段之間並無上下文關聯(沒有共享局部變量)
//1號程序段-止
Fun(c1,c2,.....);
//2號程序段-起
//2號程序段-止
.........
}
將函數(程序段)一次調用視爲一個節點,就可以將遞歸函數整個一次的執行看做是樹的遍歷:
如函數:
void Test(int n){
if(n>1){
Test(n-1);
printf("Test%d\n",n);
Test(n-2);
}
}
Test(n)函數
O (root節點) ----------Test函數,即if(n>1){ A }
|
O ----------A
/ | \
O O O
Test(n-1) Printf... Test(n-2)
繼續向下... 執行 繼續向下.....
將圖中的節點編號,Test函數節點爲-1;A程序段編號爲0;printf程序段編號爲1;
struct Param
{
int number;
int n;
}
void Test(int n)
{
stack<Param> ss;
Param p;
p.number = -1;p.n = n;
ss.put(p);
while(!ss.empty)
{
Param pa = ss.top();
ss.pop();//原遞歸函數爲void型,不需要回溯
switch(ss.number){
case -1://-1號節點Test函數節點
if(ss.n>1){
pa.number = 0;
pa.n = n;
ss.push(pa);
}
break;
case 0://0號節點A程序段節點,即:Test(n-1);printf("Test%d\n",n);Test(n-2);
//Test(n-2)節點壓入棧中
pa.number = -1;
pa.n = n-2;
ss.push(pa);
//printf程序段壓入棧中
pa.number = 1;
//pa.n = 0;//這個參數沒有意義
ss.push(pa);
//Test(n-1)節點壓入棧中
pa.number = -1;
pa.n = n-1;
ss.push(pa);
break;
case 1://printf函數段,即:printf("Test%d\n",n);
printf("Test%d\n",n);
break;
}
}
}
稍作簡化,因爲-1號節點後繼節點只有一個節點,直接將兩個節點合併(使用合併可能不是特別貼切)了:
Test(n)函數
O (root節點) ----------Test函數
/ | \
O O O
Test(n-1) Printf... Test(n-2)
繼續向下... 執行 繼續向下.....
將Test節點編號爲-1,Printf節點編號爲0;
void Test(int n)
{
stack<Param> ss;
Param p;
p.number = -1;p.n = n;
ss.put(p);
while(!ss.empty)
{
Param pa = ss.top();
ss.pop();//原遞歸函數爲void型,不需要回溯
switch(ss.number){
case -1://-1號節點Test函數節點
if(ss.n>1){
//Test(n-2)節點壓入棧中
pa.number = -1;
pa.n = n-2;
ss.push(pa);
//printf程序段壓入棧中
pa.number = 1;
//pa.n = 0;//這個參數沒有意義
ss.push(pa);
//Test(n-1)節點壓入棧中
pa.number = -1;
pa.n = n-1;
ss.push(pa);
}
break;
case 0://printf函數段,即:printf("Test%d\n",n);
printf("Test%d\n",n);
break;
}
}
}
說明:
1、注意壓棧的順序,反向壓棧
2、本方法只是用於遞歸調用的地方不需要保存現場(Test函數遞歸調用前後沒有關聯)
如果Test函數是這樣的:
void Test(int n){
if(n>1){
int t = 1;
Test(n-1);
t = 0;
Test(n-2);
}
}
由於t變量的存在,因此需要額外的空間保存t的值(天知道t需不需要使用到),不屬於本文討論的此類遞歸函數
3、遞歸函數不能有返回值(之後可能會討論這樣的遞歸函數)