貪心選擇算法為算法分析中一種常用算法,通過一系列的選擇來得到一個問題的解。它所作的每一個選擇都是當前狀態下某種意義的最好選擇,即貪心選擇。希望通過每次所作的貪心選擇導致最終結果是問題的一個最優解。這種啟發式的策略並不總能奏效,然而在許多情況下確能達到預期的目的。對於可利用貪心算法解決的問題需要同時滿足:最優子結構性質和貪心選擇性質。
1.貪心選擇性質
所謂貪心選擇性質是指所求問題的整體最優解可以通過一系列局部最優的選擇,即貪心選擇來達到。這是貪心算法可行的第一個基本要素,也是貪心算法與動態規划算法的主要區別。在動態規划算法中,每步所作的選擇往往依賴於相關子問題的解。因而只有在解出相關子問題后,才能作出選擇。而在貪心算法中,僅在當前狀態下作出最好選擇,即局部最優選擇。然后再去解作出這個選擇后產生的相應的子問題。貪心算法所作的貪心選擇可以依賴於以往所作過的選擇,但決不依賴於將來所作的選擇,也不依賴於子問題的解。正是由於這種差別,動態規划算法通常以自底向上的方式解各子問題,而貪心算法則通常以自頂向下的方式進行,以迭代的方式作出相繼的貪心選擇,每作一次貪心選擇就將所求問題簡化為一個規模更小的子問題。
對於一個具體問題,要確定它是否具有貪心選擇性質,我們必須證明每一步所作的貪心選擇最終導致問題的一個整體最優解。通常可以用我們在證明活動安排問題的貪心選擇性質時所采用的方法來證明。首先考察問題的一個整體最優解,並證明可修改這個最優解,使其以貪心選擇開始。而且作了貪心選擇后,原問題簡化為一個規模更小的類似子問題。然后,用數學歸納法證明,通過每一步作貪心選擇,最終可得到問題的一個整體最優解。其中,證明貪心選擇后的問題簡化為規模更小的類似子問題的關鍵在於利用該問題的最優子結構性質。
2.最優子結構性質
當一個問題的最優解包含着它的子問題的最優解時,稱此問題具有最優子結構性質。問題所具有的這個性質是該問題可用動態規划算法或貪心算法求解的一個關鍵特征。在活動安排問題中,其最優子結構性質表現為:若a是對於正的活動安排問題包含活動1的一個最優解,則相容活動集合a’=a—{1}是對於e’={i∈e:si≥f1}的活動安排問題的一個最優解。
3.貪心算法與動態規划算法的差異
貪心算法和動態規划算法都要求問題具有最優子結構性質,這是兩類算法的一個共同點。但是,對於一個具有最優子結構的問題應該選用貪心算法還是動態規划算法來求解?是不是能用動態規划算法求解的問題也能用貪心算法來求解?下面我們來研究兩個經典的組合優化問題,並以此來說明貪心算法與動態規划算法的主要差別。
最優裝載問題問題描述:
有一批集裝箱要裝上一艘載重量為C的輪船,其中集裝箱i的重量為Wi。要求在裝載體積不受限制的情況下,將盡可能多的集裝箱裝上輪船。
算法分析:采用重量輕者先裝的貪心選擇策略,可產生最優裝載問題的最優解。
代碼實現:
1: class LContainer implements Comparable {
2: private int id; //編號
3: private double weight; //重量
4: private boolean loadFlag = false; //是否裝船標識
5:
6: public LContainer(int id,double weight) {
7: this.id = id;
8: this.weight = weight;
9: }
10:
11: /**
12: * 因為要對對象進行排序,所以要實現java.util.Comparator接口的compareTo(T obj)方法,在該方法中自定義排序算法。
13: */
14: public int compareTo(Object obj) {
15: double ww = ((LContainer) obj).getWeight();
16: if (this.weight > ww) {
17: // 當方法返回 1 時 :把參數對象往前放。(前o.weight , 后this.weight)
18: return 1;
19: } else if (this.weight < ww) {
20: // 當方法返回 -1 時 :把參數對象往后放。(前this.weight , 后o.weight)
21: return -1;
22: } else {
23: return 0;
24: }
25: }
26:
27: public void setId(int id) {
28: this.id = id;
29: }
30:
31: public int getId() {
32: return id;
33: }
34:
35: public double getWeight() {
36: return weight;
37: }
38:
39: public void setWeight(double weight) {
40: this.weight = weight;
41: }
42:
43: public void setLoadFlag(boolean loadFlag) {
44: this.loadFlag = loadFlag;
45: }
46:
47: public boolean getLoadFlag() {
48: return loadFlag;
49: }
50: }
51:
52: public class OptLoading {
53: static int count;
54: static LContainer[] container ;
55:
56: static int loading(double[] weight, double c) {
57: container = new LContainer[weight.length] ;
58: for(int i=0;i<weight.length;i++)
59: {
60: container[i] = new LContainer(i,weight[i]);
61: }
62:
63: Arrays.sort(container); // 實現數組排序
64:
65: for (int i = 0; i < container.length&&container[i].getWeight()<=c; i++)
66: {
67: container[i].setLoadFlag(true);
68: count++;
69: c -= container[i].getWeight();
70: }
71: return count;
72: }
73:
74: public static void main(String[] args) {
75: double c = 1000;
76: double[] weight = { 800, 1000, 1001, 200, 100, 400, 600, 50 };
77: int result = loading(weight,c);
78: System.out.println("最優裝載集裝箱的數量:\n"+result);
79: System.out.println("依次裝載的順序編號如下:");
80: for (int i = 0; i < container.length; i++)
81: {
82: if (container[i].getLoadFlag())
83: {
84: System.out.print(container[i].getId() + " ");
85: }
86: }
87: }
88:
89: }