日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

贪心、递归、递推以及动态规划算法的分析与对比

發布時間:2025/6/15 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 贪心、递归、递推以及动态规划算法的分析与对比 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

PS:

??頭一次規規矩矩的按照論文的格式寫文章,呵呵。雖然是小兒科的不能再小兒科的東西了。。不過。。也忽悠了6000多字~~嘿嘿。。肯定寫的不好,第一次嘛。。所以。。接受大家一切批評哈!。。。文章NOI專刊的編輯正在審。。還不知道能不能發。。嘿嘿。。期待啊。。先發我BLOG里。。大家批評下。。

?

?

?

貪心、遞歸、遞推以及動態規劃算法的分析與對比

王喆 天津市第五十五中學

【關鍵字】

動態規劃 貪心 遞歸 遞推 分析 說明 NOIP

【摘要】

本文通過典型例題分析出貪心算法、遞歸算法、遞推算法以及動態規劃算法的區別和相似處。以及對這幾種算法的思考方法,編程方法以及“遞歸節省時間浪費空間,遞推滾動節省空間浪費時間”的解釋和舉例論證。

【正文】

一、各算法的介紹

1.1貪心算法

貪心的思想可以用一句話來歸納,“每步取優”。很好理解,假設你的程序要走I=1~N共N步,那么保證你的第I步走出的是當前這一步的最優值。這樣的解題方法叫做貪心算法。可見貪心算法并不是一個全面的枚舉方法而是若干結果中的一種,僅僅一種而已。但這種算法是不是最優解它就不能完全保證了。

1.2遞歸算法

一般每個可以使用遞歸算法求解的題目都可以寫出一個遞歸函數。假設這個函數是F(),那么F()應該為你可以表示你的解。而題目的主要問題就是把一個大問題轉換為若干個性質相同的子問題。注意是性質相同,因為只有性質相同我們才能使用同一個函數來表示。而求解的過程是從最后一步,當然每一步都會用到比自己要小的子問題的值,那么要調用程序來求出這些子問題的解,一步步返回最后得到最后的問題的解。也可以理解為求解過程是“反向”的。因為變量會是逐漸變小的。

1.3遞推算法

與遞歸算法一樣,必定會寫出一個轉移方程,而每個可以用遞歸方法解決的問題都可以用遞推方法解決。我們要做的依然是把大問題轉變為性質相同的子問題的過程。而求解過程與遞歸方法正好相反,是從最小規模的子問題開始求解,最后求到最大規模的解。與遞歸不一樣的是,遞歸可以只求我們所需要的子問題的解,而遞推算法在每一步計算求解的過程中并不知道下一步需要用什么樣的子問題的值,于是程序必須把所有的可能性都求出來。造成了很多的計算浪費。但遞推算法有一個遞歸算法永遠做不到的優勢就是“滾動性”。當遞推算法求解完第一行的子問題的時候進行第二行的處理,第二行會用到上一行的子問題值。當處理第三行的時候第一行的值就沒有用了,所以我們可以把單數行的值都存到第一各數組里,雙數行的值都存到第二個數組里。這樣可以就可以實現滾動,原來原本要開[1..n,1..n]大小的數組現在就可以只開[1..n,1..2]大小的數組了,把空間復雜度從O(N2)的復雜度變為O(2N)的復雜度。這就是所謂的“遞推省空間費時間,遞歸省時間費空間”的道理。

1.4動態規劃算法

