算法 递归 迭代 动态规划 斐波那契数列 MD

Markdown版本笔记 个人GitHub首页 个人博客 个人微信 个人邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

算法 递归 迭代 动态规划 斐波那契数列 MDgit


目录

递归和迭代

什么是递归

递归的基本概念:程序调用自身的编程技巧称为递归github

一个函数在其定义中直接或间接调用自身的一种方法,它一般把一个大型的复杂的问题转化为一个与原问题类似的规模较小的问题来解决,能够极大的减小代码量。算法

递归的能力在于用有限的语句来定义对象的无限集合.编程

因为递归引发一系列的函数调用,而且有可能会有一系列的重复计算,递归算法的执行效率相对较低。数组

递归的优劣
优势微信

  • 大问题化为小问题,能够极大的减小代码量
  • 用有限的语句来定义对象的无限集合
  • 代码更简洁清晰,可读性更好

缺点函数

  • 递归容易产生"栈溢出"错误(stack overflow)。由于须要同时保存成百上千个调用记录,因此递归很是耗费内存。
  • 递归可能存在冗余计算。好比最典型斐波那契数列,计算第6个须要计算第4个和第5个;而计算第5个还须要计算第4个,所处会重复。迭代在这方面有绝对优点。

什么是迭代法

迭代法也称展转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法,即一次性解决问题。学习

迭代法是一类利用递推公式循环算法经过构造序列来求问题近似解的方法。优化

