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

Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 [email protected]

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


目录

递归和迭代

什么是递归

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

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

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

由于递归引起一系列的函数调用,并且有可能会有一系列的重复计算,递归算法的执行效率相对较低。

递归的优劣
优点

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

缺点

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

什么是迭代法

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

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

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

递归和迭代的区别

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

递归就是自己调用自己,自己包含自己。
迭代是将输出做为输入,再次进行处理。

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

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

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

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

迭代是循环结构,例如 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

猜你喜欢

转载自www.cnblogs.com/baiqiantao/p/10093880.html
今日推荐