所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
贪心算法的基本思路如下:
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
#include
<
stdio.h
>

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个活动的开始时间和结束时间按结束时间的非减序排列如下:





/*
* 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 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;
}
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。
实现该算法的过程:
while 能朝给定总目标前进一步 do
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。
贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。
可惜的是,它需要证明后才能真正运用到题目的算法中。
一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。
贪心算法当然也有正确的时候。求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。
所以需要说明的是,贪心算法可以与随机化算法一起使用,具体的例子就不再多举了。(因为这一类算法普及性不高,而且技术含量是非常高的,需要通过一些反例确定随机的对象是什么,随机程度如何,但也是不能保证完全正确,只能是极大的几率正确)