首页 > 代码库 > 递归法

递归法

递归法(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;
}

 

递归法