迭代算法是用计算机解决问题的一种基本方法,它利用计算机运算速度快、适合作重复性操做的特色,让计算机对一组指令(或必定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值,迭代法又分为精确迭代和近似迭代。比较典型的迭代法如二分法牛顿迭代法属于近似迭代法。spa

递归和迭代的区别

知乎 上有不少人举了很是生动的例子来讲明他们的区别,下面摘录一些比较经典的描述。

递归就是本身调用本身,本身包含本身。
迭代是将输出作为输入,再次进行处理。

递归过程当中, 问题的规模在缩小,这样最终获得问题的解;
迭代是一种由远变近的逼近,问题的规模不见得缩小了,可是慢慢在调整接近答案。

递归——《盗梦空间》,不断下潜至底层并最终解决
迭代——《明日边缘》,不断回到同一个场景并优化解决

递归是一个树结构,每一个分支都探究到最远,发现没法继续的时候往回走;
迭代是一个环结构,每次迭代都是一个圈,不会拉掉其中的某一步,而后不断循环;

迭代是更新变量的旧值
递归是在内部调用自身

迭代是循环结构,例如 for while 循环
递归是选择结构,例如 if else 调用本身

在数学上,递归强调的是,新的值与前面计算的好几个值有关系 F{n} =F{n-1} +F{n-2}
而迭代通常是只是 x{n+1} 与 x{n} 之间进行计算

迭代是逐渐逼近,用新值覆盖旧值,直到知足条件后结束,不保存中间值,空间利用率高。
递归是将一个问题分解为若干相对小一点的问题,遇到递归出口再原路返回,所以必须保存相关的中间值,这些中间值压入栈保存,问题规模较大时会占用大量内存。

动态规划

动态规划 Dynamic Programming,简称DP。经过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

动态规划程序设计是对解最优化问题的一种途径、一种方法,而不是一种特殊算法。不像搜索或数值计算那样,具备一个标准的数学表达式和明确清晰的解题方法。

动态规划程序设计每每是针对一种最优化问题,因为各类问题的性质不一样,肯定最优解的条件也互不相同,于是动态规划的设计方法对不一样的问题,有各具特点的解题方法,而不存在一种万能的动态规划算法,能够解决各种最优化问题。所以读者在学习时,除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,以丰富的想象力去创建模型,用创造性的技巧去求解。咱们也能够经过对若干有表明性的问题的动态规划算法进行分析、讨论,逐渐学会并掌握这一设计方法。

基本思想

动态规划算法一般用于求解具备某种最优性质的问题。在这类问题中,可能会有许多可行解。每个解都对应于一个值,咱们但愿找到具备最优值的解。

动态规划算法与分治法相似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,而后从这些子问题的解获得原问题的解。

与分治法不一样的是,适合于用动态规划求解的问题,经分解获得子问题每每不是互相独立的。若用分治法来解这类问题,则分解获得的子问题数目太多,有些子问题被重复计算了不少次。若是咱们可以保存已解决的子问题的答案,而在须要时再找出已求得的答案,这样就能够避免大量的重复计算,节省时间。咱们能够用一个表来记录全部已解的子问题的答案。无论该子问题之后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。

具体的动态规划算法多种多样,但它们具备相同的填表格式。

关键字:分解成若干个子问题,保存子问题的解,从子问题的解获得原问题的解

问题特征
动态规划经常适用于有最优子结构和重叠子问题性质的问题:

  • 最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具备最优子结构性质。
  • 重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不老是新问题,有些子问题被反复计算屡次。动态规划算法正是利用了这种子问题的重叠性质,对每个子问题只解一次,然后将其解保存在一个表格中,在之后尽量经过查表以利用这些子问题的解。

适用条件

适用动态规划的问题必须知足最优化原理无后效性

最优化原理(最优子结构性质)
最优化原理可这样阐述:一个最优化策略具备这样的性质,不论过去状态和决策如何,对前面的决策所造成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略老是最优的。一个问题知足最优化原理又称其具备最优子结构性质。

我的理解:例如求最短路径问题,从起点到终点的最短路径,必定也是这条路径上任意一点到终点的最短路径。

无后效性
将各阶段按照必定的次序排列好以后,对于某个给定的阶段状态,它之前各阶段的状态没法直接影响它将来的决策,而只能经过当前的这个状态。换句话说,每一个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

我的理解:例如求最短路径问题,前 N 步的路径(所谓的之前阶段的状态)并不会对后续最优路径的选择产生影响,惟一对后续最优路径的选择产生影响的是当前所处的位置(所谓的状态)

子问题的重叠性
动态规划将原来具备指数级时间复杂度的搜索算法改进成了具备多项式时间复杂度(包括O(lgn)、O(n)、O(n^2)、O(n^3)等)的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程当中,不得不存储产生过程当中的各类状态,因此它的空间复杂度要大于其它的算法。

斐波那契数列

1,1,2,3,5,8,13,21,34,55

耗时时间比较:

n 递归法 迭代法 动态规划
20 0-2 所有不超过 1 所有不超过 1
25 1-4 0 0
30 5-8 0 0
35 50 0 0
40 489 0 0
41 803 0 0
42 1323 0 0
43 2025 0 0
44 3293 0 0
45 5309 0 0

递归法实现

public static int recurFib(int n) {
    if (n < 2) {
        return n;
    } else {
        return recurFib(n - 1) + recurFib(n - 2);
    }
}

迭代法实现

方式1:

public static int iterFib(int n) {
    if (n < 2) {
        return n;
    } else {
        int result = 0, a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            result = a + b; //每次都是最近的两个值的和
            a = b;// 把最旧的值替换为第二旧的值
            b = result; //把第二旧的值替换为最新的值
        }
        return result;
    }
}

方式2:

public static int iterFib(int n) {
    if (n < 2) {
        return n;
    } else {
        int result = 0, a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            result = a + b; //每次都是最近的两个值的和
            if (a < b) a = result; //较小的值是更旧的值,咱们把最新的值替换为更旧的值,另外一个值保持不变
            else b = result;
        }
        return result;
    }
}

动态规划实现

public static int dynFib(int n) {
    int[] val = new int[n + 1];
    if (n < 2) {
        return n;
    } else {
        val[1] = 1;
        for (int i = 2; i <= n; i++) {
            val[i] = val[i - 1] + val[i - 2];
        }
        return val[n];
    }
}

在使用动态规划实现时,咱们用数组保留中间值,这些中间值便是动态规划定义中的小问题

但须要注意的是,用动态规划解决斐波那契数列时能够不用数组,由于在计算某个位置上的数时只须要用到前两位的值,因此咱们只须要动态的保留前两位的值便可。这样子的动态规划的实现就和迭代是同样的了,但在其余问题上多是不同的。

2018-12-9

相关文章
相关标签/搜索