1. 程式人生 > >五大常用算法之三貪心算法

五大常用算法之三貪心算法

重疊 gets 一個 tar mon 沖突 array 最小值 貪心算法

貪心算法

貪心算法簡介:

  貪心算法是指:在每一步求解的步驟中,它要求“貪婪”的選擇最佳操作,並希望通過一系列的最優選擇,能夠產生一個問題的(全局的)最優解。

  貪心算法每一步必須滿足一下條件:

  1、可行的:即它必須滿足問題的約束。

  2、局部最優:他是當前步驟中所有可行選擇中最佳的局部選擇。

  3、不可取消:即選擇一旦做出,在算法的後面步驟就不可改變了。

貪心算法案例:

1.活動選擇問題
  這是《算法導論》上的例子,也是一個非常經典的問題。有n個需要在同一天使用同一個教室的活動a1,a2,…,an,教室同一時刻只能由一個活動使用。每個活動ai都有一個開始時間si和結束時間fi 。一旦被選擇後,活動ai就占據半開時間區間[si,fi)。如果[si,fi]和[sj,fj]互不重疊,ai和aj兩個活動就可以被安排在這一天。該問題就是要安排這些活動使得盡量多的活動能不沖突的舉行。例如下圖所示的活動集合S,其中各項活動按照結束時間單調遞增排序。

技術分享

  用貪心法的話思想很簡單:活動越早結束,剩余的時間是不是越多?那我就早最早結束的那個活動,找到後在剩下的活動中再找最早結束的不就得了?

雖然貪心算法的思想簡單,但是貪心法不保證能得到問題的最優解,如果得不到最優解,那就不是我們想要的東西了,所以我們現在要證明的是在這個問題中,用貪心法能得到最優解。