動態規劃算法,動態規劃算法可以理解為是遞歸算法的一個延伸。因為單純的遞歸算法是會出現很多子問題的重疊的,這樣還是會造成同一問題的重復運算。所以我們要找一個辦法來避免重復的運算。于是就出現了動態規劃。簡單地說,動態規劃依然是把一個大問題分為若干性質相同的子問題,而這些子問題里面會有若干的重疊。(下面的例題舉例)。為了當出現子問題重疊的時候不重復運算。我們就需要把所有的已經求出的子問題都存下來,判斷這個子問題是否已經算過,算過了就不要再算了。如果沒算過就算一遍下次在遇到這個子問題就可以不算了。因此我們必須開出一個大小為[1..N,1..N]的數組來存儲,又因為每次都有可能會遇到不同的行的子問題,所以我們必須把數組全部留住,所以就不能實現遞推算法的“滾動性”。但動態規劃算法可以節省大量的時間。假設所有的子問題都不重疊它的時間復雜度會和遞歸一樣。而如果優有大量的子問題重疊,那么會發現時間復雜度會有明顯的降低。可以提高運算效率,縮短運算時間。

二、???????????用樹狀圖直觀體現動態規劃的子問題分配

?

(圖一)

?

???????從上面的樹狀圖我們可以很清楚的看到,每一個大的問題是會被當作樹根劃分為若干個子問題的,每個子問題又會作為一個子樹的樹根被劃分為若干個子問題。只有找到最后一層的問題時才會停止,我們把這樣的最后一層稱為“邊界”。一般都是當變量為0或1或什么值時返回一個固定的值。使用遞歸就要加上一句判斷,如果使用遞推的話就要單獨初始化,單獨賦值。

下面就對典型的例題來分析貪心法的不足與動態規劃子對于重疊子問題的計算。

三、???????????典型例題分析

采藥

(medic.pas/c/cpp)
【問題描述】
辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞里對他說:“孩子,這個山洞里有一些不同的草藥,采每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間里,你可以采到一些草藥。如果你是一個聰明的孩子,你應該可以讓采到的草藥的總價值最大。”?
如果你是辰辰,你能完成這個任務嗎?
【輸入文件】
輸入文件medic.in的第一行有兩個整數T(1 <= T <= 1000)和M(1 <= M <= 100),用一個空格隔開,T代表總共能夠用來采藥的時間,M代表山洞里的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100)的整數,分別表示采摘某株草藥的時間和這株草藥的價值。
【輸出文件】
輸出文件medic.out包括一行,這一行只包含一個整數,表示在規定的時間內,可以采到的草藥的最大總價值。
【樣例輸入】
70 3
71 100
69 1
1 2
【樣例輸出】
3
【數據規模】
對于30%的數據,M <= 10;
對于全部的數據,M <= 100。

3、1對貪心算法的反例

首先我們用貪心的思想來規劃這道題的話,我們會按照順序每次取優解假設我們現在有這樣的一組數據:

100 5

50 100

1 50

2 80

1 50

96 50

無論我們是按照價值的大小排序還是按照價值于時間的比值排序我們都得不到最優解。所以,貪心法并不適用于這道題。于是貪心方法被直接PASS。所以我們試著使用遞歸和動態規劃方法來解決。

3.2對遞歸遞推算法的數學模型介紹

根據題目我門可以把總的大問題寫成一個函數F(T,M)。我們可以把這個函數這樣定義,F(I,J)表示擁有T時間,有J個未采藥材所可以獲得的最大價值。

如果把函數像上面的樣子定義的話,很容易我們就可以把一個大問題分解成若干個性質相同的子問題,從而我們就可以用地歸和動態規劃來解決。這樣呢,我們就可以寫出一個遞歸轉移方程,來解釋F(I,J)

解釋一下,對于每一個藥材都有采它所需要的時間,當這個時間比整個子問題的J也就是所剩余的時間要大的時候我們就沒有時間去采這個藥材,無論整個藥材是多么的有價值。所以,在方程中規定,只要是a[i-1,j]>j則我們就硬性結束,直接返回F[i-1,j]的值。當這個采這個藥材所需要的時間小于剩余的時間時,我們就出現了兩種解決辦法。第一采這個藥,第二不采。而我們要做的是在這兩個值里面挑一個最大的來作為這個子問題的解。而由于這道題由于藥的順序和不影響最后的解,所以我們可以忽略最后的順序。我們可以從第M個開始一直到第1個。

3.3?介紹單純遞歸方法的函數實現

