1. 程式人生 > >遞歸法

遞歸法

tro cnblogs 棋盤 次數 數學歸納法 目的 易用 color 使用

遞歸法(Recursion)是一種在函數或方法中調用自身的編程技術,在計算機方法中,使用遞歸技術往往使函數的定義和算法的描述簡潔且易於理解。任何可以用計算機求解的問題所需要的計算時間都與其規模有關。而且規模越小,解題所需要的計算時間通常越小,從而比較容易處理。

簡而言之,遞歸思想就是用與自身問題相似但規模較小的問題來描述自己。

例如,兔子出生兩個月後就有繁殖能力,一對兔子每個月能生出一對兔子來。如果所有兔子都不死,那麽一年以後可以繁殖多少對兔子?

第一個月小兔子沒有繁殖能力,所有還是一對;兩個月後,生下一對,兔子共2對;三個月後,老兔子又生下一對,因為小兔子還沒有繁殖能力,所以一共是三對。

以此類推,如下所示。

所經過月數:0 1 2 3 4 5 6 7 8 9 10 11 12

兔子對數: 1 1 2 3 5 8 13 21 34 55 89 144 233

表中數字1,1,2,3,5,8^233構成一個序列。這個數列有個明顯的特點,即前面兩項之和構成後一項。用數學表示這個無窮數列稱為裴波那契數列,形式化描述為:

     1       , n=0

F(n)=  1       ,n=1

    F(n-1)+F(n-2)  ,n>1

該數列就是個典型遞歸數列。

#include<iostream>
using namespace
std; int fib(int n) { int f=0; if(n==1) return 0; if(n==2) return 1; f=fib(n-1)+fib(n-2); return f; } int main() { int n,i,m=0; cin>>n; m=fib(n); cout<<""<<n<<"項是"<<m<<endl; m=0; for(i=1;i<=n;i++) m=fib(i)+m; cout
<<""<<n<<" 項和是"<<m<<endl; return 0; }

遞歸算法的特性:

(1)求解規模為n的問題可以轉換成一個或多個結果相同、規模較小的問題,然後從這些小問題的解能方便地構造出大問題的解。

(2)遞歸調用的次數必須是有限的。

(3)必須有結束遞歸的條件(邊界條件)來終止遞歸。即當規模為1時,能夠直接得解。

遞歸的執行過程:

遞歸算法的執行過程劃分為遞推和回歸兩個階段。在遞推階段,把規模為n的問題求解推到比原問題的規模較小的問題求解,且必須要有終止遞推的情況。在回歸階段,當獲得最簡單情況的解後,逐漸返回,依次得到規模較大問題的解。

需要註意的是,用遞歸描述問題並不表示程序也一定要直接用遞歸實現。

遞歸算法的優點:結構清晰、可讀性強,而且容易用數學歸納法來證明算法的正確性,因此它為設計算法、調試程序帶來很大方便。

遞歸算法的缺點:運行效率相對較低,無論是耗費的計算時間還是占用的存儲空間都比非遞歸算法要多。通常解決方法是消除遞歸算法中的遞歸調用,使遞歸算法轉換為非遞歸算法。

理論上遞歸算法都可以轉換為非遞歸算法。方法有如下3種

(1)通過分析,跳過分解部分,直接用循環結構實現求值過程。

(2)用棧保存程序的運行過程,通過分析只保存必須保存的信息,從而用非遞歸算法替代遞歸算法。

(3)利用棧保存參數,由於棧的後進先出特性與遞歸算法的執行過程相似,因而可以用非遞歸算法替代遞歸算法。

1漢諾塔問題求解

假設有n個金盤,並且金盤由小到大一次編號為1,2,3……n。要把放在A針上的n個金盤移到目的針C上。當只有一個金盤,即n=1時,只要將編號為1的金盤從A針直接移到C針上即可。當n>1時,可以把最上面的n-1個金盤看作一個整體。這樣n個金盤就分成了兩個部分:上面n-1個金盤和最下面的編號為n的金盤。移動金盤的問題可以轉換為如下3個步驟:

1)借助C針,將n-1個金盤從A針移到B針上;

2)將編號為n的金盤直接從A針移到C針上;

3)借助A針,將B針上的n-1個金盤移到C針上。

其中,第二步只移動一個金盤,容易解決。第一、三步不能直接解決,但由於已經把移到n個金盤問題變成了移到n-1個金盤的問題,問題規模變小。如果再把第一、三步分別變成類似的三個子問題,移到n-1個金盤的問題就可以變成移到n-2個金盤的問題,依次類推,從而將問題加以解決。

#include<iostream>
using namespace std;

void Move(int n,char x,char y) {
    cout<<""<<n<<" 號從 "<<x<<"挪動到 "<<y<<endl;
}

void Hannoi(int n,char a,char b,char c) {
    if(n==1)
        Move(1,a,c);
    else {
        Hannoi(n-1,a,c,b);
        Move(n,a,c);
        Hannoi(n-1,b,a,c);
    }
}

int main() {
    cout<<"以下是3層漢諾塔的解法"<<endl;
    Hannoi(3,a,b,c);
    cout<<"輸出完畢!"<<endl; 
    return 0;
}

2 八皇後問題

在8*8國際象棋盤上放置8個黃昏,使任何一個皇後都不能吃掉另一個。即任意兩個皇後都不能處於同一行、同一列或者同一斜線上,問共有多少種放置方案。

假設當前生成全排列中的第t個數字大於n,其中,t是每一行從左到右的標號,n是棋盤的行數,則說明生成了一個全排列,否則:

1)從1~8中依次取出一個數字來嘗試,如果此數字前面已經出現,則取下一個數字,直到找到第一個沒有出現過的數字;

2)將這個數字標記為已出現,然後以同樣的方法生成全排列中的下一個數字;

3)將這個數字標記為未出現;

顯然,這是一個遞歸定義,遞歸的結束條件為t>n。

#include<iostream>
using namespace std;
int result=1;
int chess[8];

//根據前面幾行的棋子,檢查這一行所放的棋子是否合法
int check(int n) {
    int i;
    for(i=1;i<=n-1;i++) {
        if(chess[n] == chess[i]+(n-i) || chess[n]==chess[i]-(n-i) || chess[n]==chess[i])
            return 0;
    }
    return 1;
} 
 
 void show_chess(void) {
     int i;
     cout<<endl<<"Result -"<<result<<endl;
     for(i=1;i<=8;i++) {
         cout<<i<<" : "<<chess[i]<<"\t";
     }
     ++result; 
 }
 
 //遞歸函數:放棋子
void putchess(int n) {
    int i;
    if(n<=8) {
        for(i=1;i<=8;i++) {//將第n行從第一格(i)開始往下放 
            chess[n]=i;
            if(check(n) == 1) {//若可放,則檢查是否放滿 
                if(n==8)
                    show_chess();//若已放滿到8行時,則表示找出一種解,打印出來 
                else
                    putchess(n+1);//若沒放滿則放下一行 putchess(n+1) 
            }
        }
    }
} 
  
int main() {
    cout<<"This is for 8*8 matrix"<<endl;
    putchess(1);
    return 0;
}

遞歸法