日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

PS:

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

?

?

?

貪心、遞歸、遞推以及動態(tài)規(guī)劃算法的分析與對比

王喆 天津市第五十五中學(xué)

【關(guān)鍵字】

動態(tài)規(guī)劃 貪心 遞歸 遞推 分析 說明 NOIP

【摘要】

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

【正文】

一、各算法的介紹

1.1貪心算法

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

1.2遞歸算法

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

1.3遞推算法

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

1.4動態(tài)規(guī)劃算法

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

二、???????????用樹狀圖直觀體現(xiàn)動態(tài)規(guī)劃的子問題分配

?

(圖一)

?

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

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

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

采藥

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

3、1對貪心算法的反例

首先我們用貪心的思想來規(guī)劃這道題的話,我們會按照順序每次取優(yōu)解假設(shè)我們現(xiàn)在有這樣的一組數(shù)據(jù):

100 5

50 100

1 50

2 80

1 50

96 50

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

3.2對遞歸遞推算法的數(shù)學(xué)模型介紹

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

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

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

3.3?介紹單純遞歸方法的函數(shù)實現(xiàn)

根據(jù)上面的轉(zhuǎn)移方程我們很容易的就可以寫出遞歸函數(shù)

???????示例函數(shù):

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

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?解釋單純遞歸方法的計算浪費和效率低的原因

???????根據(jù)上面的轉(zhuǎn)移方程我們假設(shè)樣例為

100 5

1 50

2 80

1 50

50 100

96 50

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

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

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

3.5??給出動態(tài)規(guī)劃方法解決的樣例程序程序

樣例程序解決:

Program medic;

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

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

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

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

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

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

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

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

Const (*關(guān)聯(lián)輸入輸出文件*)

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

outfile='medic.out';

Var (*變量定義*)

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

??????????data,s:Array[0..100,0..1000] Of integer;(*動態(tài)規(guī)劃存儲數(shù)組*)

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

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

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);(*很關(guān)鍵的一步判斷問題是否已經(jīng)求過,其中-1僅僅是一個空標(biāo)記,因為這道題不可能出現(xiàn)負(fù)值所以我們就定義-1為標(biāo)記,這個標(biāo)記不一定要用多少只要是解題中不可能出現(xiàn)的標(biāo)記就好*)

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

?

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

????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);(*文件關(guān)聯(lián)*)

??Assign(output,outfile);

??reset(input);

??rewrite(output);

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

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

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

??try1(M,t);(*動態(tài)規(guī)劃求解*)

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

??close(input);(*關(guān)閉文件*)

??close(output)

End.

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

從這個程序和上面給出的遞歸的程序?qū)Ρ纫幌戮涂吹某鰜?#xff0c;動態(tài)規(guī)劃的程序就是加入了一個存儲和判斷。其他的思想上和數(shù)學(xué)模型上都是一樣的。

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

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;(*因為滾動,開數(shù)組只需要開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(*處理邊界,也可以叫做“預(yù)處理”*)

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

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

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

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

??????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動態(tài)規(guī)劃遞歸和遞推方法的區(qū)別與聯(lián)系

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

【結(jié)語】

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

【特別鳴謝】

感謝天津FNOI的教練藤偉老師給予作者的指導(dǎo)與啟發(fā)

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。