递归做法与动态规划做法的分析和比较


本文内容

一、简介

二、动态规划原理

三、递归原理

四、实验预期现象

五、Python批量生成文件

六、遇到的困难与解决办法

6.1测试数据运行时间

6.2批量生成文件

七、递归做法相关实验

7.1源代码

7.1.1递归做法求解斐波那契数列

7.1.2测试递归次数

7.2实验数据

7.2.1测试运行时间

7.2.2测试递归次数

八、动态规划做法相关实验

8.1源代码

8.1.1动态规划做法求解斐波那契数列

8.2实验数据

8.2.2测试运行时间

九、实验结果比较

十、推测递归次数

10.1指数函数推测递归次数

10.2递推公式推测递归次数

10.3指数函数与递推公式求解递归次数对比

十一、时间复杂度和空间复杂度分析

十二、总结

一、简介:

  本篇博客以“斐波那契数列”为例,采用递归做法和动态规划做法对其求解,通过测量n1(1≤n1≤99,n1∈N)个数据运行时间以及测量递归做法n2(1≤n2≤41,n2∈N)个数据的递归次数,将得到的结果以柱状图的形式表现出来,进而对递做法和动态规划做法进行分析和比较。

  首先介绍斐波那契数列,斐波那契数列的排列是:1,1,2,3,5,8,13,21,34,55,89......。依次类推下去可以发现,它后一个数等于前面两个数的和。在这个数列中的数字,就被称为斐波那契数。由此可以得出“斐波那契数列”递推关系:

                                                                   F(n)=F(n-1)+F(n-2)(n∈N*)

二、动态规划原理

A* "1+1+1+1+1+1+1+1 =?" *

A: "上面等式的值是多少"

B : *计算"8!"

A*在上面等式的左边写上"1+" *

A: "此时等式的值为多少"

B : *quickly*"9!"

A : "你怎么这么快就知道答案了'' 

A: "只要在8的基础上加1就行了"

A: "所以你不用重新计算因为你记住了第一个等式的值为8!动态规划算法也可以说是'记住求过的解来节省时间''

由上面的对话可以知道动态规划算法的核心就是记住已经解决过的子问题的解。

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

能采用动态规划求解的问题的一般要具有3个性质:

1)最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

2)无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势。

使用动态规划求解问题,最重要的就是确定动态规划三要素:

1)问题的阶段。

2)每个阶段的状态。

3)从前一个阶段转化到后一个阶段之间的递推关系。

三、递归原理

  先来分析一下递归算法的执行流程(以“斐波那契数列”为例),假如输入6,那么执行的递归树如下:

  

  上面的递归树中的每一个子节点都会执行一次,很多重复的节点被执行,例如:fib(2)被重复执行了5次。由于调用每一个函数的时候都要保留上下文,所以空间上开销也不小。这么多的子节点被重复执行,如果在执行的时候把执行过的子节点保存起来,后面要用到的时候直接查表调用的话可以节约大量的时间,这就是博客中提到的动态规划算法的优势。

四、实验预期现象

  动态规划算法的运行时间比较平稳,不随着测试数据的增大而改变。递归做法当测试数据越大时,运行时间越长,并且有很多重复计算的节点,所以预计递归算法的运行时间会呈指数增长。

五、python批量生成文件

  由于数据较多,为了简化工作、提高效率须采用批量执行的方法:

1.用记事本建一个空白的文件,后缀保存为.bat。此处以text.bat为例。

      

2.利用c语言进行编程,内容写好bat语句,并将输出结果重定向输入到步骤1存的text.bat文本中。

#include<stdio.h> main() { int i; for(i=1;i<=99;i++) { printf("ptime 文件名.exe<%d.txt>>文件名.txt\n",i); } }
View Code

3.重定向输出:将所有执行过程中需要的文件放在同一目录下,在该目录下打开控制台,输入:要执行的文件名.exe>>文本名.bat(此处>>可以根据需求写成>,>>表示追加内容,>表示覆盖原有内容)

      

4.利用python写一段程序生成99个文本文件,每个文本中存一个测试数据。

 i = 1
while (i < 100): s2 = 'C:/Users/Administrator/Desktop/a/' s = '.txt' s1 = s2+str(i) + s print(s1) f = open(s1, 'w') f.write(str(i)) f.close() i = i + 1
View Code

5.将所有执行过程中需要的文件放入同一目录下,在该目录下打开控制台,执行1过程保存的text.bat文本。以上过程即可实现批量执行。

      

六、遇到的困难与解决办法

6.1测试数据运行时间

  解决办法:在每条执行语句前加ptime(ptime是测试运行时间的exe,精确到毫秒,在这个过程中要求的时间精度比较高)。

6.2批量生成文件

  解决办法:利用python可以自动生成文本文件,代码如5-4。(生成的文件数量不同,代码需要稍作修改)

七、递归方法相关实验

7.1源代码

7.1.1递归做法求解斐波那契数列

#include<stdio.h> 
int n = 0;//n表示输入的测试数据
int m = 0;//m表示调用函数后计算的结果
int fun(int p)
{
    
        if (p == 1 || p == 2)
         {
            return 1;
          }
     else
         {
            return fun(p - 1) + fun(p - 2);
          }
    
}
int main()
{
       scanf("%d", &n);
       m = fun(n);
       printf("%d",m);
       return 0;
}
View Code

