關於遞迴做法與動態規劃做法的分析和比較
本文內容
一、主要內容
二、動態規劃原理
三、遞迴原理
四、實驗預期現象
五、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指數函式與遞推公式求解遞迴次數對比
十一、大O分析
十二、總結
一、主要內容:
本篇部落格以“斐波那契數列”為例,採用遞迴做法和動態規劃做法對其求解,通過測量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)從前一個階段轉化到後一個階段之間的遞推關係。
(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是測試執行時間的指令碼,精確到毫秒,在這個過程中要求的時間精度比較高)。
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)。
圖一:遞迴方法求解“斐波那契數列”測執行時間結果
柱狀圖表示實驗結果:
圖二:遞迴方法求解“斐波那契數列”測執行時間結果柱狀圖
7.2.2測試遞迴次數
測試n(1≤n≤41,n∈N)個數據遞迴次數實驗結果。
圖三:遞迴方法求解“斐波那契數列”測遞迴次數結果
柱狀圖表示實驗結果:
圖四:遞迴方法求解“斐波那契數列”測遞迴次數結果柱狀圖
八、動態規劃相關程式碼
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)個數據執行時間實驗結果。
圖五:動態規劃方法求解“斐波那契數列”測執行時間結果
柱狀圖表示實驗結果:
圖六:動態規劃方法求解“斐波那契數列”測執行時間結果柱狀圖
九、實驗結果比較
圖七:遞迴方法求解每個資料執行時間實驗結果
圖八:動態規劃方法求解每個資料執行時間實驗結果
通過兩種方法執行時間的實驗結果表明,遞迴做法的執行時間成指數增長,動態規劃做法的執行時間不隨測試資料的改變而變化。兩種方法對比可以看出動態規劃做法降低了時間複雜度,提高效率。實驗結果與實驗預期現象一致。
十、推測遞迴次數
目的:當輸入的測試資料較大時,執行時間較長,無法一一測試,所以需要推測測試結果。
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,白色列是求解出的遞迴次數。
圖九:指數函式推測遞迴次數實驗結果
折線圖表示實驗結果:
圖十:指數函式推測遞迴次數實驗結果折線圖
結論:通過這種方法,我們測試資料較大時,可以推測遞迴需要的次數。
缺點:與圖三遞迴次數結果相比,可以看出指數函式求解得到的資料有誤差,不精確。
10.2遞推公式推測遞迴次數
1.根據遞迴方法求解“斐波那契數列”程式碼可以推匯出求遞迴次數的遞推公式:
f(p)=f(p-1)+f(p-2)+1
設f(p)是遞迴次數。
2.實驗結果
圖十一:遞推公式求解遞迴次數實驗結果
折線圖表示實驗結果:
圖十二:遞推公式求解遞迴次數實驗結果折線圖
結論:與圖七相比可以得出,通過遞推公式求解出的資料與實驗測得的資料相同,由此可以證明通過遞推公式推測遞迴次數誤差為零,可以更加精確的推測當輸入的測試資料較大時需要遞迴的次數。
10.3指數函式與遞推公式求解遞迴次數對比
圖十三:指數函式與遞推公式求解遞迴次數對比圖
結論:通過對比圖可以得出,兩種方法的大致趨勢相同,呈指數增長。不同的是指數函式求解問題精度不高,有誤差。而遞推公式求解此問題精確度高,誤差為零。
十一、大O分析
演算法複雜度分為時間複雜度和空間複雜度。其作用:時間複雜度是指執行演算法所需要的計算工作量;而空間複雜度是指執行這個演算法所需要的記憶體空間。(演算法的複雜性體現在執行該演算法時的計算機所需資源的多少上,計算機資源最重要的是時間和空間(即暫存器)資源,因此複雜度分為時間和空間複雜度。動態規劃演算法不隨測試資料的增大而增大,所以動態規劃演算法的時間複雜度是O(1),空間複雜度是O(1)。
如圖所示,遞迴演算法的時間複雜度為(二叉樹的節點個數):O()=2^h-1=2^n(h為二叉樹的高度),空間複雜度為樹的高度:h即O(n)。
十二、總結
本篇部落格以斐波那契數列為例,重點對動態規劃演算法和遞迴演算法做分析比較,通過一系列的實驗資料表明,動態規劃演算法與遞迴演算法相比降低的時間複雜度和空間複雜度,從而提高工作效率,節省時間。本篇部落格實驗結果與預期實驗結果一致。