再谈递归与循环

  • 在某些算法中,可能需要重复计算相同的问题,通常我们可以选择用递归或者循环两种方法。递归是一个函数内部的调用这个函数自身。循环则是通过设置计算的初始值以及终止条件,在一个范围内重复运算。比如,我们求累加1+2+3+…n,这个既可以用循环也可以用递归
 /*** 递归实现* */public Long addFrom1N(long n){return n <= 0 ? 0 : n + addFrom1N(n-1);}/*** 循环实现* */public long addForm1N_interative(long n){if(n <=0){return 0;}long result= 0;for (long i = 1; i < n; i++) {result +=i;}return result;}
  • 如上案例实现,递归代码简洁,循环代码比较多,同样的在之前的文章二叉树实现原理在树的前序,中序,后序遍历的代码中,递归实现也明显比循环实现要简洁的多,所以我们尽量用递归来表达我们的算法思想。

  • 递归的缺点:

    • 递归优点显著,由于是函数自身的调用,而函数调用有时间与空间的消耗:每一次调用都需要内存栈分配空间保存参数返回地址以及临时变量,而往栈里压入与弹出数据也需要时间,那就自然递归实现的效率比同等条件下循环要低下了
    • 另外递归中可能有很多计算是重复的,这个比较致命,对性能带来很大影响。
    • 除了效率,递归还有可能出现调用栈溢出问题。因为每个进程栈空间有限,当递归次数太多,超出栈容量,导致栈溢出。
案例分析:斐波那契数列
  • 题目:写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。斐波那契数列的定义如下:
f(n) = f(n-1) + f(n-2) , n>1
f(n) = 0 , n=0
f(n) = 1 , n=1
斐波那契数列递归实现
  • 记得谭浩强版本的C语言中讲解递归的时候就是用的斐波那契数列的案例,所以对这个问题非常的熟悉。看到之后自然就能提供如下代码:
 /*** f(n) = f(n-1) + f(n-2)* n > 2* n=1 : f(1) = 1* n=2 : f(2) = 1* f(3) = f(1) + f(2) = 1 + 1 = 2;*/public static Long getFibonacci(int n) {if (n <= 0L) {return 0L;}if (n == 1L || n == 2L) {return 1L;}return getFibonacci(n - 1) + getFibonacci(n - 2);}
  • 教科书上只是为了讲解递归,这个案例正好比较合适,并不表示是最优解,其实以上方法是一种存在严重效率问题的解法,如下分析:

    • 我们求解f(10) 需要求解f(9),f(8),继而需要先求解f(8),f(7),…我们可以用树形结构来说明这种依赖求解关系:

  • 如上图中分解,树中很多节点是重复的,而且重复的节点数会随着n的增大指数级别的增大,我们可以用以上算法测试第100项的值,慢的你怀疑人生。
我认为的最优解:动态规划(循环实现)
  • 改进方法并不复杂,上述代码中是因为大量重复计算,我们只要避免重复计算就行了。比如我们将已经计算好的数列保存到一个临时变量,下次计算直接查找前一次计算的结果,就无须重复计算之前的值。
  • 例如我们从下往上算,根据f(0) 和f(1) 求f(2), 继续f(1),f(2) 求f(3),依次类推得出第n项。很容易得出解。而且时间复杂度控制在O(n)
  • 如下代码实现:
 /*** 动态规划求值* */public static Long getFibonacciGreat(int n) {if (n <= 0L) {return 0L;}if (n == 1L || n == 2L) {return 1L;}Long answer = 1L;Long last = 1L;Long nextLast = 1L;for (Long i = 0L; i < n - 2; i++) {answer = last + nextLast;nextLast = last;last = answer;}return answer;}
时间复杂度更优O(logn)但是复杂度过于高的解法
  • 一般以上解法是最优解,但是如果在追求时间复杂度最优的算法场景下,我们有更快的O(logn)的算法。由于这种算法需要一个比较生僻的数学公式(离散数学没学好的代价),因此很少有人会去写这种算法,此处我们只介绍该算法,不递推数学公式(不会),如下:
  • 先介绍数学公式如下:

[f(n)f(n−1)f(n−1)f(n−2)]=[1110]n−1\left[ \begin{matrix} f(n) &f(n-1) \\ f(n-1) & f(n-2) \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] ^{n-1} [f(n)f(n−1)​f(n−1)f(n−2)​]=[11​10​]n−1

  • 如上数学公式可以用数学归纳法证明,有了这个公式我们只需要求如下矩阵的值,既可以的到f(n)的值,
    [1110]n−1\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] ^{n-1} [11​10​]n−1

  • 那么我们只需要求,基础矩阵的乘方问题。如果只是简单的从0~n循环,n次方需要n次运算,那么时间复杂度还是O(n),并不比之前的方法快,但是我们可以考虑乘方的如下性质
    [1110]\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] [11​10​]

  • 情况一
    an=an/2∗an/2,n为偶数a^n = a^{n/2}* a^{n/2} , n为偶数 an=an/2∗an/2,n为偶数

  • 情况二
    an=a(n−1)/2∗a(n−1)/2∗a,n为奇数a^n = a^{(n-1)/2}* a^{(n-1)/2}*a , n为奇数 an=a(n−1)/2∗a(n−1)/2∗a,n为奇数

  • 从上面公式我们看出,要求n次方,我们可以先求n/2次方,再把n/2次方平凡就可以。这可以用递归的思路来实现。

  • 我们用如下方式实现,因为存在矩阵的计算,用代码实现比较繁琐,如下:

