文章目录
1.递归三条件
周末你带着女朋友去电影院看电影,女朋友问你,咱们现在坐在第几排啊?电影院里面太黑了,看不清,没法数,现在你怎么办?
别忘了你是程序员,这个可难不倒你,递归就开始排上用场了。于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也问他前面的人。就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。直到你前面的人告诉你他在哪一排,于是你就知道答案了。
这就是一个非常标准的递归求解问题的分解过程,去的过程叫**“递”,回来的过程叫“归”**。基本上,所有的递归问题都可以用递推公式来表示。刚刚这个生活中的例子,我们用递推公式将它表示出来就是这样的
f(n) = f(n-1) + 1
相应的代码如下:
public int f(n) {
if(n == 1) {
return 1;
}
return f(n-1) + 1;
}
1.1 一个问题可分解为子问题
子问题:就是数据规模更小的问题,拿前面的问题,要想知道你在那一排,可以分解为”前一排的人在那一排“这样的子问题。
1.2 分解完后,问题与子问题除了数据规模不一样,求解思路完全一样
前一排的人求解他自己在哪一排的方法,和我再那一排的方法完全一样。
1.3 存在递归终止条件
需要有终止条件,不然会陷入死循环,比如前面问题,第一排知道自己的位置,所以f(1) = 1就是终止条件。
2.如何写递归代码
2.1 将大问题化为小问题,写出地推公式,找到终止条件
只考虑当前级别和下一级别的问题,不去思考层层迭代问题。比如,现在呢总共有7个台阶,一次可以走一阶,也可以走两阶,请问有几种走法?可以将问题分解为:只有两层:这一层和下一层的问题。如果第一次走1部,则下一层剩下 (n-1)个台阶;如果第一次走两步,则下一层只剩下(n-2)个台阶。因此递推公式为:
f(n) = f(n-1) + f(n-2);
终止条件为:f(1)=1. 这个条件满足条件吗?用n=1,2,3,4试一下:n=1, f(2) = f(1) + f(0), 变成了 f(1)=0,f(0)=1这两个终止条件。f(0)=1,走0个台阶有1中走发。这个有点奇怪。优化一下,变成 f(1)=1, f(2)=2. 当n > 2,都可以拆成 f(1)与f(2)的组合。
2.2 翻译成代码
/**
* f(1)=1,f(0)=1
* @param n
* @return
*/
public static long fstrange(int n) {
if(n == 1) return 1l;
if(n == 0) return 1l;
return f(n-1) + f(n-2);
}
/**
* no cache.
* @param n
* @return
*/
public static long f(int n) {
if(n == 1) return 1l;
if(n == 2) return 2l;
return f(n-1) + f(n-2);
}
3.注意实现
3.1 堆栈溢出
栈-栈帧有自己的空间,用于存储局部变量表和操作数栈等
,如果太深,会导致内存不够发送内存溢出问题。
3.2 重复计算
上图中的 f(4)/f(3)/f(2)/…等都会被重复计算多次,时间复杂度很高,可以采取空间换时间的思路(其实空间复杂度不一定比递归高,因为递归有函数栈帧开销),可以将计算好的结果存起来,后面直接从缓存里面读取。代码如下:
/**
* cache.
* @param n
* @return
*/
public static long fwithcache(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
if(map.containsKey(n)) {
return map.get(n);
}
long ret = fwithcache(n-1) + fwithcache(n-2);
map.put(n, ret);
return ret;
}
用我的2017年macPro比较了一下,计算n=50次的规模,加缓存比不加缓存多用了 50.186s(50188-2) :
public static void main(String[] args) {
System.out.println(f(1));
System.out.println(f(2));
System.out.println(f(10) + "==" + fstrange(10) + "?");
// 无缓存
long l = System.currentTimeMillis();
System.out.println(f(50));
System.out.println(System.currentTimeMillis() - l);
// 有缓存
l = System.currentTimeMillis();
System.out.println(fwithcache(50));
System.out.println(System.currentTimeMillis() - l);
}
Output:
1
2
89==89?
20365011074
50188
20365011074
2