递归迭代和分治:递归的优化

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;   }

这种方法,也被称之为递推。

以上是关于递归迭代和分治:递归的优化的主要内容,如果未能解决你的问题,请参考以下文章

递归迭代和分治法

递归迭代和分治:递归

递归和分治思想

递归迭代和分治:递归的典型例子

递归迭代和分治:迭代器原理

递归迭代和分治:分治与二分查找