本文內容
一、簡介
二、動態規划原理
三、遞歸原理
四、實驗預期現象
五、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); } }
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
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; }
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; }
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; }
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; }
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)。
十二、總結
本篇博客以斐波那契數列為例,重點對動態規划算法和遞歸算法做分析比較,通過一系列的實驗數據表明,動態規划算法與遞歸算法相比降低的時間復雜度和空間復雜度,從而提高工作效率,節省時間。本篇博客實驗結果與預期實驗結果一致。最后感謝老師指點,感謝高遠博師兄耐心講解相關知識,感謝代秋彤師姐每日批注,感謝李淼洋學長指出我的錯誤。感謝!