C語言貪心演算法
阿新 • • 發佈:2019-02-01
貪心演算法
所謂貪心演算法是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的區域性最優解。
貪心演算法不是對所有問題都能得到整體最優解,但對範圍相當廣泛的許多問題他能產生整體最優解或者是整體最優解的近似解。
貪心演算法的基本思路如下:
1.建立數學模型來描述問題。
2.把求解的問題分成若干個子問題。
3.對每一子問題求解,得到子問題的區域性最優解。
4.把子問題的解區域性最優解合成原來解問題的一個解。
實現該演算法的過程:
從問題的某一初始解出發;
while 能朝給定總目標前進一步 do
求出可行解的一個解元素;
由所有解元素組合成問題的一個可行解;
二、例題分析
1、[揹包問題]有一個揹包,揹包容量是M=150。有7個物品,物品可以分割成任意大小。
要求儘可能讓裝入揹包中的物品總價值最大,但不能超過總容量。
物品 A B C D E F G
重量 35 30 60 50 40 10 25
價值 10 40 30 50 35 40 30
分析:
目標函式: ∑pi最大
約束條件是裝入的物品總重量不超過揹包容量:∑wi<=M( M=150)
(1)根據貪心的策略,每次挑選價值最大的物品裝入揹包,得到的結果是否最優?
(2)每次挑選所佔重量最小的物品裝入是否能得到最優解?
(3)每次選取單位重量價值最大的物品,成為解本題的策略。 ?
值得注意的是,貪心演算法並不是完全不可以使用,貪心策略一旦經過證明成立後,它就是一種高效的演算法。
貪心演算法還是很常見的演算法之一,這是由於它簡單易行,構造貪心策略不是很困難。
可惜的是,它需要證明後才能真正運用到題目的演算法中。
一般來說,貪心演算法的證明圍繞著:整個問題的最優解一定由在貪心策略中存在的子問題的最優解得來的。
對於例題中的3種貪心策略,都是無法成立(無法被證明)的,解釋如下:
(1)貪心策略:選取價值最大者。反例:
W=30
物品:A B C
重量:28 12 12
價值:30 20 20
根據策略,首先選取物品A,接下來就無法再選取了,可是,選取B、C則更好。
(2)貪心策略:選取重量最小。它的反例與第一種策略的反例差不多。
(3)貪心策略:選取單位重量價值最大的物品。反例:
W=30
物品:A B C
重量:28 20 10
價值:28 20 10
根據策略,三種物品單位重量價值一樣,程式無法依據現有策略作出判斷,如果選擇A,則答案錯誤。
所以需要說明的是,貪心演算法可以與隨機化演算法一起使用,具體的例子就不再多舉了。(因為這一類演算法普及性不高,而且技術含量是非常高的,需要通過一些反例確定隨機的物件是什麼,隨機程度如何,但也是不能保證完全正確,只能是極大的機率正確)
貪心演算法
例題:畜棚修理(1999年USACO春季公開賽試題)
有一長列畜棚,其中一些被木板覆著。你可以使用總共N個木板,這些木板可以覆蓋任意連續的畜棚。覆蓋所有必要的畜棚,使得所用的木板儘可能的少。
貪心演算法很快,一般的為O(n)到O(n^2)而且需要很少的額外空間。不幸地是,它一般是不正確的。但是,當它正確,它就會遠快於其它演算法。
演算法
在貪心演算法背後所隱含的基本演算法,是從小規模問題中生成大規模問題。這不像其他演算法,在貪心演算法的進行過程中,它只關心它發現的最好的策略。因此,對於例題,為了生成N=5時的策略,它先尋找N=4時的最優策略,然後以此生成。此時,對於其它N=4時的策略,不予考慮。
問題
貪心時,有兩個基礎的問題需要考慮。
怎麼生成?
怎樣從小規模策略中生成大規模策略?一般地,有這樣一個運作過程。對於例題,由四塊木板生成五塊木板的最顯然的方法,就是將其中一塊木板中的一部分拿掉,這樣一塊木板變成了兩塊。而你要做的,是在那些不需要被木板覆蓋的畜棚區域中,選出最長的片段(這樣,就使得所用的木板最少了)。
拿掉被覆蓋畜棚的木板中的一個片段,使得它變成兩段。一段在該片段之前,一段在該片段之後。
它有效嗎?
對於程式設計師,真正的挑戰是談心演算法不總有效。即便它可能對於一些簡單的資料、隨意的資料,以及所有你能考慮到的情況有效,但只要有一種情況不奏效,至少一個(假設沒有更多),評測系統就會由此停下。
對於例題,看看上述的談心演算法的工作工程,要考慮如下幾點:
假設,答案中不包括貪心演算法所拿掉的較大缺口,而是較小的。那麼通過結合較小缺口,使其與兩邊的木板相連。答案是通過用一定量的木板覆蓋儘量少的畜棚。新的答案更好些,所以假設是錯誤的,我們總是去掉最大缺口是正確的。
如果答案不包含特殊的缺口,但是包含一個與之同樣大的缺口,做相同的轉換,發現所需的木板量並沒有變化,這個新的答案與原來的答案是一樣的。我們可以選取任意一個。
因此,所產生的結果是最優的,它包含的是最大的切除片段。每一步都是這樣,最終也一定是這樣。
結論
如果一個貪心演算法存在,那就用它。它們的編寫、編譯、執行都很快,唯一令人遺憾地,它地正確性。如果貪心演算法可以得到正確的答案,那就努力地尋找這個演算法。但是,不要指望貪心演算法能解決所有問題
貪心演算法的3個相當經典的程式
1.線段覆蓋(linescover)
題目大意:在一維空間中告訴你N條線段的起始座標與終止座標,要求求出這些線段一共佔了多大的長度。
解題思路:將線段按其實座標進行排序,使之依次遞增。然後定義一個變數last記錄考慮到當前線段之時被線段覆蓋的最大的座標值。因為已經排過序所以當前線段的效應長度為(x為起始座標,y為終止座標):
length:=0 (y<=last)
y-last (x<=last & y>last)
y-x (x>last & y>last)
並且注意同時更新last的值。最後統計每條線段的效應長度就為我們所需要的答案。
2.最優數對(bestpair)
題目大意:按遞增的順序告訴你N個正整數和一個實數P,要求求出求出該數列中的比例最接近P的兩個數(保證絕對沒有兩個數使得其比值為P)。
解題思路:定義兩個指標i和j,然後進行如下操作:當code[j]/code[i]>p時inc(j),當code[j]/code[i]<p時inc(i),然後記錄其中產生的最優值即可。
3.連續數之和最大值(maxsum)
題目大意:給你N個數,要求求出其中的連續數之和的最大值。(也可以加入a和b來限制連續數的長度不小於a且不大於b)。
解題思路:定義一個統計變數tot,然後用迴圈進行如下操作:inc(tot,item) 其中如果出現tot<0的情況,則將tot賦值為0。在迴圈過程之中tot出現的最大值即為答案。如果加入了限制條件的話,問題就變得難一些了(這句真的不是廢話)。為此我們需要定義:陣列sum[i]來表示code[1]到code[i]之和(這樣的話code[a]~code[b]的和我們就可以用sum[b]-sum[a-1]來表示了。),陣列hash[i]來表示滿足條件的sum[a-1]所對應的下標(並使之按sum[i]的遞增順序排列,一定要處理好sum[i]進出hash的過程)。這樣的話對於終止座標為i的連續數列其最大和必定為sum[i]-sum[hash[1]],求出其最大值即可。
還是舉個例子,貪心演算法最經典的例子,給錢問題。
比如中國的貨幣,只看元,有1元2元5元10元20、50、100
如果我要16元,可以拿16個1元,8個2元,但是怎麼最少呢?
如果用貪心算,就是我每一次拿那張可能拿的最大的。
比如16,我第一次拿20拿不起,拿10元,OK,剩下6元,再拿個5元,剩下1元
也就是3張 10、5、1。
每次拿能拿的最大的,就是貪心。
但是一定注意,貪心得到的並不是最優解,也就是說用貪心不一定是拿的最少的張數
貪心只能得到一個比較好的解,而且貪心演算法很好想得到。
再注意,為什麼我們的錢可以用貪心呢?因為我們國家的錢的大小設計,正好可以使得貪心演算法算出來的是最優解(一般是個國家的錢幣都應該這麼設計)。如果設計成別的樣子情況就不同了
比如某國的錢幣分為 1元3元4元
如果要拿6元錢 怎麼拿?貪心的話 先拿4 再拿兩個1 一共3張錢
實際最優呢? 兩張3元就夠了
所謂貪心演算法是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的區域性最優解。
貪心演算法不是對所有問題都能得到整體最優解,但對範圍相當廣泛的許多問題他能產生整體最優解或者是整體最優解的近似解。
貪心演算法的基本思路如下:
1.建立數學模型來描述問題。
2.把求解的問題分成若干個子問題。
3.對每一子問題求解,得到子問題的區域性最優解。
4.把子問題的解區域性最優解合成原來解問題的一個解。
實現該演算法的過程:
從問題的某一初始解出發;
while 能朝給定總目標前進一步 do
求出可行解的一個解元素;
由所有解元素組合成問題的一個可行解;
二、例題分析
1、[揹包問題]有一個揹包,揹包容量是M=150。有7個物品,物品可以分割成任意大小。
要求儘可能讓裝入揹包中的物品總價值最大,但不能超過總容量。
物品 A B C D E F G
重量 35 30 60 50 40 10 25
價值 10 40 30 50 35 40 30
分析:
目標函式: ∑pi最大
約束條件是裝入的物品總重量不超過揹包容量:∑wi<=M( M=150)
(1)根據貪心的策略,每次挑選價值最大的物品裝入揹包,得到的結果是否最優?
(2)每次挑選所佔重量最小的物品裝入是否能得到最優解?
(3)每次選取單位重量價值最大的物品,成為解本題的策略。 ?
值得注意的是,貪心演算法並不是完全不可以使用,貪心策略一旦經過證明成立後,它就是一種高效的演算法。
貪心演算法還是很常見的演算法之一,這是由於它簡單易行,構造貪心策略不是很困難。
可惜的是,它需要證明後才能真正運用到題目的演算法中。
一般來說,貪心演算法的證明圍繞著:整個問題的最優解一定由在貪心策略中存在的子問題的最優解得來的。
對於例題中的3種貪心策略,都是無法成立(無法被證明)的,解釋如下:
(1)貪心策略:選取價值最大者。反例:
W=30
物品:A B C
重量:28 12 12
價值:30 20 20
根據策略,首先選取物品A,接下來就無法再選取了,可是,選取B、C則更好。
(2)貪心策略:選取重量最小。它的反例與第一種策略的反例差不多。
(3)貪心策略:選取單位重量價值最大的物品。反例:
W=30
物品:A B C
重量:28 20 10
價值:28 20 10
根據策略,三種物品單位重量價值一樣,程式無法依據現有策略作出判斷,如果選擇A,則答案錯誤。
所以需要說明的是,貪心演算法可以與隨機化演算法一起使用,具體的例子就不再多舉了。(因為這一類演算法普及性不高,而且技術含量是非常高的,需要通過一些反例確定隨機的物件是什麼,隨機程度如何,但也是不能保證完全正確,只能是極大的機率正確)
貪心演算法
例題:畜棚修理(1999年USACO春季公開賽試題)
有一長列畜棚,其中一些被木板覆著。你可以使用總共N個木板,這些木板可以覆蓋任意連續的畜棚。覆蓋所有必要的畜棚,使得所用的木板儘可能的少。
貪心演算法很快,一般的為O(n)到O(n^2)而且需要很少的額外空間。不幸地是,它一般是不正確的。但是,當它正確,它就會遠快於其它演算法。
演算法
在貪心演算法背後所隱含的基本演算法,是從小規模問題中生成大規模問題。這不像其他演算法,在貪心演算法的進行過程中,它只關心它發現的最好的策略。因此,對於例題,為了生成N=5時的策略,它先尋找N=4時的最優策略,然後以此生成。此時,對於其它N=4時的策略,不予考慮。
問題
貪心時,有兩個基礎的問題需要考慮。
怎麼生成?
怎樣從小規模策略中生成大規模策略?一般地,有這樣一個運作過程。對於例題,由四塊木板生成五塊木板的最顯然的方法,就是將其中一塊木板中的一部分拿掉,這樣一塊木板變成了兩塊。而你要做的,是在那些不需要被木板覆蓋的畜棚區域中,選出最長的片段(這樣,就使得所用的木板最少了)。
拿掉被覆蓋畜棚的木板中的一個片段,使得它變成兩段。一段在該片段之前,一段在該片段之後。
它有效嗎?
對於程式設計師,真正的挑戰是談心演算法不總有效。即便它可能對於一些簡單的資料、隨意的資料,以及所有你能考慮到的情況有效,但只要有一種情況不奏效,至少一個(假設沒有更多),評測系統就會由此停下。
對於例題,看看上述的談心演算法的工作工程,要考慮如下幾點:
假設,答案中不包括貪心演算法所拿掉的較大缺口,而是較小的。那麼通過結合較小缺口,使其與兩邊的木板相連。答案是通過用一定量的木板覆蓋儘量少的畜棚。新的答案更好些,所以假設是錯誤的,我們總是去掉最大缺口是正確的。
如果答案不包含特殊的缺口,但是包含一個與之同樣大的缺口,做相同的轉換,發現所需的木板量並沒有變化,這個新的答案與原來的答案是一樣的。我們可以選取任意一個。
因此,所產生的結果是最優的,它包含的是最大的切除片段。每一步都是這樣,最終也一定是這樣。
結論
如果一個貪心演算法存在,那就用它。它們的編寫、編譯、執行都很快,唯一令人遺憾地,它地正確性。如果貪心演算法可以得到正確的答案,那就努力地尋找這個演算法。但是,不要指望貪心演算法能解決所有問題
貪心演算法的3個相當經典的程式
1.線段覆蓋(linescover)
題目大意:在一維空間中告訴你N條線段的起始座標與終止座標,要求求出這些線段一共佔了多大的長度。
解題思路:將線段按其實座標進行排序,使之依次遞增。然後定義一個變數last記錄考慮到當前線段之時被線段覆蓋的最大的座標值。因為已經排過序所以當前線段的效應長度為(x為起始座標,y為終止座標):
length:=0 (y<=last)
y-last (x<=last & y>last)
y-x (x>last & y>last)
並且注意同時更新last的值。最後統計每條線段的效應長度就為我們所需要的答案。
2.最優數對(bestpair)
題目大意:按遞增的順序告訴你N個正整數和一個實數P,要求求出求出該數列中的比例最接近P的兩個數(保證絕對沒有兩個數使得其比值為P)。
解題思路:定義兩個指標i和j,然後進行如下操作:當code[j]/code[i]>p時inc(j),當code[j]/code[i]<p時inc(i),然後記錄其中產生的最優值即可。
3.連續數之和最大值(maxsum)
題目大意:給你N個數,要求求出其中的連續數之和的最大值。(也可以加入a和b來限制連續數的長度不小於a且不大於b)。
解題思路:定義一個統計變數tot,然後用迴圈進行如下操作:inc(tot,item) 其中如果出現tot<0的情況,則將tot賦值為0。在迴圈過程之中tot出現的最大值即為答案。如果加入了限制條件的話,問題就變得難一些了(這句真的不是廢話)。為此我們需要定義:陣列sum[i]來表示code[1]到code[i]之和(這樣的話code[a]~code[b]的和我們就可以用sum[b]-sum[a-1]來表示了。),陣列hash[i]來表示滿足條件的sum[a-1]所對應的下標(並使之按sum[i]的遞增順序排列,一定要處理好sum[i]進出hash的過程)。這樣的話對於終止座標為i的連續數列其最大和必定為sum[i]-sum[hash[1]],求出其最大值即可。
還是舉個例子,貪心演算法最經典的例子,給錢問題。
比如中國的貨幣,只看元,有1元2元5元10元20、50、100
如果我要16元,可以拿16個1元,8個2元,但是怎麼最少呢?
如果用貪心算,就是我每一次拿那張可能拿的最大的。
比如16,我第一次拿20拿不起,拿10元,OK,剩下6元,再拿個5元,剩下1元
也就是3張 10、5、1。
每次拿能拿的最大的,就是貪心。
但是一定注意,貪心得到的並不是最優解,也就是說用貪心不一定是拿的最少的張數
貪心只能得到一個比較好的解,而且貪心演算法很好想得到。
再注意,為什麼我們的錢可以用貪心呢?因為我們國家的錢的大小設計,正好可以使得貪心演算法算出來的是最優解(一般是個國家的錢幣都應該這麼設計)。如果設計成別的樣子情況就不同了
比如某國的錢幣分為 1元3元4元
如果要拿6元錢 怎麼拿?貪心的話 先拿4 再拿兩個1 一共3張錢
實際最優呢? 兩張3元就夠了