一、思想-多路递归

多路递归multi recursion就是在每次递归时包含多次(大于一次)的自身调用。也就是一个问题会被拆分成多个子问题。多路递归比单路递归在分析时间复杂度上比较复杂一些。

二、斐波那契数列

维基百科链接🔗:https:///wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0

百度百科链接🔗:https://baike.baidu.com/item/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97/99145

简要介绍:

数学上,斐波那契数是以递归的方法来定义:

Java-用递归的思想求斐波那契数列第n项的值_递归

用文字来说,就是斐波那契数列由0和1开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89、 144、 233、 377、 610、 987……

特别指出:0不是第一项,而是第零项。

三、例子

以 n = 4 为例,当我们用下面(第四部分)的代码实现时,这个多路递归的求解过程如下图所示。

Java-用递归的思想求斐波那契数列第n项的值_斐波那契数列_02

图1

我们可以看到,当这个问题被拆成两个子问题时,这个问题在递归时就变成了一个二叉树,同理,一个问题分成几个子问题在递归过程中就会形成几个叉的树,这也是叫做多路递归的原因,也是它复杂的原因。单路递归的过程是不分叉的,它是从头走到尾的。

递归函数的时间复杂度主要取决与递归的次数,也就是这个递归函数在执行的过程中一共被调用了多少次,以这个斐波那契数列为例,上图中有几个圆圈我们就调用了几次这个递归函数,当n=4时,一共调用了9次递归函数。以此类推,n = 3时调用了5次递归函数,n = 5时调用了15次递归函数。

要求的项n

递归函数调用的次数

n = 3

5

n = 4

9

n = 5

15

可知上述表格中描述的也是一个斐波那契数列,由公式2*f(n + 1) - 1,就可以求出斐波那契数列第n项的一个调用次数。

经过推导可知,斐波那契数列的时间复杂度为Θ(1.618^n)。指数级的时间复杂度说明这个算法不太好,还需要进行优化。如上图可以看到,多路递归中有很多次计算其实是重复的,如计算f(2)重复了两次,f(1)重复了两次,f(0)重复了两次等,n越大我们计算中的重复越多。那么我们怎么优化?就是尽量的让计算不要重复,详见四-3。

四、Java代码实现

1.求斐波那契数列的第n项

/*递归函数*/
    public static int f(int n){
			//写两个递归出口
        if(n == 0){
            return 0;
        }
        if(n ==1){
            return 1;
        }
			//调用两次自身的函数
        int x = f(n - 1);//要先走完这一步才能继续下面的代码
        int y = f(n - 2);
        return x + y;
    }

2.完整代码 

用 n = 8来测试,结果为21.

public class E06Fibonacci {

    public static void main(String[] args) {
        int f = f(8);
        System.out.println(f);
    }

    /*递归函数*/
    public static int f(int n){

        if(n == 0){
            return 0;
        }
        if(n ==1){
            return 1;
        }

        int x = f(n - 1);
        int y = f(n - 2);
        return x + y;
    }
}

3.代码优化-尽量不出现重复的计算

加入一个数组,存储已经求出来的值,需要用到值时直接从数组里面拿。

没有优化前的计算过程,以n = 5为例:

Java-用递归的思想求斐波那契数列第n项的值_斐波那契数列_03

图2

优化后的计算过程,以n = 5为例:

Java-用递归的思想求斐波那契数列第n项的值_多路_04

图3

从图2到图3,可以看到重复的计算没有了。故我们就可以用这种“记忆化”的方式来简化算法。这个过程我们可以形象的称为“剪枝”,就像把一棵树中不必要的枝条修剪掉一样。这是一种优化的思想。

改进之后的时间复杂度是O(n)。

虽然时间改进了,但是也付出了代价,就是空间成本,引入了一个额外的数组空间,额外占用的空间就称为空间复杂度。这里的空间复杂度是O(n),与数据的规模呈线性关系。所以,这个记忆法是用空间换时间的一种做法。

Memoization(记忆法,也称备忘录)改进的算法:

import java.util.Arrays;

//用Memoization(记忆法,也称备忘录)改进的算法
public class E01Fibonacci {
   
    //主函数,计算结果
    public static void main(String[] args) {
        int f = fibonacci(5);
        System.out.println(f);
    }
    
    public static int fibonacci(int n){
        //cache数组用来缓存斐波那契数列第n项的结果
        int[] cache = new int[n + 1];//若传进来的值是5则将f(5)存进索引为5的地方
        Arrays.fill(cache,-1);//初始化数组,这里是用-1填充数组[-1,-1,-1,-1,-1,-1]
        cache[0] = 0;
        cache[1] = 1;
        //[0,1,-1,-1,-1,-1] -1表示还未计算
        return f(n,cache);
    }

    /*递归函数*/
    public static int f(int n,int[] cache){

        if(cache[n] != -1){//表示已经计算出了结果
            return cache[n];
        }

        int x = f(n - 1,cache);
        int y = f(n - 2,cache);
        cache[n] = x + y;//现在计算的是斐波那契数列第n项的值,即当我们完成某次递归时要将结果存入数组。
        return cache[n];
    }
}