递归与递推

                                                   递归与递推

递推:

一般而言,递推是一种顺序递推的数学关系模型,好比通项公式。在数值计算的过程之中,只需要知道递推的边界值,也就是最开始的原始数值,比如斐波那契数中的第一个数值1和第二个数值1,知道这两个数值之后再根据最终的目标来计算,即第三条数值是第一条数值和第二条数值的相加,而第四条数值是第二条数值和第三条数值的相加,以此类推,直到求出第n条数值,也就是目标值为终点。

如下图所示,根据兔子无线繁殖的思路走下去,会发现,第一对兔子生出第二对兔子,而等第二对兔子长大之后,第一对兔子又生出第三对兔子,由于第二对兔子具有了繁殖能力,

第二对兔子由此生出第四对兔子。

总结以上斐波那契的规律,不难发现,顺着数值计算的方式并且以初始值为a1=1,a2=1这样的条件递推过去, 就可以建立最原始的数值模型,即得出ai=ai-1+ai-2这样的公式。

       斐波那契顺序递推源代码:

       public static int[] recursion(int n){

        int []a=new int[n];

        a[0]=1;

        a[1]=1;

        if(n<2){

            return null;

        }

        for(int i=2;i<n;i++){

            a[i]=a[i-1]+a[i-2];

        }

        return a;

}

输出代码:

int []a=recursion(8);

for(int i=0;i<a.length;i++){

    System.out.print(a[i]+" , ");

}

运算结果:

递归:

递归实际上是一种调用自己的函数,它往往从结果出发,每一次函数的调用都是从函数结果和当前传入值相关,然后再顺着函数结果的下一个函数结果和当前函数的传入值再次计算,直到最终破除这个调用的界限,即函数结果最终返回的是一个确定的值而不再是这个函数结果的本身调用。

比如斐波那契数,我们可以把斐波那契数当作一个函数完成它,即n->f(n)。为了方便理解,我们需要知道斐波那契数的数学模型,即1,1,2,3…n-1+n-2,n,不难发现,每一次计算的结果都是前两位数值相加,根据这条思路,就可以创建一个函数结果f(n),那么下一个函数结果就可以计算成f(n-1)+f(n-2),而f(n-1)又可以通过f(n-1)+f(n-2)继续计算。从下面的图就可以看出,f7会调用f6,f5;;;而f6又会调用f5,直到最终调用的结果是f2或者f1的时候那么就结束本次函数的调用,转而返回f4的调用状态,然后继续调用f2。调用时候发现返回的是边界条件,那么结束本次调用,然后返回f5的状态,发现下面还有需要调用的函数,转而去调用f3,然后再去计算,直到最后调用的函数是f7->f5->f3->f1这样的路线,则最终退出本次递归,返回最终的结果值。

根据以上推论,可以得出每一个数值的结果实际上是前两个结果的相加,即前两个函数f(n-1)+f(n-2),但是每一次两个结果的相加并不能确定最开始需要的那个边界,因此为了破除这种重复循环的问题,则需要在最开始的结果中加上n=1以及n=2这样的条件来解除循环。

       斐波那契递归代码:

       public static int recursion1(int n){

        System.out.print(n+",");

        if(n==1){

            return 1;

        }

        if(n==2) {

            return 1;

        }

        return recursion1(n-1)+recursion1(n-2);

}

运算推理:

运算结果:

递推与递归的本质区别在于,一个是顺着结果去推出公式,而另一个是逆着结果去获取结果,而后者一般称之为逆推法,即递推的一种逆推方法。相对于递归方法,递推显然在思考的过程中更加简洁明了,而递归法则是屏蔽了整个计算流程,只考虑结果和边界值,剩下的都由操作系统来负责辅助计算,这样往往会因为不确定边界值而进入死循环,这显然是一个非常致命的错误,而且递归在计算的过程之中,由于每一次函数的调用都会保存当前函数的状态,也就是把这个状态入栈,以寻找原始的函数的位置而进行下一次的函数调用,这样做的话,会造成进程进入死循环,因此递归往往不适用于亿级用户的调用。但是递归的好处也在于它简化了问题,可读性更好。

直接递归与间接递归:

       直接递归:

    直接递归实际上是函数调用自身。

     public static int fff(int x){

        System.out.println(x);

        if(x<=0){

            return x;

        }

        return fff(x-1);

    }

    间接递归:

    间接递归实际上是函数调用其它的递归函数。

public static int ttt(int y){

        return fff(y-1);

 }

递归与递推的相互转化:

      递归和递推是可以相互转化的,即两种运用是不相冲突且能合作在一起进行关系转换。

比如获取文件的所有子目录路径或文件路径。文件夹可以生出文件夹,而文件夹又可以生成文件,为了获取所有的文件夹目录和文件目录,需要遍历每一个文件夹来获取当前文件夹的状态,而每一次遍历又需要直到当前内容是否是文件夹以及文件,如果仍然是文件夹,那么就需要递归调用来获取最终的文件夹内容的路径。

如下图所示,要获取CattleText文件夹下面的所有子文件夹和文件夹绝对路径,首先要判断CattleText的派生是不是文件夹或者是文件,为了寻找到CattleText所有的派生,那么需要遍历CattleText所有文件夹和文件,而在这种时候,CattleText的父文件目录和文件名是已知的,那么可以通过父文件目录和文件生成当前文件路径,这种思想即顺序递推法。为了再获取CattleText的子文件里面的派生, 由于CattleText的子文件夹的派生以及它的派生的派生的个数是不清楚的,因此需要使用递归的方法,从源头上寻找文件,而不再是文件夹。而为了结束这场递归,则需要判断是否为文件来决定最终边界值

       获取文件源代码:

       public static void getFilePath(File files,List directory){

        File [] files1=files.listFiles();

        if(files1==null){

            return;

        }

        for(File file:files1){

            if(files.isDirectory()){

                System.out.println(file.getParent()+"\\"+file.getName());

//                directory.add(file.getAbsolutePath());

                getFilePath(file,directory);

            }else{

//                directory.add(file.getAbsolutePath());

            }

        }

    }

输出代码:

List<String> file=new ArrayList<>();

getFilePath(new File("E:\\code\\CattleText"),file);

for(String item:file) {

    System.out.println(item);

}

运算结果:

递归和递推的总结:

递推的边界条件一般很明显,而递归的边界条件比较隐蔽,容易容易被忽略。

递推的数学性一般较强,而递归的数学性相对较弱。

递推一般不划分阶段,而递归一般有较为明显的阶段。

递归往往是用来求一个最优值,而一般的递推往往是用来计数或求一个值

  • 12
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值