貪心演算法顧名思義在一個貪字上面,它在解決某個問題的時候,總是先從眼前利益出發。也就是說只顧眼前,不顧大局,所以它是區域性最優解。它的核心的就是區域性最優推出全域性最優。
比如公司只有一個會議室,明天有幾場同樣的重要的會議要開,怎麼安排會議才能儘可能的多開會。
如果我們將所有會議的結束時間從小到大排序。然後從結束時間最小的開始選(區域性最優),然後按照這個排序去遍歷判斷後面是開始時間是否在這之後,如果在則選它,如果不在,則判斷下一個是否在。這樣是否就能達到最終的最優解?可以嘗試舉反例來證明它結果的合法性。你會發現在這個場景中,似乎找不到比上述方案的更好的結果了。其實這就是一個貪心演算法
貪心演算法:
一定會有一個排序,這個就是我們所說的貪心策略,上面問題的策略就是以會議結束時間排序;不同的貪心策略出來結果是不一樣,如果結果能被其他舉例推翻,那就說明這個策略不合理。
貪心演算法不是對所有問題都能得到整體最優解。但是如果經過大量證明成立之後,那麼它就是一種高效的演算法。
揹包問題:
小偷去偷東西,揹包只有5KG容量,現在有三個物品(不能分割,且各自只有一個),問小偷怎麼拿才能帶走最大價值的東西
A物品:1kg 6元
B物品:2kg 10元
C物品:4kg 12元
如果用貪心來看,這個問題我們似乎只能根據單價來排序了A單價6,B單價5,C單價3,那麼我們按排序結果選擇,就會選擇AB,這個時候我們的價值是16,顯然選擇AC,價值是18,所以這個貪心策略很容易就被推翻了。
如此看來這個問題用貪心演算法好像是無法解決了。
1.我們可以列舉(把所有情況的列出來)來看,那用到排列組合了,C33全部列出來看,資料小還可以,資料大了呢?
2.遍歷所有,我們每個物品都有兩個操作,選或者不選。
顯然標紅的那條路線就是我們最優的方案。每次在物品加進來的時候都會儲存選擇與不選擇兩種狀態那麼這樣下去越到後面狀態儲存的就越多其實就是2^n次。
3.動態規劃
問將揹包按1kg來邏輯分割,可以整理出來如下表格
|
1kg |
2kg |
3kg |
4kg |
5kg |
加入物品A |
6 |
6 |
6 |
6 |
6 |
加入物品B |
6 |
10 |
10+6=16 |
16 |
16 |
加入物品C |
6 |
10 |
16 |
16 |
12+6=18 |
首先這個表格是從上往下一行一行的來看,前面的會影響後面的。第一行為什麼全是6,因為這個是後只有A加入,第二行加入B,這個時候前面有一行A的資料了,所以要一起考慮A了.....
從第二行開始看下具體的邏輯,加入物品B,當揹包容量1kg的時候,B是裝不進去的,但是前面的資料1kg的時候可以裝A,所以價值是6;揹包容量2kg這個時候剛好可以裝B,比較下B和之前的價值,發現10是大於6的,顯然我們裝B;揹包容量3kg的時候,顯然裝完B,還剩1kg,因此可以一起裝就是價值16
第三行的1 2 3列資料同理,C裝不下但是可以裝A和B;當揹包4kg時,可以裝下C,但是C的價值才12,而且裝完C之後沒有容量了,然而我們不裝C的時候 價值都是16了,顯然我們不裝C;當揹包5kg的時候,這個時候可以裝C,價值12,裝完還剩1kg,前面1kg的時候可以裝A,因此12+6=18的價值。
這個表格最後的資料就是我們所需要的最優結果。
能裝的時候 每次和上面的比較,大我就裝,否則就不裝。這個過程和先裝誰沒有關係,比如下面先裝C再裝B最後A,按照上面的邏輯一樣的可以得到這個結果
1kg |
2kg |
3kg |
4kg |
5kg |
|
加入物品C |
0 |
0 |
0 |
12 |
12 |
加入物品B |
0 |
10 |
10 |
12 |
12 |
加入物品A |
6 |
10 |
16 |
16 |
18 |
上面這個推導過程總結起來就是------狀態轉移方程
上面的這個表的資料是不是就是一個二維陣列,可以得到如下公式
(注意:為了方便程式碼實現,陣列下標均從1開始)
Math.max(money[i - 1] + freeMoney , notHaveCurrent)
freeMoney = result[i - 1][currentW - weight[i - 1]];
notHaveCurrent = result[i - 1][currentW];
int money[] ={6,10,12}
int weight[] = {1,2,4};
result[][] 結果二維陣列
currentW: 當前揹包容量
freeMoney : 裝完當前物品,餘下空間的價值
notHaveCurrent: 不要加入物品的價值,也就是前面一次的價值
money[i - 1]:當前的物品的價值
下面就用程式碼來實現這個邏輯:
public static int dynamicProgramming() { int[] money ={10,6,12}; int[] weight = {2,1,4}; int w = 5; //包容量 int count = 3; int result[][] = new int[count+1][w+1]; //容量是從1開始,都擴大一位防止越界,放置物品也從一開始,因為第一次也要取前一次的資料,保證前一次有預設資料 for (int i = 1; i <= count; i++) { //依次裝物品 for (int currentW = 1; currentW <= w; currentW++) { //揹包容量依次增加 if (weight[i - 1] <= currentW) { //當前物品的容量小於當前揹包重量表示可以裝進去 int freeMoney = result[i - 1][currentW - weight[i - 1]]; //裝完當前物品,餘下空間的價值 int notHaveCurrent = result[i - 1][currentW]; //不要當前物品的價值,也就是前面一次的價值 result[i][currentW] = Math.max(money[i - 1] + freeMoney, notHaveCurrent); } else { //不能裝當前 取前一次 result[i][currentW] = result[i - 1][currentW]; } } } return result[count][w]; }
在動態規劃中最重要的就是找到狀態轉移方程。可以藉助狀態轉移表,來找到狀態轉移方程,比如上面的表格,找到這個方程之後,基本上就可以用程式碼來實現了。
動態規劃是每次都會把當前情況下的最優解計算出來,層層遞推,下一層的最優解都是基於它上一次結果存下來的,所以最後一個結果就一定是最優解。而貪心是指關心區域性,不管後面,上面會議,第一個會議決定了,後面其實也就決定了。