【譯】貪心演演算法,你入門了嗎?
- 原文地址:Greedy Algorithms 101
- 原文作者:Mario Osorio
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:chaingangway
- 校對者:PingHGao、司徒公子
貪心演演算法,你入門了嗎?
貪心演演算法在大多數情況下都易於實現,在求解最優問題時,也是最常用的編碼套路之一,而且它的資源消耗也比較低。
不過這個演演算法也有缺點,它不能保證每次都能找到最優解,有時候只能找到接近最優解的方案。不管怎樣,在很多情況下,接近最優解就足夠了。
這個演演算法一般是對規模為 “n” 的問題迭代 “n” 次,所以它的複雜度可能是 O(n),O(n × log(n)),但是不會超過 O(n²)。
這個演演算法能解決的大多數問題都有以下兩個特性:
- 貪心屬性:它的意思是每次迭代時都採用區域性最優解,而無需考慮對全域性的影響。我們相信通過不斷求解區域性最優解終會得到全域性最優解,但是正如我之前所說,這個結論不一定成立。為了證明在每次迭代中都求得了最優解,我們需要使用歸納法(顯然不是簡單的證明)。
- 最優子結構: 我之前提到過一些。求解的問題必須能劃分為子問題,每個子問題都有最優解。
本文中,我們將學習如何編寫自己的貪心演演算法,然後用這個演演算法解決一個非常著名的難題。
貪心演演算法通用模版 (Java)
public ArrayList greedy(ArrayList candidates) {
solution;
while (!isSolution(solution) && (candidatesLeft(candidates)) {
cadidate = selectCandidate(candidates);
removeCandidate(candidate,candidates);
if (isGoodCandidate(candidate,solution)) {
addCandidate(candidate,solution);
}
}
if (isSolution(solution)) {
return solution;
} else {
return null;
}
}
}
複製程式碼
在解釋程式碼之前,我先給出一些在虛擬碼中用到的術語的定義。
- Candidates: 所有可能的解集。它可以是任意的資料型別,但通常是可迭代的。在我們處理示例問題時,會加深對它的理解。現在請先記住結論 ?。
- Candidate: 在解集中,我們當前選中的一個解。
- Solution: 解變數的第一個例項只需是一種資料結構,在這裡我們將儲存當前的解。
- isSolution,candidatesLeft,removeCandidate,addCandidate,isGoodCandidate: 這些也是我們要建立的方法,其中的某些方法在一些實際問題中不必是完整的,但是為了總結程式碼模版,我把它們全部定義為方法。
首先,我們初始化解的資料結構,它可以是陣列,布林值,整數…… 我們只需要宣告一下。
solution
複製程式碼
然後,我們看一下這個 while 迴圈,它的迴圈條件中有兩個方法。這些方法必須編寫,但有時並不需要完整的方法體,例如,判斷是否有剩餘備選解的這個方法。
while (!isSolution(solution) && (candidatesLeft(candidates))
複製程式碼
當我們發現當前尚未找到解,並且有剩餘備選解可以嘗試時,我們將選擇一個備選解,並立即將其從我們的備選解集中刪除。
cadidate = selectCandidate(candidates);
removeCandidate(candidate,candidates);
複製程式碼
下一步很簡單。如果候選解是正確的解,則只需將其新增到解結構中。
if (isGoodCandidate(candidate,solution)) {
addCandidate(candidate,solution);
}
複製程式碼
然後,我們只需檢查問題是否已達到解決的狀態,然後將解返回。
if (isSolution(solution)) {
return solution;
} else {
return null;
}
複製程式碼
至此我們已經看完了程式碼並對其進行了粗略的解釋,現在我給您出道題,請您嘗試自己解答。這是一個眾所周知的問題,在網上很容易就能搜到答案,但我建議您還是嘗試自己解決。
零錢兌換的問題
有6種硬幣,每種硬幣的值分別為 {50,20,10,5,2,1},它們按遞減排序作為引數傳遞。 每種硬幣都可能成為我們的候選解。您必須找到一種最佳的兌換方式。(用最少的硬幣找零)
示例輸入: 15(我們必須以最少的硬幣數量湊齊 15 並返回硬幣集合)
示例輸出: 10、5(我們返回了和為 15 的硬幣集合,且硬幣數量最少)
在這個確定的硬幣系統 {50,20,10,5,2,1} 中,該演演算法能找到最優解,但是請注意,如果候選解發生改變,可能會導致該演演算法無法找到最優解。
提示
如果您沒有足夠努力嘗試,就不應該看這一段內容 ?…… 開個玩笑,繼續吧,我確信我希望您已經學到了一些新知識 ?。
- 在 selectCandidate() 方法中,首先選擇面額最大的硬幣,然後用較小的硬幣填充剩餘的零錢。在這個過程中,您要一直檢查是否超出剩餘的零錢。
解法
我提供的解法用 Java 編寫的,其中用到了面向物件的知識。
public class Coin {
private int value;
private int quantity;
Moneda(int value,int quantity) {
this.value = value;
this.quantity = quantity;
}
/* getters & setters */
}
/* This is actually the "hard" part */
int selectCandidate(ArrayList < Integer > values) {
int biggest = Integer.MIN_VALUE;
for (Integer coin: values)
if ((biggest < coin)) biggest = coin;
return biggest;
}
/* Now the actual schema */
ArrayList < Coin > greedySchema(ArrayList < Integer > values,int quantity) {
/* We initialize our solution structure */
ArrayList < Coin > solution = new ArrayList < Coin > ();
/* Any auxiliary variable is ok */
int value;
while ((quantity > 0) && (!values.isEmpty())) {
/* Select and remove the coin from our monetary system */
value = selectCandidate(values);
values.remove(new Integer(value));
/* If this is true,it meanwe can still look for coins to give */
if ((quantity / value) > 0) {
solution.add(new Coin(value,quantity / value));
/* This will lower the quantity we need to give back */
quantity = quanity % value;
}
}
/* Once the quantity is 0 we are DONE! */
if (quantity == 0) {
return solution;
} else return null;
}
複製程式碼
資源
如果您喜歡貪心演演算法的工作原理,並且想深入研究,請訪問 Hackerrank 或者 Hackerearth,這裡有有很多要解決的問題,我相信您已經對它們有一定了解 ?。
有時,我個人也會把 GitHub 作為搜尋引擎,並簡單地寫下我尋找的主題 [貪心演演算法]。
結論
綜上所述,即使對於簡單的個人專案,貪心演演算法也能表現優異,它不需要你花費太多時間去思考,並且只消耗很少的資源。而且,使用貪心演演算法可以輕鬆解決很多面試問題。大多數時候,使用貪心或動態規劃都可以滿足記憶體和複雜度方面的要求,但這就是另一個話題了 ?。
感謝您的閱讀,歡迎評論 ?。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。