java代碼實現:

  1 public class ActiveTime {
  2     public static void main(String[] args) {
  3         //創建活動並添加到集合中
  4         Active act1 = new Active(1, 4);
5 Active act2 = new Active(3, 5); 6 Active act3 = new Active(0, 6); 7 Active act4 = new Active(5, 7); 8 Active act5 = new Active(3, 8); 9 Active act6 = new Active(5, 9); 10 Active act7 = new Active(6, 10); 11 Active act8 = new Active(8, 11);
12 Active act9 = new Active(8, 12); 13 Active act10 = new Active(2, 13); 14 Active act11 = new Active(12, 14); 15 List<Active> actives = new ArrayList<Active>(); 16 actives.add(act1); 17 actives.add(act2); 18 actives.add(act3); 19 actives.add(act4); 20 actives.add(act5); 21 actives.add(act6); 22 actives.add(act7); 23 actives.add(act8); 24 actives.add(act9); 25 actives.add(act10); 26 actives.add(act11); 27 28 List<Active> bestActives = getBestActives(actives, 0, 16); 29 for (int i = 0; i < bestActives.size(); i++) { 30 System.out.println(bestActives.get(i)); 31 } 32 } 33 34 35 /** 36 * 37 * @param actives 38 * 活動集合 39 * @param startTime 40 * 教室的開始使用時間 41 * @param endTime 42 * 教室的結束使用時間 43 * @return 44 */ 45 public static List<Active> getBestActives(List<Active> actives, int startTime, int endTime) { 46 //最佳活動選擇集合 47 List<Active> bestActives = new ArrayList<Active>(); 48 //將活動按照最早結束時間排序 49 actives.sort(null); 50 //nowTime 用來記錄上次活動結束時間 51 int nowTime = startTime; 52 /** 53 * 因為我們已經按照最早結束時間排序,那麽只要活動在時間範圍內 54 * actives.get(1)就應當是第一個活動的結束時間. 55 * 則我們記錄第一次活動結束的時間,在結合剩下的活動中, 56 * 選取開始時間大於nowTime且結束時間又在範圍內的活動,則為第二次活動時間, 57 * 知道選出所有活動 58 */ 59 for (int i = 0; i < actives.size(); i++) { 60 Active act = actives.get(i); 61 if(act.getStartTime()>=nowTime&&act.getEndTime()<=endTime){ 62 bestActives.add(act); 63 nowTime = act.getEndTime(); 64 } 65 } 66 return bestActives; 67 } 68 } 69 70 /** 71 * 活動類 72 * @CreatTime 下午9:45:37 73 * 74 */ 75 class Active implements Comparable<Active>{ 76 private int startTime;//活動開始時間 77 private int endTime;//活動結束時間 78 79 public Active(int startTime, int endTime) { 80 super(); 81 this.startTime = startTime; 82 this.endTime = endTime; 83 } 84 85 public int getStartTime() { 86 return startTime; 87 } 88 89 public void setStartTime(int startTime) { 90 this.startTime = startTime; 91 } 92 93 public int getEndTime() { 94 return endTime; 95 } 96 97 public void setEndTime(int endTime) { 98 this.endTime = endTime; 99 } 100 101 @Override 102 public String toString() { 103 return "Active [startTime=" + startTime + ", endTime=" + endTime + "]"; 104 } 105 106 //活動排序時按照結束時間升序 107 @Override 108 public int compareTo(Active o) { 109 if(this.endTime>o.getEndTime()){ 110 return 1; 111 }else if(this.endTime == o.endTime){ 112 return 0; 113 }else{ 114 return -1; 115 } 116 } 117 118 119 }

運行結果:

Active [startTime=1, endTime=4]
Active [startTime=5, endTime=7]
Active [startTime=8, endTime=11]
Active [startTime=12, endTime=14]

可以看出,求得的結果正好是最優解。


2.錢幣找零問題
這個問題在我們的日常生活中就更加普遍了。假設1元、2元、5元、10元、20元、50元、100元的紙幣分別有c0, c1, c2, c3, c4, c5, c6張。現在要用這些錢來支付K元,至少要用多少張紙幣?用貪心算法的思想,很顯然,每一步盡可能用面值大的紙幣即可。在日常生活中我們自然而然也是這麽做的。在程序中已經事先將Value按照從小到大的順序排好。

java代碼實現:

 1 package GreedyAlgorithm;
 2 
 3 public class CoinChange {
 4     public static void main(String[] args) {
 5         //人民幣面值集合
 6         int[] values = { 1, 2, 5, 10, 20, 50, 100 };
 7         //各種面值對應數量集合
 8         int[] counts = { 3, 1, 2, 1, 1, 3, 5 };
 9         //求442元人民幣需各種面值多少張
10         int[] num = change(442, values, counts);
11         print(num, values);
12     }
13 
14     public static int[] change(int money, int[] values, int[] counts) {
15         //用來記錄需要的各種面值張數
16         int[] result = new int[values.length];
17 
18         for (int i = values.length - 1; i >= 0; i--) {
19             int num = 0;
20             //需要最大面值人民幣張數
21             int c = min(money / values[i], counts[i]);
22             //剩下錢數
23             money = money - c * values[i];
24             //將需要最大面值人民幣張數存入數組
25             num += c;
26             result[i] = num;
27         }
28         return result;
29     }
30 
31     /**
32      * 返回最小值
33      */
34     private static int min(int i, int j) {
35         return i > j ? j : i;
36     }
37     
38     private static void print(int[] num, int[] values) {
39         for (int i = 0; i < values.length; i++) {
40             if (num[i] != 0) {
41                 System.out.println("需要面額為" + values[i] + "的人民幣" + num[i] + "張");
42             }
43         }
44     }
45 }

運行結果:

需要面額為2的人民幣1張
需要面額為5的人民幣2張
需要面額為10的人民幣1張
需要面額為20的人民幣1張
需要面額為100的人民幣4張

可以看出,求出的結果也剛好等於442元。正好為最優解。但是,當面額及數量為下種特殊情況時,貪心算法就無法給出最優解。

//人民幣面值集合
 6         int[] values = { 3, 5, 10, 20, 50, 100 };
 7         //各種面值對應數量集合
 8         int[] counts = { 3, 2, 1, 1, 3, 5 };
需要求得money = 416元

運行結果如下:

需要面額為5的人民幣1張
需要面額為10的人民幣1張
需要面額為100的人民幣4張

於是我們可以看出,有些情況,貪心算法確實可以給出最優解,然而,還有一些問題並不是這種情況。對於這種情況,我們關心的是近似解,或者只能滿足於近似解,貪心算法也是有價值的。

五大常用算法之三貪心算法