• 递归法


    递归法(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]<<"	";
         }
         ++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;
    }
    拥抱明天! 不给自己做枷锁去限制自己。 别让时代的悲哀,成为你人生的悲哀。
  • 相关阅读:
    CUDA教程三、cuda的随机数生成
    python 使用element tree 解析 xml
    [转载] Shell正则表达式大全
    git 更新项目中子模块的版本
    git 切换到本地不存在但远端存在的分支
    vue.js3:在css中使用变量(vue@3.2.6)
    mybatis做update:动态传入要更新的字段名和字段值(mybatis 3.5.7)
    npm全局安装和非全局安装的区别(node v16.13.1 / npm 8.4.1)
    vue.js3:用mitt发送接收事件消息(vue@3.2.6 / mitt@3.0.0)
    vue3: 动态修改favicon(网站的ico 图标)(vue@3.2.26)
  • 原文地址:https://www.cnblogs.com/dd2hm/p/7225223.html
Copyright © 2020-2023  润新知