7.1.2测试递归次数

#include<stdio.h> 
int n = 0;//n表示输入的测试数据
int m = 0;//m表示调用函数后计算的结果
int i=0;
int fun(int p,int t)
{
    
    if (p == 1 || p == 2)
    {
        i++;
        return 1;
    }
    else
    {
        i++;
        return fun(p - 1,t) + fun(p - 2,t);
    }
    
}
int main()
{
    scanf("%d", &n);
    m = fun(n,i);
    printf("%d\n", i);
    return 0;
}
View Code

7.2实验数据

7.2.1测试运行时间

    测试n(1≤n≤55,n∈N)个数据运行时间实验结果灰色列为测试数据,白色列为运行时间(单位S)。

      

                            图1 递归方法求解“斐波那契数列”测运行时间结果

      柱状图表示实验结果:

      

                   图2 递归方法求解“斐波那契数列”测运行时间结果柱状图

7.2.2测试递归次数

  测试n(1≤n≤41,n∈N)个数据递归次数实验结果。

      

                     图3 递归方法求解“斐波那契数列”测递归次数结果

      柱状图表示实验结果:

      

                    图4 递归方法求解“斐波那契数列”测递归次数结果柱状图

八、动态规划相关代码

8.1源代码

8.1.1动态规划做法求解斐波那契数列

#include<stdio.h>
int main() { int a[100]; int n; int i=2; a[0]=0; a[1]=1; a[2]=1; scanf("%d",&n); if(n==1) printf("1"); else{ while(1) { a[i]=a[i-1]+a[i-2]; if(i>=n) break; else i++; } printf("%d\n", a[i]); } return 0; }
View Code

8.2实验数据

8.2.2测试运行时间

    测试n(1≤n≤97,n∈N)个数据运行时间实验结果。

        

              图5 动态规划方法求解“斐波那契数列”测运行时间结果

        柱状图表示实验结果:

        

            图6 动态规划方法求解“斐波那契数列”测运行时间结果柱状图

九、实验结果比较

       

                     图7 递归方法求解每个数据运行时间实验结果

        

                   图8 动态规划方法求解每个数据运行时间实验结果

  通过两种方法运行时间的实验结果表明,递归做法的运行时间成指数增长,动态规划做法的运行时间不随测试数据的改变而变化。两种方法对比可以看出动态规划做法降低了时间复杂度,提高效率。实验结果与实验预期现象一致。

十、推测递归次数

  目的:当输入的测试数据较大时,运行时间较长,无法一一测试,所以需要推测测试结果。

10.1指数函数推测递归次数

  1. 通过图三测试不同输入时递归的次数,可以推测出数据呈指数增长(与图四柱状图相同),设y=a^x(设x是测试数据,y是递归次数),代入图三数据求解得a=1.61。指数函数为:

                                                                                                   y=1.61^x

  2.写一段C程序批量执行利用指数函数对不同的输入求解其递归次数。C代码:

#include<stdio.h> #include<math.h>
int main() { int x; float y; scanf("%d",&x); y=pow(1.61,x); printf("%f\n",y); return 0; }
View Code

  3.  执行结果:灰色列是测试数据n,白色列是求解出的递归次数

      

                       图9 指数函数推测递归次数实验结果

        折线图表示实验结果:

        

                        图10 指数函数推测递归次数实验结果折线图

    结论:通过这种方法,我们测试数据较大时,可以推测递归需要的次数。

    缺点:与图三递归次数结果相比,可以看出指数函数求解得到的数据有误差,不精确。

10.2递推公式推测递归次数

  1.根据递归方法求解“斐波那契数列”代码可以推导出求递归次数的递推公式:

                                f(p)=f(p-1)+f(p-2)+1

        设f(p)是递归次数。

  2.实验结果

    

                   图11 递推公式求解递归次数实验结果

    折线图表示实验结果:

    

                  图12 递推公式求解递归次数实验结果折线图

  结论:与图七相比可以得出,通过递推公式求解出的数据与实验测得的数据相同,由此可以证明通过递推公式推测递归次数误差为零,可以更加精确的推测当输入的测试数据较大时需要递归的次数。

10.3指数函数与递推公式求解递归次数对比

    

                           图13 指数函数与递推公式求解递归次数对比图

  结论:通过对比图可以得出,两种方法的大致趋势相同,呈指数增长。不同的是指数函数求解问题精度不高,有误差。而递推公式求解此问题精确度高,误差为零。

十一、时间复杂度和空间复杂度分析

  算法复杂度分为时间复杂度和空间复杂度。其作用:时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度。动态规划算法不随测试数据的增大而增大,所以动态规划算法的时间复杂度是O(1),空间复杂度是O(1)。

  如图所示,递归算法的时间复杂度为(二叉树的节点个数):O()=2^h-1=2^n(h为二叉树的高度),空间复杂度为树的高度:h即O(n)。

   

十二、总结

  本篇博客以斐波那契数列为例,重点对动态规划算法和递归算法做分析比较,通过一系列的实验数据表明,动态规划算法与递归算法相比降低的时间复杂度和空间复杂度,从而提高工作效率,节省时间。本篇博客实验结果与预期实验结果一致。最后感谢老师指点,感谢高远博师兄耐心讲解相关知识,感谢代秋彤师姐每日批注,感谢李淼洋学长指出我的错误。感谢!

  


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM