递归算法是一种直接或间接调用自身函数或方法的算法,实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。
递归与for循环区别:
递归:可看作到楼顶取东西,从一楼开始爬,看,不是楼顶,继续爬,每层楼看上去都一样,执行过程也一样,等你到楼顶了,取到了你想要的东西,然后一层一层的退回来。
循环:可看作驴拉磨,无论驴拉多少次,都是在原地打转,位置不变,变化的只是磨盘里的东西。
为什么要用递归:我们要解决的很多问题本身的定义就是“递归”的,比如最常见的“树的遍历”,树本身就是一个递归的结构,如果你想要用程序来完成对它的遍历,最自然的方式也就是递归地把这个遍历过程描述出来,试想一下如果你偏要用循环的方式来完成树的遍历得多麻烦?代码长度也会增长很多很多;所以面对递归的问题,递归的解法是最自然的、最好理解的。
1、经典问题——斐波那契数列之兔子问题
斐波那契数列:兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么一年以后可以繁殖多少对兔子?
依次类推可以列出下表:
经过月数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
幼仔对数 | 1 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 |
成兔对数 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 |
总体对数 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 | 233 |
幼仔对数=前月成兔对数 成兔对数=前月成兔对数+前月幼仔对数 总体对数=本月成兔对数+本月幼仔对数
前面相邻两项之和,构成了后一项。
2、扩展问题——奶牛问题
一个农夫养了一头牛,三年后,这头牛每年会生出1头牛,生出来的牛三年后,又可以每年生出一头牛……问农夫10年后有多少头牛?n年呢?(用JAVA实现)
2.1 用递归的方法 (结果为28)
public class Cow {
static int count = 1; //牛的数量
private static void feedCow(int year,int age){
year++; //第几年
age++; // 对应牛的年龄
if(year<=10){
if(age>=3){
count++;
feedCow(year,0); // 新生牛
}
feedCow(year,age); //老牛长一岁
}
}
public static void main(String[] args) {
new Cow().feedCow(0, 0);
System.out.println(count);
}
}
思路:今年的牛的数目=去年的牛的数目+三年前牛的数目 即:f(n)=f(n-1)+f(n-3)
这里 f(n-2) 第一年 、 f(n-1) 第二年、 f(n) 第三年
int Fibonacci(int n){
if (n==1 || n==2 || n==3){
return 1;
}
return Fibonacci(n-1)+Fibonacci(n-3);
}
扩展:m年则得到 f(n)=f(n-1)+f(n-m) 又假如每头牛的寿命只有十年,则 f(n)=f(n-1)+f(n-3)-f(n-10)
3、递归优化
缓存原理:用类变量保存已经求出来的斐波那契结果,内存中这个结果如果已经有了,那么可以直接返回,不用再次计算。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 递归求斐波那契数列
*/
public class Recursion {
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedReader strin=new BufferedReader(new InputStreamReader(System.in));
int n=0;
try {
System.out.println("请输入50以内数字:");
n=Integer.parseInt(strin.readLine());
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Fibonacci.fibonacci(n));
Fibonacci.printMap();
}
}
/**
* 工具类
* 添加了缓存控制
*/
class Fibonacci{
private static Map m=new HashMap();//用于保存n对应的斐波那契值
static long fibonacci(int n){
if(n<=1) return 1;
//缓存!如果存在直接返回
if(m.containsKey(n))
return (long)m.get(n);
long store=fibonacci(n-1)+fibonacci(n-2);
m.put(n, store);
return store;
}
/**
* 对m的读取可能破坏了封装
* 打印出map
*/
public static void printMap(){
Set keySet = m.keySet();
Iterator keys=keySet.iterator();
while(keys.hasNext()){
int temp=(int)keys.next();
System.out.println(temp+":"+m.get(temp));
}
}
}
4、斐波那契几种实现方法
4.1非尾递归实现(书本上经常出现)
public static long fibo1(long n){
if(n<2) return n;
return fibo1(n-1)+fibo1(n-2); //小心栈溢出
}
4.2 缓存实现
public static int LENGTH=30; //过大了就会占用过多空间
public static long[] cache=new long[LENGTH];
public static long fibo2(int n){
if(n<2) return n;
if(n>=LENGTH){
return fibo2(n-1)+fibo2(n-2);
}else if(cache[n]==0){
cache[n]=fibo2(n-1)+fibo2(n-2); //减少重复计算
}
return cache[n];
}
4.3 迭代实现
public static long fibo3(long n){
if(n<2) return n;
long pre=1,prepre=1,ret=0;
for(int i=2;i<n;i++){
ret=pre+prepre;
prepre=pre;
pre=ret;
}
return ret;
}
4.4 尾递归实现
public static long fibo4(int n){
if(n<2) return n;
return fibo4Helper(n, 1, 1, 3); //保持与非尾递归接口不变,是借助帮助方法实现尾递归的
}
private static long fibo4Helper(int n,long prepre,long pre,int begin){
if(n==begin) return pre+prepre;
return fibo4Helper(n, pre, prepre+pre, ++begin); //这里相当于迭代实现for-loop的浓缩
}