貪心算法(一)


概念:貪心法,指的是從問題的初始狀態出發,通過若干次的貪心選擇而得出最優值(或較優解)的一種解題方法。
其實,從“貪心策略”一詞我們便可以看出,貪心策略總是做出在當前看來是最優的選擇,也就是說貪心策略並不是從整體上加以考慮,它所做出的選擇只是在某種意義上的局部最優解,而許多問題自身的特性決定了該題運用貪心策略可以得到最優解或較優解。

貪心法的特點

1、貪心選擇性質: 所謂貪心選擇性質是指應用同一規則f,將原問題變為一個相似的、但規模更小的子問題、而后的每一步都是當前看似最佳的選擇。這種選擇依賴於已做出的選擇,但不依賴於未做出的選擇。從全局來看,運用貪心策略解決的問題在程序的運行過程中無回溯過程。
2、局部最優解: 由於運用貪心策略解題在每一次都取得了最優解,但能夠保證局部最優解的不一定是貪心算法。如后面我們要學習的動態規划算法、寬度優先搜索(BFS)等算法中的解題過程亦可以滿足局部最優解。

貪心法的思路

基本思路
從問題的某一個初始解出發逐步逼近給定的目標,以盡可能快的地求得更好的解。當達到某算法中的某一步不能再繼續前進時,算法停止。


實現該算法的過程:
   從問題的某一初始解出發;
     while 能朝給定總目標前進一步 do
  求出可行解的一個解元素;
    由所有解元素組合成問題的一個可行解

 

  • 貪心算法其實是挺容易實現的,因為它是符合人的一般思維過程,按照人解決問題的思路來解決問題,這一點可以在下面的例題體現的非常明了。
  • 貪心法的優勢在於編程簡單、運行效率高、空間復雜度低,是信息學競賽中的一個有力武器,受到了廣大信息學愛好者的青睞。
  • 貪心法的應用非常廣泛,比如在圖論中,“Dijkstra算法”求解單源最短路徑問題、“Prim 算法”和“Kruskla 算法”求最小生成樹問題、哈夫曼樹的構造(哈夫曼算法)等都是基於貪心法。

 

舉例

例1:在N 行M 列的正整數矩陣中,要求從每行中選出1 個數,使得選出的總共N 個數的和最大。
【算法分析】
要使總和最大,則每個數要盡可能大,自然應該選每行中最大的那個數。因此,我們設計出如下算法:
讀入N, M,矩陣數據;
Total := 0;
For I := 1 to N do begin {對N 行進行選擇}
選擇第I行最大的數,記為K;
Total := Total + K;
End;
輸出最大總和Total;
從上例中我們可以看出,和遞推法相仿,貪心法也是從問題的某一個初始解出發,向給定的目標遞推。但不同的是,推進的每一步不是依據某一固定的遞推式,而是做一個局部的最優選擇,即貪心選擇(在例中,這種貪心選擇表現為選擇一行中的最大整數),這樣,不斷的將問題歸納為若干相似的子問題,最終產生出一個全局最優解。

 

例2、刪數問題(delete.???)
[問題描述]
 輸入一個高精度的正整數n(≤240位),去掉其中任意s個數字后剩下的數字按原左右次序將組成一個新的正整數。
編程,要求對給定的n和s,尋找一種方案,使得剩下的數字組成的新數最小。
[輸入格式]
輸入文件delete.in,共兩行,第一行為一個整數n,第二行為一個整數s。
[輸出格式]
輸出文件delete.out,僅一行,表示最后剩下的最小數。
[樣例輸入]
178543
4
[樣例輸出]
13

