递归的理解(1)

递归函数

例子1 阶乘

#include <iostream>
using namespace std;
int factorial(int n)
{

    if(n <= 1)

        return 1;

    else

        return n * factorial(n-1);

}

int main()
{

    int i = 5;

    cout<<factorial(i)<<endl;

    return 0;

}

理解“函数调用栈”这个概念

例子2 (质)因数分解

#include <iostream>
#include <cmath>

using namespace std;

void Get_Divisior(int );
int main()
{
    int num = 100;
    Get_Divisior(num);
    return 0;
}


void Get_Divisior(int number)
{
    double sqrt_of_number = sqrt(number);

    for(int i = 2;i<=sqrt_of_number;i++)
    {
        if(number%i==0){
            cout<<i<<", " ;
            Get_Divisior(number/i);
            return ;
        }
    }

    cout<<number<<endl;
    return ;
}

例子3 最大公因数(greatest common factor)

最大的公共因数,比如15和25的最大公因数是5
20和10 2、5和10都是公因数,10是它们的最大公因数

在这里我们使用辗转相除法编程
辗转相乘法的好处就是便于使用递归,计算机会更喜欢辗转相除法而不是表格法或者短除法
关于辗转相除法可以查看这个视频
https://www.bilibili.com/video/BV19r4y127fu/?spm_id_from=333.788.recommend_more_video.6

过程就是
被除数 ÷ 除数 = 商 ······余数
除数成为新的被除数,而余数成为新的除数
···
当某一时刻
被除数 ÷ 除数 = 商 ······ 0时,此时的除数就是最大公因数
可以看到,这个就有个递归的思想在里面
“除数成为新的被除数,而余数成为新的除数”

#include <iostream>
#include <cmath>

using namespace std;
//指示非法输入用
int counter =0;
int Greatest_Common_Factor(int number1,int number2)
{
    if((number1<=0 || number2 <=0) && counter == 0)
    {
        
        cout<<"invalid inputs"<<endl;
        return false;
    }

    counter = 1;

    if(number2 == 0)
    {
        return number1;
    }
    else
        return Greatest_Common_Factor(number2, number1%number2);
}


int main()
{
    int A = 0;
    int B = 342;
    int result;
    result = Greatest_Common_Factor(A,B);
    cout<<result<<endl;
    return 0;
}

例子4 汉诺塔(Tower of Hanoi)

汉诺塔概述

汉诺塔有三个塔,第一个塔由一叠穿孔圆盘组成,圆盘由下到上依次变小,我们需要将这一叠圆盘从1塔移动到3塔,并需要遵守以下规则

  • 一次只能移动一个盘
  • 大盘不能叠在小盘上

递归的思路,就是假定已经知道如何移动n-1个盘(这时候我们还没开始移动盘),那么
我们为了完成任务,需要

  • 将n-1和盘从源塔(1塔)移到2塔
  • 将最底下的那个盘动源塔移动到目标塔(3塔)
  • 将n-1个盘从2塔移动到目标塔

显然,第一个任务是假定已知的(虽然我们还不知道具体步骤),而第二个任务很无脑,
第三个任务则很艰巨,不过,既然假定已知如何移动n-1个塔,那当n变成n-1时,n-1-1个盘子的移动不是更简单吗

递归的英文是recurrence,意思就是”重复地发生“,n是一个可变的参数,但问题的整体结构不变
正如那句著名的论断
“历史不会重复,但是会惊人的相似”
“递归技术使得我们能在已知如何解决n-1的情况下解决n

但递归有一个很重要的条件,就是一定要有退出机制
这里的退出机制也是很简单很显而易见的————n==1时

代码实现汉诺塔

#include <iostream>

using namespace std;

void Move_a_Ring( int src_tow, int dest_tow);
void Move_Rings(int n, int src_tow, int dest_tow, int other_tow);
int main()
{

    int n =3;
    Move_Rings(n,1,3,2);
    return 0;
}


void Move_Rings(int n, int src_tow, int dest_tow, int other_tow)
{
    if(n==1){
        Move_a_Ring(src_tow, dest_tow);
    }
    else
    {
        Move_Rings(n-1,src_tow, other_tow, dest_tow);
        Move_a_Ring(src_tow, dest_tow);
        Move_Rings(n-1,other_tow, dest_tow, src_tow);
    }
}
对代码的解读

表面上看,我们不过是仅仅是根据上文的叙述进行相应的编程而已
在假定已经知道如何移动n-1个盘的情况下

  • 将n-1和盘从源塔(1塔)移到2塔
  • 将最底下的那个盘动源塔移动到目标塔(3塔)
  • 将n-1个盘从2塔移动到目标塔
    代码最核心的部分在这一部分
Move_Rings(n-1,src_tow, other_tow, dest_tow);
Move_a_Ring(src_tow, dest_tow);
Move_Rings(n-1,other_tow, dest_tow, src_tow);

这三句话的意思就是,我这时候在源塔上有n个盘子

  • 我先将n-1个盘子从源塔移到其他塔上
  • 再将源塔上残留的那个当前n个盘子中最大最底下的那个盘子移到目标塔
  • 将n-1个盘子从其他塔移到目标塔上
    注意到,在第三步中,有一个“将n-1个盘子从其他塔移到目标塔上”,这时候等于是
    其他塔和源塔身份互换
    而我们又假定我们是能够移动n-1个盘子的,这和n的大小无关。
    因此我们想执行移动n个盘子的操作,就要执行两次n-1次盘子的操作
    而要执行一次n-1个盘子的操作,又要执行两次移动n-2个盘子的操作,所以总的来说,n加一,输出行数就增加一倍
    n增加一倍,操作就变成原来的2^n倍,是一个指数增长的过程
    其时间复杂度为O(2^n)

总结

递归算法其实在于给了我们一个抽象的思考框架,简而言之,就是一句话
在假定已知(n-1)的情况可以解决时,我们只要解决了n,就解决了全部
比如,在阶乘的例子中,我们要算n!,便可以假定了factorial(n-1)已经可以算出来
那么我们唯一需要知道的,需要做的,就是factorial(n) = n*factorial(n-1);
factirial(n-1)中的n-1成为了新的n,而根据假定,factorial(n-2)的解决方法又是已知的
广而推之,最后就到了需要我们出手的时刻
factorial(1)
这个东西我们也需要知道怎么解决,否则递归就没办法退出,re—currence不是per-currence
一再出现不代表会一直出现,天下没有不散的宴席,量变总有质变的一天。
因此虽然时间复杂度增加了,但问题的解决变得无脑了——这就是递归


同理,在解决质因子分解的问题的时,我们看到
如果n不是一个质数,且能够被i整除(i是n的最小质因子),那么就可以假定
算法已知(n/i)的最小质因子如何计算,继而n/i成为了新的n(n’)
这也是递归的“在假定已知(n-1)的情况可以解决时,我们只要解决了n,就解决了全部”的原理

因为n的质因子分解被我们变成了i*因子(n/i)
若(n/i)的分解问题被假定已经解决,则整个问题就算解决了。
而对于n‘而言,如果它不是一个质数,且i’为其最小质因子,根据假定
算法也知道(n’/i’)的最小质因子如何计算
同样的,我们要设置一个退出机制:直到当n’‘是质数时,算法输出n’'是n的一个质因子并退出


在辗转相除法的例子中,我们要求两个正整数A、B(A>B)的最大公因数
根据辗转相除法的原理,最重要的一个结论就是
A,B的最大公因数和B,A%B的最大公因数是一样的,

这不就是完美的、递归的“在假定已知(n-1)的情况可以解决时,我们只要解决了n,就解决了全部”的原理的具体化吗?

若假定如何求(B,A%B)的方法是已知的,则问题便告解决
因此系统假定了如何求(B,A%B)的方法是已知的
因此求 A,B 的最大公约数就转化为求 B, A%B 的最大公约数
然后B 变成了A’, A%B变成了B‘
为了求 A’, B’ 的最大公约数,我们又需要求 B’, A’%B’
而根据假定,如何求 B’, A’%B’ 的最大公约数又是已知的
因此我们只需要知道退出机制是什么就可了。
退出机制就是,当出现一组(A’,B’),其中A’%B’ == 0,系统就得出B‘为A,B的最大公约数 并退出


在汉诺塔的例子中,有一点不太一样,就是增加了一个Move_a_Ring,当然这个操作是很简单很简单的
在汉诺塔的例子中,我们需要移动n个盘,因此我们就假定
如何移动n-1个盘的操作是已知的,而移动一个盘的操作也是已知的
因此问题就被分解为

  • 将n-1和盘从源塔(1塔)移到2塔(移动n-1个盘
  • 将最底下的那个盘动源塔移动到目标塔(3塔)(移动一个盘
  • 将n-1个盘从2塔移动到目标塔(移动n-1个盘

而要移动n-1个盘,根据假定
如何移动n-2个盘的方法也是已知的
因此,广而推之,就到了需要退出机制的时刻
刚好,我们前面已经知道怎么移动一个盘
而移动一个盘作为递归的退出机制再好不过了。
因此问题就解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值