貪心轉載專題之:貪心演算法介紹
原文連結:http://blog.csdn.net/zhouq1986/article/details/2037126
注意,本人不贊同原文中揹包問題的反例。但做未修改。本文中後面三個貪心演算法例子相當經典,此為轉載此篇的很重要原因。
貪心演算法思想:
顧名思義,貪心演算法總是作出在當前看來最好的選擇。也就是說貪心演算法並不從整體最優考慮,它所作出的選擇只是在某種意義上的區域性最優選擇。當然,希望貪心演算法得到的最終結果也是整體最優的。雖然貪心演算法不能對所有問題都得到整體最優解,但對許多問題它能產生整體最優解。如單源最短路經問題,最小生成樹問題等。在一些情況下,即使貪心演算法不能得到整體最優解,其最終結果卻是最優解的很好近似。
貪心演算法的基本要素:
1.貪心選擇性質。所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列區域性最優的選擇,即貪心選擇來達到。這是貪心演算法可行的第一個基本要素,也是貪心演算法與動態規劃演算法的主要區別。
動態規劃演算法通常以自底向上的方式解各子問題,而貪心演算法則通常以自頂向下的方式進行,以迭代的方式作出相繼的貪心選擇,每作一次貪心選擇就將所求問題簡化為規模更小的子問題。
對於一個具體問題,要確定它是否具有貪心選擇性質,必須證明每一步所作的貪心選擇最終導致問題的整體最優解。
2. 當一個問題的最優解包含其子問題的最優解時,稱此問題具有最優子結構性質。問題的最優子結構性質是該問題可用動態規劃演算法或貪心演算法求解的關鍵特徵。
貪心演算法的基本思路:
從問題的某一個初始解出發逐步逼近給定的目標,以儘可能快的地求得更好的解。當達到演算法中的某一步不能再繼續前進時,演算法停止。
該演算法存在問題:
1. 不能保證求得的最後解是最佳的;
2. 不能用來求最大或最小解問題;
3. 只能求滿足某些約束條件的可行解的範圍。
實現該演算法的過程:
從問題的某一初始解出發;
while 能朝給定總目標前進一步 do
求出可行解的一個解元素;
由所有解元素組合成問題的一個可行解;
用揹包問題來介紹貪心演算法:
揹包問題:有一個揹包,揹包容量是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種貪心策略,都是無法成立(無法被證明)的,解釋如下:
貪心策略:選取價值最大者。反例:
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,則答案錯誤。
所以需要說明的是,貪心演算法可以與隨機化演算法一起使用,具體的例子就不再多舉了。(因為這一類演算法普及性不高,而且技術含量是非常高的,需要通過一些反例確定隨機的物件是什麼,隨機程度如何,但也是不能保證完全正確,只能是極大的機率正確)。
幾種常見的貪心演算法
有人說貪心演算法是最簡單的演算法,原因很簡單:你我其實都很貪,根本不用學。有人說貪心演算法是最複雜的演算法,原因也很簡單:這世上貪的人太多了,那輪到你我的份?
不論難度如何,貪心演算法都是一個很重要的演算法,我在網上以及書上題目中,總結了三類較為常見,也十分經典的貪心演算法,這裡略表介紹。
(注:由於沒有現成的名字可用,這三種類型貪心演算法的名字都是我自己取的,只是用一個詞語去表達一類的貪心問題)。
1.線段覆蓋(lines cover)
題目大意:
在一維空間中告訴你N條線段的起始座標與終止座標,要求求出這些線段一共覆蓋了多大的長度。
解題思路:
將線段按其座標進行排序(排序的具體方法:按起始座標排,起始座標相同的按終止座標排,都是小在前大在後),使之依次遞增,並按順序分別編號為X(i),X(i).a代表其起始座標,X(i).b代表其終止座標。
然後按排好的順序依次處理:定義一個變數last記錄考慮到當前線段之時被線段覆蓋的最大的座標值,再定義一個變數length記錄當前線段覆蓋的長度。對於後面的線段,我們把它看成由兩個部分組成,即把它分成last之前的線段和last之後的線段。(如果線段全部處在last之後,其last之前的部分不存在。)由於我們排過序,我們可以肯定當前考慮的線段X(i)其處在last之前的部分不會對length造成影響(因為X(i-1).b=last,X(i).a>=X(i-1).a,即X(i)在last之前的部分所處位置肯定被線段X(i-1)覆蓋過),所以會對length產生影響的即是X(i)處在last之後的部分。
所以我們可以依次對每條線段做如下處理:(初始化length為零,last為負無窮)
length+=X(i).b-last (X(i).a<=last 且 X(i).b>=last)
length+=X(i).b-X(i).a (X(i).a>last)
last=X(i).b;
最後length就為我們所需要的答案。
2.最優數對(best pair)
題目大意:
按遞增的順序告訴你N個正整數和一個實數P,要求求出求出該數列中的比例最接近P的兩個數(保證絕對沒有兩個數使得其比值為P)。
解題思路:
定義兩個指標i和j,先初始化i=j=1,然後進行如下操作:
當code[j]/code[i]>p時,inc(j);
當code[j]/code[i]<p時,inc(i)。
記錄其中產生的最優值即為答案。
3.連續數之和最大值(max sum)
題目大意:
給出一個長度為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]的下標,並使之按遞增順序排列,這樣當前以第i的數為終止的數列的最大值肯定就是sum[i]-sum[hash[1]]。
現在我們來討論hash陣列之中的資料需要滿足的條件和如何維護的具體問題:
當考慮到以第i個數為結尾時,hash[i]所表示的下標需要滿足的第一個條件就是題目規定的長度限制,我們需要實時的加入滿足長度規定的下標,刪除不符合要求的下標。其次,與不加限制條件時相同,若sum[i]-sum[hash[1]]的值小於零,則清空陣列hash。
維護時可以這樣,當考慮到第i個數時,我們就將下標i-a+1加入到hash中,因為hash中原來已經排好序,因此我們我們可以用插入排序來維護hash的遞增性,然後我們考察hash[1],若hash[1]<i-b+1,則證明其已超出長度限制,我們就將其刪除,接著再考慮更新後的hash[1],如此重複直至找到一個滿足條件的hash[1]為止。
我們可以用連結串列來表示hash,這樣就可以減少資料加入和刪除時頻繁資料移動的時間消耗。
記錄下sum[i]-sum[hash[1]]的最大值即為答案。
動態規劃和貪心演算法相同與不同:
相同點:
動態規劃和貪心演算法都是一種遞推演算法;
均有區域性最優解來推導全域性最優解 ;
不同點:
貪心演算法:
1.貪心演算法中,作出的每步貪心決策都無法改變,因為貪心策略是由上一步的最優解推導下一步的最優解,而上一部之前的最優解則不作保留。
2.由(1)中的介紹,可以知道貪心法正確的條件是:每一步的最優解一定包含上一步的最優解。
動態規劃演算法:
1.全域性最優解中一定包含某個區域性最優解,但不一定包含前一個區域性最優解,因此需要記錄之前的所有最優解。
2.動態規劃的關鍵是狀態轉移方程,即如何由以求出的區域性最優解來推導全域性最優。
3.邊界條件:即最簡單的,可以直接得出的區域性最優解。
貪心演算法的理論基礎:
藉助於擬陣工具,可建立關於貪心演算法的較一般的理論。這個理論對確定何時使用貪心演算法可以得到問題的整體最優解十分有用。
1. 擬陣
擬陣M定義為滿足下面3個條件的有序對(S,I):
(1)S是非空有限集。
(2)I是S的一類具有遺傳性質的獨立子集族,即若BÎI,則B是S的獨立子集,且B的任意子集也都是S的獨立子集。空集Æ必為I的成員。
(3)I滿足交換性質,即若AÎI,BÎI且|A|<|B|,則存在某一元素xÎB-A,使得A∪{x}ÎI。
例如,設S是一給定矩陣中行向量的集合,I是S的線性獨立子集族,則由線性空間理論容易證明(S,I)是一擬陣。擬陣的另一個例子是無向圖G=(V,E)的圖擬陣。
給定擬陣M=(S,I),對於I中的獨立子集AÎ I,若S有一元素xÎ A,使得將x加入A後仍保持獨立性,即A∪{x} Î I,則稱x為A的可擴充套件元素。
當擬陣M中的獨立子集A沒有可擴充套件元素時,稱A為極大獨立子集。
下面的關於極大獨立子集的性質是很有用的。
定理1:擬陣M中所有極大獨立子集大小相同。
這個定理可以用反證法證明。
若對擬陣M=(S,I)中的S指定權函式W,使得對於任意xÎS,有W(x)>0,則稱擬陣M為帶權擬陣。依此權函式,S的任一子集A的權定義為 。
2. 關於帶權擬陣的貪心演算法
許多可以用貪心演算法求解的問題可以表示為求帶權擬陣的最大權獨立子集問題。
給定帶權擬陣M=(S,I),確定S的獨立子集AÎI使得W(A)達到最大。這種使W(A)最大的獨立子集A稱為擬陣M的最優子集。由於S中任一元素x的權W(x)是正的,因此,最優子集也一定是極大獨立子集。
例如,在最小生成樹問題可以表示為確定帶權擬陣 的最優子集問題。求帶權擬陣的最優子集A的演算法可用於解最小生成樹問題。
下面給出求帶權擬陣最優子集的貪心演算法。該演算法以具有正權函式W的帶權擬陣M=(S,I)作為輸入,經計算後輸出M的最優子集A。
Set greedy (M,W)
{A=Æ;
將S中元素依權值W(大者優先)組成優先佇列;
while (S!=Æ) {
S.removeMax(x);
if (A∪{x}I) A=A∪{x};
}
return A;
}
演算法greedy的計算時間複雜性為 。
引理1 (擬陣的貪心選擇性質)
設M=(S,I)是具有權函式W的帶權擬陣,且S中元素依權值從大到小排列。又設xÎS是S中第一個使得{x}是獨立子集的元素,則存在S的最優子集A使得xÎ A。
演算法greedy在以貪心選擇構造最優子集A時,首次選入集合A中的元素x是單元素獨立集中具有最大權的元素。此時可能已經捨棄了S中部分元素。可以證明這些被捨棄的元素不可能用於構造最優子集。