[算法分析]
由於正整數n的有效數位為240位,所以很自然地采用字符串類型存貯n。那么如何決定哪s位被刪除呢?是不是最大的s個數字呢?顯然不是,大家很容易舉出一些反例。為了盡可能逼近目標,我們選取的貪心策略為:每一步總是選擇一個使剩下的數最小的數字刪去,即按高位到低位的順序搜索,若各位數字遞增,則刪除最后一個數字;否則刪除第一個遞減區間的首字符,這樣刪一位便形成了一個新數字串。然后回到串首,按上述規則再刪下一個數字。重復以上過程s次為止,剩下的數字串便是問題的解了。
例如:n=178543,s=4。刪數的過程如下:
          n=178543        {刪掉8}  
              17543          {刪掉7}
              1543            {刪掉5}
              143              {刪掉4}
              13                {解即為13}
這樣,刪數問題就與如何尋找遞減區間首字符這樣一個簡單的問題對應起來。不過還要注意一個細節問題,就是刪除若干個字符后可能會出現字符串串首有若干0的情況,甚至整個字符串都是0的情況。
按以上貪心策略編制的程序框架如下:

 1 begin
 2 輸入n,s;  3      while s>0 do
 4      begin
 5         i:=1{從串首開始找}
 6         while (i<length(n)) and (n[i]<=n[i+1]) do  i:=i+1 7         delete (n,i,1);   {刪除字符串n的第i個字符}
 8         s:=s-1 9      end10      while (length(n)>1)and (n[1]=‘0’) do delete(n,11); 11        {刪去串首可能產生的無用零}
12  輸出n; 13 End;

 

例3、獨木舟(KAJ.???)
[問題描述]
    我們計划組織一個獨木舟旅行。租用的獨木舟都是一樣的,最多乘兩人,而且載重有一個限度。現在要節約費用,所以要盡可能地租用最少的舟。你的任務是讀入獨木舟的載重量,參加旅行的人數以及每個人的體重,計算出所需要的租船數目。
[輸入格式]
輸入文件kaj.in,第一行是w(80≤w≤200),表示每條獨木舟最大的載重量。第二行是整數n(1≤n≤30000,參加旅行的人數。接下來的n行,每行是一個整數Ti(5≤Ti≤w),表示每個人的重量。
[輸出格式]
輸出文件僅一行,表示最少的租船數目。
[樣例輸入]
100
9
90
20
20
30
50
60
70
80
90
[樣例輸出]
6

[算法分析]
  基於貪心法,找到一個重量最大的人,讓它盡可能與重量大的人同乘一船。如此循環直至所有人都分配完畢即可統計出所需要的獨木舟數。
特別注意的是,局部貪心的選擇是否可以得出全局最優是能否采用貪心法的關鍵所在。對於能否使用貪心策略,應從理論上予以證明。下面我們看看另一個問題。

 

例4:部分背包問題
給定一個最大載重量為M 的卡車和N 種食品,有食鹽,白糖,大米等。已知第i 種食品的最多擁有Wi公斤,其商品價值為Vi元/公斤,編程確定一個裝貨方案,使得裝入卡車中的所有物品總價值最大。

【算法分析】
因為每一個物品都可以分割成單位塊,單位塊的利益越大顯然總收益越大,所以它局部最優滿足全局最優,可以用貪心法解答,

方法如下:先將單位塊收益按從大到小進行排列,然后用循環從單位塊收益最大的取起,直到不能取為止便得到了最優解。因此我們非常容易設計出如下算法:

 1 問題初始化; {讀入數據}
 2 按Vi從大到小將商品排序;  3 I := 1;  4 repeat
 5   if M = 0 then Break; {如果卡車滿載則跳出循環}
 6   M := M - Wi;  7   if M >= 0 then 將第I 種商品全部裝入卡車  8    else
 9     將(M + Wi)重量的物品I 裝入卡車; 10    I := I + 1; {選擇下一種商品}
11 until (M <= 0) OR (I >N)

在解決上述問題的過程中,首先根據題設條件,找到了貪心選擇標准(Vi),並依據這個標准直接逐步去求最優解,這種解題策略被稱為貪心法。

小結

利用貪心策略解題,需要解決兩個問題:
首先,確定問題是否能用貪心策略求解;一般來說,適用於貪心策略求解的問題具有以下特點:
①可通過局部的貪心選擇來達到問題的全局最優解。運用貪心策略解題,一般來說需要一步步的進行多次的貪心選擇。在經過一次貪心選擇之后,原問題將變成一個相似的,但規模更小的問題,而后的每一步都是當前看似最佳的選擇,且每一個選擇都僅做一次。
②原問題的最優解包含子問題的最優解,即問題具有最優子結構的性質。在背包問題中,第一次選擇單位重量價值最大的貨物,它是第一個子問題的最優解,第二次選擇剩下的貨物中單位重量價值最大的貨物,同樣是第二個子問題的最優解,依次類推。
③其次,如何選擇一個貪心標准?正確的貪心標准可以得到問題的最優解,在確定采用貪心策略解決問題時,不能隨意的判斷貪心標准是否正確,尤其不要被表面上看似正確的貪心標准所迷惑。在得出貪心標准之后應給予嚴格的數學證明
以上幾個例題所用的貪心思想還是很明顯的,選取的貪心策略也很直觀(可能也是唯一的),不僅能得到最優解,而且解的正確性也可以得到嚴格的證明。但有些問題采用貪心法求解時,貪心策略可能有多種,在針對性不同的測試數據下,帶來的效果可能也不一樣,並不是總能得到最優解,而且解的正確性證明也很困難,最典型的例子就是0/1背包問題。

例5、0/1背包問題(bb.???)
[問題描述]
有一容量為weight(longint范圍以內)的背包。現在要從n(<20)件物品中選取若干裝入背包中,每件物品i的重量為w[i],價值為p[i]。定義一種可行的背包裝載為:背包中物品的總重量不能超過背包的容量,並且一個物品要么全部選取,要么不選取。定義最佳裝載是指所裝入的物品價值最高,並且是可行的背包裝載。w[i],p[i]均為integer以內的正整數。
[樣例輸入]
11              {weight}
4                {n}
2 4 6 7       {w[i]}
6 10 12 13 {p[i]}
[樣例輸出]
0 1 0 1
23
 
[算法分析]
設數組choice[1..n],若choice[i]=1表示物品i裝入背包中,choice[i]=0表示物品i不裝入背包中。則choice=[0,1,0,1]是一種可行的背包裝載方案,也是一種最佳裝載方案,此時總價值為23。
0/1背包問題有好幾種貪心准則,每種貪心准則都是采用多步過程來完成背包的裝入,在每一步過程中利用某種固定的貪心准則選擇一個物品裝入背包。

  • 一種貪心准則為:從剩余的物品中,選出可以裝入背包的價值最大的物品,利用這種規則,價值最大的物品首先被裝入(假設有足夠容量),然后是下一個價值最大的物品,如此繼續下去。這種貪心准則不能保證得到最優解。例如weight=105,n=3,w=[100,10,10],p=[20,15,15]。按照以上這種“價值貪心准則”,獲得的解為choice=[1,0,0],這種方案的總價值為20。而最優解為choice =[0,1,1],其總價值為30。
  • 另一種方案是“重量貪心准則”,即從剩下的物品中選擇可裝入背包的重量最小的物品。雖然這種規則對於前面的例子能產生最優解,但在一般情況下不一定能得到最優解,例如weight=25,n=2,w=[10,20],p=[5,100]。獲得的解為choice=[1,0],總價值為5,比最優解choice =[0,1]的總價值100要差。
  • 本題的另一方案為“單位價值貪心准則”,這種方案是從剩余物品中選擇可裝入包的p[i] /w[i]值最大的物品,這種策略也不能保證得到最優解。例如weight=30,n=3,w=[20,15,15],p=[40,25,25]。獲得的解為choice=[1,0,0],總價值為40。而最優解為choice=[0,1,1]的總價值為50。

既然任何一種貪心策略都不能保證得到最優解,那么本題是不是不應該用貪心法?其實我們不必因所考察的幾個貪心策略都不能保證得到最優解而沮喪(或放棄),因為0/1背包問題是一個復雜的NP問題。對於這類問題,也許根本就不可能找到具有多項式時間的算法。雖然按“單位價值貪心准則”遞減的次序裝入物品不能保證得到最優解,但它是一個直覺上近似的解,而且時間復雜度僅為O(nlog2n)。造成多種貪心都不能全對的原因其實在於,在很多情況下,無法將背包填滿,空間上的浪費間接降低了實際上的單位價值。在實際應用中,我們還可以就在一個程序中用多種貪心准則得到多個最優解,然后再打擂台選擇一個最最優的輸出。當然,本題還有一種思路,就是用動態規划求解。

說明:
數學上著名的“NP問題”,完整的叫法是“NP完全問題”,也即“NP COMPLETE問題”,NP就是Non-deterministic Polynomial的問題,中文意思是多項式復雜程度的非確定性問題。什么是非確定性問題呢?有些計算問題是確定性的,比如加減乘除之類,你只要按照公式推導,按部就班一步步來,就可以得到結果。但是,有些問題是無法按部就班直接計算出來的。比如,找大質數的問題,有沒有一個公式,你只要一套這個公式,就可以一步步推算出來,下一個質數應該是多少呢?這樣的公式是沒有的。再比如,大的合數分解質因數的問題,有沒有一個公式,把合數代進去,就直接可以算出,它的因子各自是多少?也沒有這樣的公式。這種問題的答案,是無法直接計算得到的,只能通過間接的“猜算”來得到結果。這就是非確定性問題。


這些問題通常都有一些算法,它不能直接告訴你答案是什么,但可以告訴你,某個可能的結果是正確的答案還是錯誤的。這個可以告訴你“猜算”的答案正確與否的算法,假如可以在多項式時間內算出來,就叫做多項式非確定性問題。而如果這個問題的所有可能答案,都可以在多項式時間內進行正確與否的驗算的話,就叫完全多項式非確定問題。完全多項式非確定性問題可以用窮舉法得到答案,一個個檢驗下去,最終便能得到結果。但是這樣的算法,它的時間復雜度是指數關系,因此計算的時間隨問題的復雜程度成指數的增長,很快便變得不可計算了。


因此,貪心不能簡單進行,而需要全面的考慮,最后得到證明

貪心法的正確性證明

貪心法的正確性證明是個難點,尤其是在非常有限的競賽時間內。所以,很多選手往往是大膽假設自己選擇的貪心策略是正確的,這樣難免會出錯,但也是一種不得已而為之的辦法。其實貪心法的證明雖然不容易,但一些常見的方法還是值得總結的。
當一個貪心算法不能確定其100%正確,使用之前就應該嘗試證明它的不正確性。而要證明其不正確,一種最簡單的方法就是舉一個反例。其實,要嚴格證明一個貪心算法的正確性是很困難,目前最有效的一種方法叫“矩陣胚理論”,但是很復雜,不再介紹。其實,即使一個貪心算法是不完全正確的,我們也可以努力尋找一些調整方法,或制定多種貪心策略,通過調整優化、比較擇優來爭取得到最優解,甚至也可以先得到一個較優解,然后在此基礎上進行搜索剪枝或分支定界。常見的證明方法有以下這樣幾種。

  • 反證法

        用貪心的策略,依次構造出一個解S1,假設最優解S2不同於S1,可以證明是矛盾的,從而得出S1就是最優解。

        P1005 NOIP1998_T2_聯接數

  • 構造法

        根據描述的算法,用貪心的策略,依次構造出一個解,可證明一定是合法的解。即用貪心法找可行解。
        P1425 貪心算法_取數游戲

  • 調整法

              用貪心的策略,依次構造出一個解S1。假設最優解S2不同於S1,找出不同之處,在不破壞最優性的前提下,逐步調整S2,最終使其變為S1。從而S1也是最優解。
        P1421 貪心算法_排隊接水


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM