一、思想-多路递归
多路递归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
简要介绍:
在数学上,斐波那契数是以递归的方法来定义:
用文字来说,就是斐波那契数列由0和1开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89、 144、 233、 377、 610、 987……
特别指出:0不是第一项,而是第零项。
三、例子
以 n = 4 为例,当我们用下面(第四部分)的代码实现时,这个多路递归的求解过程如下图所示。
图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为例:
图2
优化后的计算过程,以n = 5为例:
图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];
}
}