从斐波那契数列想到递归的优化

从斐波那契数列想到递归的优化

每天多学一点点~
话不多说,这就开始吧…

1.前言

过年在家,因为疫情的原因,不出门不串门,想着来年还要继续工作,继续生存,且码农年龄等因素,总之危机感四伏,学习不能少~之前总是学习各种高大上的中间件而忽略了基础知识的巩固,于是在家复习了一下数据结构和算法方面的知识。算法这玩意,要么用不到,要么玩死你,是真的难,如果不是专门搞算法的,学学基础就好,就像博主这样。多学一点总是好的,在这分享一下博主觉得有点用的知识点。
为什么要学习数据结构与算法呢?

  1. BAT一线互联网公司面试必问技术:
    为什么必问?数据结构与算法是所有计算机类的基础。大企业看重的是潜力。

  2. 不想做一辈子的CRUD工程师、业务工程师。。。

  3. 架构师必备,写出框架级的代码;API,写出开源级代码。

  4. 提升自己的能力,不被行业淘汰(前三条都是吹的,中年危机才是大头!!!趁着年轻多学点,争取早日上岸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.优化(三)——尾递归

什么是尾递归
尾递归就是调用函数一定出现在末尾,没有任何其他的操作了。因为我们编译器在编译代码时,如果发现函数末尾已经没有操作了,这时候就不会创建新的栈,而且覆盖到前面去。
倒着算,不需要在回溯了,因为我们每次会把中间结果带下去。
在优化斐波那契额数列的尾递归之前,先来个简单的方便理解——阶乘

  1. 递归阶乘
    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);
    }
  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!!!

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值