从斐波那契数列想到递归的优化
每天多学一点点~
话不多说,这就开始吧…
1.前言
过年在家,因为疫情的原因,不出门不串门,想着来年还要继续工作,继续生存,且码农年龄等因素,总之危机感四伏,学习不能少~之前总是学习各种高大上的中间件而忽略了基础知识的巩固,于是在家复习了一下数据结构和算法方面的知识。算法这玩意,要么用不到,要么玩死你,是真的难,如果不是专门搞算法的,学学基础就好,就像博主这样。多学一点总是好的,在这分享一下博主觉得有点用的知识点。
为什么要学习数据结构与算法呢?
-
BAT一线互联网公司面试必问技术:
为什么必问?数据结构与算法是所有计算机类的基础。大企业看重的是潜力。 -
不想做一辈子的CRUD工程师、业务工程师。。。
-
架构师必备,写出框架级的代码;API,写出开源级代码。
-
提升自己的能力,不被行业淘汰(前三条都是吹的,中年危机才是大头!!!趁着年轻多学点,争取早日上岸23333)
最后,武汉加油~
2.斐波那契数列
1 1 2 3 5 8 13 21
从第三个数开始 就等于前面两个数相加;
说到斐波那契数列,很容易想到递归
public static int fab(int n) {
if (n <= 2)
return 1; // 递归的终止条件
return fab(n - 1) + fab(n - 2); // 继续递归的过程
}
分析一段代码好坏,有两个指标,时间复杂度和空间复杂度,很显然,都是:O(2^n)
这样不仅耗时,而且很占内存,测试了一下fab(45),居然用了3s多,肯定不能接受
3.优化(一)——使用非递归
使用非递归。所有的递归代码理论上是一定可以转换成非递归的。
将斐波那契数列转化成非递归,直接循环
public static int noFab(int n) {
// 循环
if (n <= 2)
return 1;
int a = 1;
int b = 1;
int c = 0;
for (int i = 3; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
上诉代码的时间复杂度和空间复杂度为O(n),这样循环也很好理解,大大的加快了效率,耗时0s。。。
4.优化(二)——加入缓存
我们中间的运算结果保存起来,这样就可以把递归降至为o(n)
private static int data[]; // 初始化全部是0
public static int fab2(int n) { // 用数组来做缓存 时间负责都降为了O(n),空间也降至为O(n)
if (n <= 2)
return 1; // 递归的终止条件
if (data[n] > 0) { // 不为0,说明计算过了,直接返回,不再继续无意义递归
return data[n];
}
int res = fab2(n - 1) + fab2(n - 2); // 继续递归的过程
data[n] = res;
return res;
}
// mian方法测试的时候赋值
for (int i = 1; i <= 45; i++) {
data = new int[45+1];
long start = System.currentTimeMillis();
System.out.println(i + ":" + fab2(i) + " 所花费的时间为"
+ (System.currentTimeMillis() - start) + "ms");
}
这样虽然是递归,但是减少了很多没必要的递归次数,时间和空间都为O(n),耗时0s。。。
5.优化(三)——尾递归
什么是尾递归?
尾递归就是调用函数一定出现在末尾,没有任何其他的操作了。因为我们编译器在编译代码时,如果发现函数末尾已经没有操作了,这时候就不会创建新的栈,而且覆盖到前面去。
即倒着算,不需要在回溯了,因为我们每次会把中间结果带下去。
在优化斐波那契额数列的尾递归之前,先来个简单的方便理解——阶乘
- 递归阶乘
public static int fac(int n) { // 求N的阶乘 5!=5*4*3*2*1=> f(n)=n*f(n-1)
if (n <= 1)
return 1;
return n * fac(n - 1);
}
- 尾递归阶乘
/**
* 尾递归-----阶乘
* @param n 要求阶乘的数
* @param res 上一次的结果
* @return
*/
public static int tailFac(int n, int res) { // 尾递归 传结果,res就是我们每次保存的结果
// 5:1
// 4:5
// 3:20
// 2:60
// 1:120
System.out.println(n + ":" + res); // 这个地方打印出来的值是怎么样的?
if(n<=1){
return res;
}
return tailFac(n-1,n*res);
}
通过上述例子,再来写斐波那契数列的尾递归代码,即
public static int tailfab(int pre, int res, int n) {
if (n <= 2)
return res; // 递归的终止条件
return tailfab(res, pre + res, n - 1);
//参数:
/**
* n 要求的数
* res 上一次运算出来结果
* pre 上上一次运算出来的结果
*/
}
测试一下耗时0s。对于尾递归,个人认为是一种思想,还需要多练多思考,别这个看懂了,再来个问题就不会了。不过博主还是推荐前面两种优化方式,好理解,也不容易出错(毕竟博主自己尾递归也不是很明白~只能写出简单的)
6.结语
世上无难事,只怕有心人,每天积累一点点,fighting!!!