/*** 矩阵对象定义* @author liaojiamin* @Date:Created in 15:21 2021/3/16*/
public class Matrix2By2 {private long m_00;private long m_01;private long m_10;private long m_11;public Matrix2By2(){this.m_00 = 0;this.m_01 = 0;this.m_10 = 0;this.m_11 = 0;}public Matrix2By2(long m00, long m01, long m10, long m11){this.m_00 = m00;this.m_01 = m01;this.m_10 = m10;this.m_11 = m11;}public long getM_00() {return m_00;}public void setM_00(long m_00) {this.m_00 = m_00;}public long getM_01() {return m_01;}public void setM_01(long m_01) {this.m_01 = m_01;}public long getM_10() {return m_10;}public void setM_10(long m_10) {this.m_10 = m_10;}public long getM_11() {return m_11;}public void setM_11(long m_11) {this.m_11 = m_11;}
}*** 获取斐波那契数列* @author liaojiamin* @Date:Created in 12:06 2021/2/2*/
public class Fibonacci {/*** 矩阵乘法求值* */public static Matrix2By2 matrixMultiply(Matrix2By2 matrix1, Matrix2By2 matrix2){return new Matrix2By2(matrix1.getM_00()*matrix2.getM_00() + matrix1.getM_01()*matrix2.getM_10(),matrix1.getM_00()*matrix2.getM_01() + matrix1.getM_01()*matrix2.getM_11(),matrix1.getM_10()*matrix2.getM_00() + matrix1.getM_11()*matrix2.getM_10(),matrix1.getM_10()*matrix2.getM_01() + matrix1.getM_11()*matrix2.getM_11());}/*** 矩阵乘方实现* */public static Matrix2By2 matrixPower(int n){if( n<= 0){return new Matrix2By2();}Matrix2By2 matrix = new Matrix2By2();if(n==1){matrix = new Matrix2By2(1,1,1,0);}else if(n%2 == 0){matrix = matrixPower(n/2);matrix = matrixMultiply(matrix, matrix);}else if(n%2 == 1){matrix = matrixPower((n-1)/2);matrix = matrixMultiply(matrix, matrix);matrix = matrixMultiply(matrix, new Matrix2By2(1,1,1,0));}return matrix;}public static long getFibonacciBest(int n){if(n == 0){return 0;}if (n <= 0) {return 0;}if (n == 1) {return 1;}Matrix2By2 powerNminus2 = matrixPower(n-1);return powerNminus2.getM_00();}public static void main(String[] args) {System.out.println("-------------------------1");System.out.println(System.currentTimeMillis()/1000);System.out.println(getFibonacci(40));System.out.println(System.currentTimeMillis()/1000);System.out.println("-------------------------2");System.out.println(System.currentTimeMillis()/1000);System.out.println(getFibonacciGreat(40));System.out.println(System.currentTimeMillis()/1000);System.out.println("-------------------------3");System.out.println(System.currentTimeMillis()/1000);System.out.println(getFibonacciBest(40));System.out.println(System.currentTimeMillis()/1000);}
}
  • 时间复杂度:设为f(n),其中n 是矩阵的幂次。从上述代码中不难得出f(n) = f(n/2) + O(1) 。利用主定理,可以解得f(n) = O(log⁡n\log^{n}logn)

  • 空间复杂度:每一次递归调用时新建了一个变量matrixPower(n/2)。由于代码需要执行log⁡2n\log_2^{n}log2n​次,即递归深度是log⁡2n\log_2^{n}log2n​ ,所以空间复杂度是O(log⁡n\log^{n}logn)

解法比较
  • 用不同方法求解斐波那契数列的时间效率有很大区别。第一种基于递归的解法,时间复杂度效率低,时间开发中不可能会用
  • 第二种将递归算法用循环实现,极大提高效率
  • 第三种方法将斐波那契数列转换炒年糕矩阵n次方求解,少有这种算法出现,此处只是提出这种解法而已
变种题型
  • 处理斐波那契数列这种问题,还有不少算法原理与斐波那契数列是一致的,例如:

  • 题目:一只青蛙一次可以跳一个台阶,也可以跳两个台阶。求解青蛙跳上n个台阶有多少中跳法

  • 分析

    • 最简单情况,如果总共只有一节台阶,只有一种解法,如果有两个台阶,有两种跳法,
    • 一般情况,n级台阶看出是n的函数,记f(n), 当 n> 2 时候,第一次条就有两种不同选择,
    • 第一种跳1级,此时后面的台阶的跳法等于f(n-1) ,那么总的跳法是 1 * f(n-1) = f(n -1)
    • 第二种跳2级,此时后面的台阶跳法等于f(n-2) ,那么总的跳法是 1*f(n-2) = f(n-2)
    • 所以n级台阶的不同跳法是 (n) = f(n-1) + f(n-2),实际上就是斐波那契数列

