本文轉自: https://blog.csdn.net/a925907195/article/details/41314549,如涉及版權請聯繫我
一、定義
什麼是貪心算法呢?所謂貪心算法是指,在對問題求解時,總是做出在當前看來最好的選擇。也就是說,不從整體最優解出發來考慮,它所做出的僅是在某種意義上的局部最優解。
貪心算法不是對所有問題都能得到整體最優解,但對範圍相當廣泛的許多問題都能產生整體最優解或整體最優解的近似解。
貪心算法的基本思路如下:
1.建立數學模型來描述問題。
2.把求解的問題分成若干個子問題。
3.對每個子問題求解,得到每個子問題的局部最優解。
4.把每個子問題的局部最優解合成爲原來問題的一個解。
實現該算法的過程:
從問題的某一初始狀態出發;
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種貪心策略,都無法成立,即無法被證明,解釋如下:
(1)貪心策略:選取價值最大者。反例:
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,則答案錯誤。
值得注意的是,貪心算法並不是完全不可以使用,貪心策略一旦經過證明成立後,它就是一種高效的算法。比如,求最小生成樹的Prim算法和Kruskal算法都是漂亮的貪心算法。
[均分紙牌]有N堆紙牌,編號分別爲1,2,…,n。每堆上有若干張,但紙牌總數必爲n的倍數.可以在任一堆上取若干張紙牌,然後移動。移牌的規則爲:在編號爲1上取的紙牌,只能移到編號爲2的堆上;在編號爲n的堆上取的紙牌,只能移到編號爲n-1的堆上;其他堆上取的紙牌,可以移到相鄰左邊或右邊的堆上。現在要求找出一種移動方法,用最少的移動次數使每堆上紙牌數都一樣多。例如:n=4,4堆紙牌分別爲:① 9 ② 8 ③ 17 ④ 6 移動三次可以達到目的:從③取4張牌放到④ 再從③區3張放到②然後從②去1張放到①。
輸入輸出樣例:4
9 8 17 6
屏幕顯示:3
算法分析:設a[i]爲第I堆紙牌的張數(0<=I<=n),v爲均分後每堆紙牌的張數,s爲最小移動次數。
我們用貪心算法,按照從左到右的順序移動紙牌。如第I堆的紙牌數不等於平均值,則移動一次(即s加1),分兩種情況移動:
1.若a[i]>v,則將a[i]-v張從第I堆移動到第I+1堆;
2.若a[i]<v,則將v-a[i]張從第I+1堆移動到第I堆。
爲了設計的方便,我們把這兩種情況統一看作是將a[i]-v從第I堆移動到第I+1堆,移動後有a[i]=v; a[I+1]=a[I+1]+a[i]-v.
在從第I+1堆取出紙牌補充第I堆的過程中可能回出現第I+1堆的紙牌小於零的情況。
如n=3,三堆指派數爲1 2 27 ,這時v=10,爲了使第一堆爲10,要從第二堆移9張到第一堆,而第二堆只有2張可以移,這是不是意味着剛纔使用貪心法是錯誤的呢?
我們繼續按規則分析移牌過程,從第二堆移出9張到第一堆後,第一堆有10張,第二堆剩下-7張,在從第三堆移動17張到第二堆,剛好三堆紙牌都是10,最後結果是對的,我們在移動過程中,只是改變了移動的順序,而移動次數不便,因此此題使用貪心法可行的。
Java源程序:
public class Greedy {
public static void main(String[] args) {
int n = 0, avg =0, s = 0;
Scanner scanner = new Scanner(System.in);
ArrayList<Integer> array = new ArrayList<Integer>();
System.out.println("Please input the number of heaps:");
n = scanner.nextInt();
System.out.println("Please input heap number:");
for (int i = 0; i < n; i++) {
array.add(scanner.nextInt());
}
for(int i = 0; i < array.size(); i ++){
avg += array.get(i);
}
avg = avg/array.size();
System.out.println(array.size());
System.out.println(avg);
for(int i = 0; i < array.size()-1; i ++){
s++;
array.set(i+1, array.get(i+1)+array.get(i)-avg);
}
System.out.println("s:" + s);
}
}
利用貪心算法解題,需要解決兩個問題:
一是問題是否適合用貪心法求解。我們看一個找幣的例子,如果一個貨幣系統有三種幣值,面值分別爲一角、五分和一分,求最小找幣數時,可以用貪心法求解;如果將這三種幣值改爲一角一分、五分和一分,就不能使用貪心法求解。用貪心法解題很方便,但它的適用範圍很小,判斷一個問題是否適合用貪心法求解,目前還沒有一個通用的方法,在信息學競賽中,需要憑個人的經驗來判斷。
二是確定了可以用貪心算法之後,如何選擇一個貪心標準,才能保證得到問題的最優解。在選擇貪心標準時,我們要對所選的貪心標準進行驗證才能使用,不要被表面上看似正確的貪心標準所迷惑,如下面的例子。
[最大整數]設有n個正整數,將它們連接成一排,組成一個最大的多位整數。
例如:n=3時,3個整數13,312,343,連成的最大整數爲34331213。
又如:n=4時,4個整數7,13,4,246,連成的最大整數爲7424613。
輸入:n
N個數
輸出:連成的多位數
算法分析:此題很容易想到使用貪心法,在考試時有很多同學把整數按從大到小的順序連接起來,測試題目的例子也都符合,但最後測試的結果卻不全對。按這種標準,我們很容易找到反例:12,121應該組成12121而非12112,那麼是不是相互包含的時候就從小到大呢?也不一定,如12,123就是12312而非12123,這種情況就有很多種了。是不是此題不能用貪心法呢?
其實此題可以用貪心法來求解,只是剛纔的標準不對,正確的標準是:先把整數轉換成字符串,然後在比較a+b和b+a,如果a+b>=b+a,就把a排在b的前面,反之則把a排在b的後面。
java源程序:
public static void main(String[] args){
String str = "";
ArrayList<String> array = new ArrayList<String>();
Scanner in = new Scanner(System.in);
System.out.println("Please input the number of data:");
int n = in.nextInt();
System.out.println("Please input the data:");
while (n-- > 0) {
array.add(in.next());
}
for(int i = 0; i < array.size(); i ++)
for(int j = i + 1; j < array.size(); j ++){
if((array.get(i) + array.get(j)).compareTo(array.get(j) + array.get(i)) < 0){
String temp = array.get(i);
array.set(i, array.get(j));
array.set(j, temp);
}
}
for(int i = 0; i < array.size(); i ++){
str += array.get(i);
}
System.out.println("str=:"+str);
}
}
貪心算法所作的選擇可以依賴於以往所作過的選擇,但決不依賴於將來的選擇,也不依賴於子問題的解,因此貪心算法與其他算法相比具有一定的速度優勢。如果一個問題可以同時用幾種方法解決,貪心算法應該是最好的選擇之一。