所謂貪心算法是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的局部最優解。
貪心算法不是對所有問題都能得到整體最優解,但對範圍相當廣泛的許多問題他能產生整體最優解或者是整體最優解的近似解。
貪心算法的基本思路如下:
1 活動安排問題
活動安排問題就是要在所給的活動集合中選出最大的相容活動子集合,是可以用貪心算法有效求解的很好例子。該問題要求高效地安排一系列爭用某一公共資源的活動。貪心算法提供了一個簡單、漂亮的方法使得儘可能多的活動能兼容地使用公共資源。
一個由需要使用某一資源的n個活動組成的集合S = {1, 2, ... , n},該資源一次只能被一個活動佔用。每個活動i有個開始時間s[i]和結束時間f[i],且s[i] <= f[i]。一旦被選擇,活動i就佔據半開時間區間[s[i], f[i])。如果[s[i], f[j])與[s[i], f[j])互不重疊,則稱活動i和j是兼容的。活動安排問題就是要選擇一個由互相兼容的問題組成的最大集合。
ActivitySelectorMain.c
void GreedyActivitySelector(int n, int *s, int *f, int *A);
void PrintActivity(int n, int *s, int *f, int *A);
/*
* n:活動個數
* s:活動開始時間
* f:活動結束時間
* 假設輸入的活動按結束時間遞增序排列:f[1] <= f[2] <= ... <= f[n]
* A:記錄所選擇的集合
*/
void GreedyActivitySelector(int n, int *s, int *f, int *A)
{
int i, j;
A[0] = 1;
j = 0;
for(i = 1; i < n; i++)
{
if(s[i] >= f[j])
{
A[i] = 1;
j = i;
}
}
}
void PrintActivity(int n, int *s, int *f, int *A)
{
int i;
for(i = 0; i < n; i++)
{
if(A[i])
printf(" %d %d-->%d", i, s[i], f[i]);
}
}
int main(int argc, char **argv)
{
int s[] ={1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};
int f[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
int A[11] ={0, };
int n;
n = 11;
GreedyActivitySelector(n, s, f, A);
PrintActivity(n, s, f, A);
return 0;
}
由於輸入的活動以其完成時間的非減序排列,所以算法greedySelector每次總是選擇具有最早完成時間的相容活動加入集合A中。直觀上,按這種方法選擇相容活動爲未安排活動留下儘可能多的時間。也就是說,該算法的貪心選擇的意義是使剩餘的可安排時間段極大化,以便安排儘可能多的相容活動。
此算法的效率極高。當輸入的活動已按結束時間的非減序排列,算法只需O(n)的時間安排n個活動,使最多的活動能相容地使用公共資源。如果所給出的活動未按非減序排列,可以用O(nlogn)的時間重排。
例:設待安排的11個活動的開始時間和結束時間按結束時間的非減序排列如下:
2.把求解的問題分成若干個子問題。
3.對每一子問題求解,得到子問題的局部最優解。
4.把子問題的解局部最優解合成原來解問題的一個解。
實現該算法的過程:
while 能朝給定總目標前進一步 do
求出可行解的一個解元素;
由所有解元素組合成問題的一個可行解;
全不可以使用,貪心策略一旦經過證明成立後,它就是一種高效的算法。
貪心算法還是很常見的算法之一,這是由於它簡單易行,構造貪心策略不是很困難。
可惜的是,它需要證明後才能真正運用到題目的算法中。
一般來說,貪心算法的證明圍繞着:整個問題的最優解一定由在貪心策略中存在的子問題的最優解得來的。
貪心算法當然也有正確的時候。求最小生成樹的Prim算法和Kruskal算法都是漂亮的貪心算法。
所以需要說明的是,貪心算法可以與隨機化算法一起使用,具體的例子就不再多舉了。(因爲這一類算法普及性不高,而且技術含量是非常高的,需要通過一些反例確定隨機的對象是什麼,隨機程度如何,但也是不能保證完全正確,只能是極大的機率正確)