上一篇:数据结构与算法–查找与排序另类用法
下一篇:数据结构与算法–位运算

数据结构与算法--再谈递归与循环(斐波那契数列)相关推荐

  1. 递归和循环:斐波那契数列

    题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). n<=39 解题思路 递推公式f(n)=f(n)= 当n=0=0,当n=0 当 ...

  2. 用递归法计算斐波那契数列的第n项

     斐波纳契数列(Fibonacci Sequence)又称黄金分割数列,指的是这样一个数列:1.1.2.3.5.8.13.21.--在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1, ...

  3. python 递归方式实现斐波那契数列

    python 递归方式实现斐波那契数列 import time t1=time.time() def factorial(n):if n==1 or n==2:return 1else:return ...

  4. python装饰器模式带参数_Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列)...

    Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列) 一丶带参数的装饰器 def wrapper_out(pt): def wrapper(func): ...

  5. C语言递归算法求斐波那契,递归法求斐波那契数列(C语言版)

    斐波那契数列: 又称黄金分割数列,指的是这样一个数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... 在数学上,斐波纳契数列以如下被以递归的方法定义 ...

  6. 循环斐波那契数列_剑指offer #10 斐波那契数列

    (递归和循环)#10 斐波那契数列 一.斐波那契数列 定义: n = 0 , f(n) = 0 n = 1 , f(n) = 1 n > 1 , f(n) = f(n-1) + f(n-2) 思 ...

  7. 循环斐波那契数列_每日一课 | 斐波那契数列的第n个项

    Python程序借助两种方法来计算斐波那契数列的第n个项 (有许多方法可以计算第n个项). 描述: 第一种方法:动态编程 在这种方法中,我们计算出斐波那契数列直到n的所有项,如果我们需要计算小于n的任 ...

  8. Python之递归(含斐波那契数列和汉诺塔)

    Python递归 一.什么是递归? 递归就是在调用一个函数的过程中,直接或者间接的调用函数自身这就叫递归. 二.实例 这里讲一下递归的注意点: (1)调用一次函数就会在内存里开辟栈帧空间,在调用结束后 ...

  9. 递归与分治——斐波那契数列非递归,递归,与优化后的递归算法

    斐波那契数列: 1.1.2.3.5.8.13.21.-- 简单说,就是前两项的和是第三项的值. 1.求第N个斐波那契数的值(非递归) //斐波那契数列 int fun(int n) {int a = ...

最新文章

  1. ACM学习历程—HDU5586 Sum(动态规划)(BestCoder Round #64 (div.2) 1002)
  2. MySQL中interactive_timeout和wait_timeout的区别
  3. 【算法设计与分析】07 算法的数学基础
  4. linux编译lnx文件命令_linux命令dd
  5. mybatis parameterType
  6. 【动态规划】记录每步选择:牛客网:连续子数组的最大和(二)
  7. 检查Mysql引擎的方法
  8. 基于ssm的城市公交查询系统的设计与实现(附源码)
  9. nginx源码安装教程
  10. 微信小程序手写输入法input和textarea获取不到值
  11. 什么是多道程序设计技术,试述多道程序运行的特征。
  12. Akka Actors入门案例解析
  13. 立体匹配(Stereo Matching)
  14. 使用ArrayList集合,对其添加10个不同的元素,并使用Iterator遍历该集合
  15. Filecoin主网上线只是鸣枪开跑,完美落地需要成长的过程
  16. 后羿采集器怎么导出数据_数据采集教程_智能模式_如何设置自动导出_后羿采集器...
  17. 修改Android app图标(Android Studio)
  18. 维度建模——维度建模与数据仓库概述
  19. 通过xmail构建本地电子邮件测试环境
  20. Vue3 自定义指令:ClickOutside(点击当前区域之外的位置)

热门文章

  1. mybatis执行批量更新batch update 的方法(oracle,mysql)
  2. Sigmoid函数与逻辑回归
  3. 数学能有多美?这个动图看了完全停不下来...
  4. 网友半夜差点被沐浴露吓死,众人:原来不止我胆小....
  5. java里面什么时候环境变量_Java的环境变量什么时候需要设置?
  6. ubuntu 新增mysql用户_Ubuntu中给mysql添加新用户并分配权限
  7. 苹果手机怎么拍星空_手机拍星空,看这篇教程就够了!
  8. 七0二所与江南计算机研究所,江南大学:一所被低估的“211”大学,2个A+学科,丝毫不输985...
  9. android gdb 远程调试工具,Android下用gdb远程调试办法
  10. 在栈中压入一个字符串c语言,面试题 31:栈的压入、弹出序列