根據上面的轉移方程我們很容易的就可以寫出遞歸函數

???????示例函數:

???????Function try1(i,j:Integer):Longint;(*定義函數*)

Var(*共需要兩個局部變量*)

????????????max1,max2:Longint;

Begin

????????????If (i=0) Or (j=0) Then Begin(*邊界判斷*)

??????????????????try1:=0;(*滿足邊界則返回0,直接退出*)

???????????????????Exit;

????????????End;

????????????If i>=a[j].t Then max1:=try1(i-a[j].t,j-1)+a[j].v Else max1:=0;(*判斷時間是否足夠,足夠則求出采這個藥之后的最大價值*)

????????????max2:=try1(i,j-1);(*直接求不采的最大價值,(多說下,這里J-1一定不會減到小于0的情況因為前面有邊界條件限制了)*)

????????????If max1>max2 Then try1:=max1 Else try1:=max2;(*判斷出個最大值然后返回*)

End;

3.4?解釋單純遞歸方法的計算浪費和效率低的原因

???????根據上面的轉移方程我們假設樣例為

100 5

1 50

2 80

1 50

50 100

96 50

用樹狀圖來表示就會很清晰

很容易看出來在這棵樹的第四層就出現了重復情況。子問題出現了重疊,如果我們這個樣例的話這棵樹只有7層。可想而知當一個數據M=100的時候。會出現多少同樣的情況。如果都算一遍,我們是不是浪費了很多很多時間呢?

???????所以我們才要想辦法避免這種情況的出現,否則很多的時間都會浪費在“無用功”上,使得我們程序的效率會降低很多。很好的一個解決辦法就是把每次所求出來的子問題的值都用一個數組存下來,這樣我們可以判斷這個子問題是否處理過,如果處理過了我們就不要再去處理,如果沒處理我們就處理它。這就是動態規劃的思想。所以我們必須要開出一個大小為[0..100,0..1000]的數組來存儲這些子問題。

3.5??給出動態規劃方法解決的樣例程序程序

樣例程序解決:

Program medic;

(**************************medic.pas **************************)

(********WangZhe,55Th?senior high school of Tianjin********)

(**************************2008-8-21***************************)

Type (*定義數組類型,.t表示時間,.v表示價值*)

???????????????????re=record

???????????????????????t:Integer;

???????????????????????v:integer;

????????????????????End;

Const (*關聯輸入輸出文件*)

????????????Infile='medic.in';

outfile='medic.out';

Var (*變量定義*)

????????????m,t,i:Integer;

??????????data,s:Array[0..100,0..1000] Of integer;(*動態規劃存儲數組*)

??????????a:Array[0..100] Of re;(*輸入數據數組*)

Procedure try1(i,j:Integer);(*動態規劃主過程*)

Var

max,max1,l:integer;(*所需要的兩個局部變量*)

Begin

??If (i=0) Or (j=0) Then Begin(*判斷邊界*)

????Data[i,j]:=0;(*處理邊界*)

????Exit;

??End;

??If data[i-1,j]=-1 Then Try1(i-1,j);(*很關鍵的一步判斷問題是否已經求過,其中-1僅僅是一個空標記,因為這道題不可能出現負值所以我們就定義-1為標記,這個標記不一定要用多少只要是解題中不可能出現的標記就好*)

??Max:=Data[i-1,j];(*求出不采這個藥的最大價值*)

?

??If j>=a[i].t Then Begin(*判斷時間是否足夠,足夠則求出采這藥的最優值*)

????If Data[i-1,j-a[i].t]=-1 Then try1(i-1,j-a[I].t);

Max1:=data[i-1,j-a[i].t]+a[i].v;

Else

??Max1:=0;(*不夠就付個0就可以了*)

End;

????If max< max1 Then max:=max1;(*判斷出一個最大值來*)

??data[i,j]:=max;(*把最大值付給DATA*)

??End;

Begin(*主程序*)

??Assign(input,infile);(*文件關聯*)

