今天学习了一下算法思维–递归思想;递归是衡量一个程序员初中级的分水岭,主要是代码可读性强,可以使得代码更加简洁。
初识递归
举个例子:在抗战时期,某个队伍中在夜里行军,由于天色昏暗,无法确认某某士兵所在的位置是多少号(这里指的是士兵距离最前一位士兵的位置),这个的时候可以模拟递归的思维,只要改士兵找到前面的士兵,询问相同的问题,以此类推,一直往前,直到找到第一个士兵的时候,之后又从第一个士兵返回,也已相同的形式像报数一样的告诉他所在的位置。
分析:整个过程如其名字一样一个递的过程和一个归的过程。可以简单的使用数学思想表达:f(n)=f(n-1)+1;如此以来,就确定了一个未知的士兵的位置。
public static int funcdg(int n){
if (n<2){
return 1;
}
return funcdg(n-1)+1;
}
代码大致如此
总结一下以上的思想:可以得出一些什么场景下使用递归的思路。
1.一个大问题可以拆分成无数的相同的小问题,如上,我在哪个位置?这个大问题可以分给前面的每个士兵相同的小问题。
2.最终总有一个已知解,第一个士兵一定知道自己在哪个位置,因为他前面没有人
基于以上的思路在学习下经典的递归算法使用场景–》》斐波拉契序列,也就是前两个数字之和得到第三个数字,数学表达F(n)=F(n-1)+F(n-2);
public static int fbl(int n){
if (n<=2){
return 1;
}
return fbl(n-1)+fbl(n-2);
}
三行代码就解决了这个问题。是不是很简洁?同时也符合以上所说的思想,即要求得第几个数字的斐波拉契数字是多少,这个问题可以一直拆分成相同的问题,最终找到,F(1)=1,F(2)=1;这个问题就迎刃而解。
基于上述问题,其实也可以看到时间复杂度和空间复杂度在2^n
如何优化?
使用缓存数组解决问题:
private static int[] data;//缓存数组
//使用缓存作用降低空间复杂度O(n)
public static int fbl2(int n){
if (n<=2){
return 1;
}
if (data!=null&&data[n]!=0){
return data[n];
}
int res=fbl2(n-1)+fbl2(n-2);
data[n]=res;
return res;
}
private static int[] data;声明一个静态的缓存数组即可。这样的话就可以减少一些相同的裂变,例如F(3)=F(1)+F(2)这种裂变,从而降低时间复杂度和空间复杂度
使得为O(n)
斐波拉契可以转化为非斐波拉契
/**
*使用非菲薄啦求解===》递归算法一定可以转化为非递归算法
*/
public static int nofbl(int n){
if (n<=2){
return 1;
}
int f1=1;
int f2=1;
int f3=0;
for(int i=3;i<=n;i++){
f3=f1+f2;
f1=f2;
f2=f3;
}
return f3;
}
这个可以和之前的递归做比较可以看出递归代码的简洁和易读性
尾递归
尾递归算法是一个比较高级的递归思想,一般的话,很容易出错,所以用得好的额一般是大神。我这里也是简单介绍。大致的思想是,只有递的过程没有归的过程。同时也要return 中只有方法名没有其他的参数。
先看一个阶乘的算法:
/*
* 求一个阶乘!
*
* */
public static int qjc(int n){
System.out.println("求出的值为:"+n);
if (n<2){
return 1;
}
return n*qjc(n-1);
}
改成尾递归:
public static int tailarch(int n,int res){
if (n<2){
return n*res;
}
return tailarch(n-1,n*res);
}
可以把以上的斐波拉契改成尾递归
public static int tailarch2(int pre,int res,int n){
if (n<=2){
return 1;
}
return tailarch2(res,pre+res,n-1);
}
这就是今天以上所学!