递归迭代和分治:递归的优化
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了递归迭代和分治:递归的优化相关的知识,希望对你有一定的参考价值。
1.如何优雅地说一万次“我爱你”
咕泡曾经举行过一次线上小活动,看谁说更多的“我爱咕泡”,我当时写了这么一段代码:
public class FibonacciTest {
public static int count = 0;
public static void main(String[] args) {
Fibonacci(20);
System.out.println("count:" + count);
}
public static int Fibonacci(int n) {
System.out.println("我爱咕泡");
count++;
if (n == 0 || n == 1)
return n;
else {
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
}
你知道这个结果是多少吗?count是21891次,2万多次。
而当n=30 的时候结果是2692537,也就是接近270万,而我们求的不过是三十几个数的和而已,由此可见递归的重复计算是非常高的。
例如当n=8的时候,我们看下面的结构图就已经有很多重复计算了:
在很多场景,我们也需要对递归进一步优化。
如何优化?最简单的方式是将计算的结果保证起来,例如把 f(4) 的计算结果保证起来,当再次要计算 f(4) 的时候,我们先判断一下,之前是否计算过,如果计算过,直接把 f(4) 的结果取出来就可以了,没有计算过的话,再递归计算。
用什么保存呢?可以用数组或者 HashMap 保存,我们用数组来保存吧,把 n 作为我们的数组下标,f(n) 作为值,例如 arr[n] = f(n)。f(n) 还没有计算过的时候,我们让 arr[n] 等于一个特殊值,例如 arr[n] = -1。
当我们要判断的时候,如果 arr[n] = -1,则证明 f(n) 没有计算过,否则, f(n) 就已经计算过了,且 f(n) = arr[n]。直接把值取出来就行了。代码如下:
// 我们实现假定 arr 数组已经初始化好的了。
int f(int n){
if(n <= 2){
return n;
}
//先判断有没计算过
if(arr[n] != -1){
//计算过,直接返回
return arr[n];
}else{
// 没有计算过,递归计算,并且把结果保存到 arr数组里
arr[n] = f(n-1) + f(n-2);
return arr[n];
}
}
也就是说,使用递归的时候,需要考虑有没有重复计算,如果重复计算了,一般要把计算过的状态保存起来。
2. 考虑是否可以自底向上
对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。
不过,有时候当 n 比较大的时候,例如当 n = 10000 时,那么必须要往下递归10000层直到 n <=1 才将结果慢慢返回,如果n太大的话,可能栈空间会不够用。
对于这种情况,其实我们是可以考虑自底向上的做法的。例如我知道
f(1) = 1;
f(2) = 2;
那么我们就可以推出 f(3) = f(2) + f(1) = 3。从而可以推出f(4),f(5)等直到f(n)。因此,我们可以考虑使用自底向上的方法来取代递归,代码如下:
public int f(int n) {
if(n <= 2)
return n;
int f1 = 1;
int f2 = 2;
int sum = 0;
for (int i = 3; i <= n; i++) {
sum = f1 + f2;
f1 = f2;
f2 = sum;
}
return sum;
}
这种方法,也被称之为递推。
以上是关于递归迭代和分治:递归的优化的主要内容,如果未能解决你的问题,请参考以下文章