??Assign(output,outfile);

??reset(input);

??rewrite(output);

??fillchar(data,sizeof(data),$ff);(*數組清零!這步很重要!!多打幾個嘆號!這個如果不標記那么是求不出值的。*)

??readln(t,m);(*讀入兩個控制值*)

??For i:=1 To m Do readln(a[i].t,a[i].v);(*讀入數據*)

??try1(M,t);(*動態規劃求解*)

??writeln(data[m,t]);(*輸出最優解*)

??close(input);(*關閉文件*)

??close(output)

End.

3.6遞推方法樣例程序的介紹和注釋

從這個程序和上面給出的遞歸的程序對比一下就看的出來,動態規劃的程序就是加入了一個存儲和判斷。其他的思想上和數學模型上都是一樣的。

???下面給出一個遞推滾動的程序大家對比看看。(上面注釋過的我就不注釋了,只注釋出區別)

Program medic;

Type re=record

?????t:Integer;

?????v:integer;

End;

Const infile='medic.in';

??????outfile='medic2.out';

Var m,t,i,j,n,k,max1:Integer;

????data:Array[0..2,0..1000] Of integer;(*因為滾動,開數組只需要開2個就行了*)

????a:Array[0..100] Of re;

Begin

??Assign(input,infile);

??Assign(output,outfile);

??reset(input);

??rewrite(output);

??fillchar(data,sizeof(data),$ff);(*可以不清零,不過保險起見還是清*)

??readln(t,m);

??For i:=1 To m Do readln(a[i].t,a[i].v);

??For i:=0 To t Do(*處理邊界,也可以叫做“預處理”*)

????If a[1].t<=i Then Data[1,i]:=a[1].v Else data[1,i]:=0;

??For i:=2 To m Do(*遞推用循環就可以實現,不用再寫過程了*)

????For j:=0 To T Do Begin

??????If i mod 2 =1 then n:=1 Else n:=2;(*控制滾動,這一步很重要,單數用1數組,雙數用2數組*)

??????If n=1 Then k:=2 Else k:=1;(*還是滾動控制*)

??????Data[n,j]:=Data[k,j];

??????If (j>=a[i].t) Then Begin(*這個地方的思想是一樣的,判斷時間是否足夠*)

????????max1:=Data[k,j-a[i].t]+a[i].v;

????????If max1>data[n,j] Then Data[n,j]:=max1;(*求最大值保存*)

??????End;

??End;

??writeln(data[n,t]);

??close(input);

??close(output)

End.

3.7動態規劃遞歸和遞推方法的區別與聯系

從上面的程序我們可以看的出來數學模型上,遞歸遞推沒有什么區別,而遞推可以實現滾動,我們可以節省空間復雜度,使空間復雜度降低了50倍。而時間上勢必會多浪費些。

【結語】

???????通過作者的介紹,我想讀者會很容易的看出來這集中方法的區別和聯系。從作者自己的經驗來講,我覺得處理一道這樣的題。我們首先要想想是否可以適用貪心算法來解決,由于貪心算法是“一條鏈”式的處理算法,所以無論空間復雜度還是時間復雜度都是很低的。拿到一道題我們可以先試著用貪心算法試驗一下是否可以用別的反例來推翻貪心算法。判斷題目不可以用貪心算法來解決了以后我們就要靜下心來,再仔細看一遍題目,總結遞歸轉移方程。因為總結轉移方程是解題的最重要的一步。如果你的編程技巧和熟練度足夠的話,只要你的轉移方程寫對,剩下來的就只剩“抄”程序了,會很快完成題目的。作者也是每年都要參加NOIP的選手,所以我很希望能與各位讀者有交流的機會。貼出我的博客,歡迎大家來交流!http://skysummerice.programfan.com

【特別鳴謝】

感謝天津FNOI的教練藤偉老師給予作者的指導與啟發

總結

以上是生活随笔為你收集整理的贪心、递归、递推以及动态规划算法的分析与对比的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。