一類void 遞歸函數的非遞歸實現

        對於如下類型的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、遞歸函數不能有返回值(之後可能會討論這樣的遞歸函數)




發佈了43 篇原創文章 · 獲贊 3 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章