一、问题描述:
有一个需要使用每个资源的n个活动组成的集合S= {a,b,c,d... },资源每次只能由一个活动使用。每个活动都有一个开始时间和结束时间,且 0<= s < f <z 。一旦被选择后,活动a就占据半开时间区间[s,f]。如果[s,f]和[s,f]互不重叠,则称两个活动是兼容的。z假设是一个无穷大的常数。该问题就是要找出一个由互相兼容的活动组成的最大子集。
二、图像化描述:
感觉上面的描述形式化有一些复杂,我下面给出一个图像化的傻瓜式问题描述。
如上图所示,我们可以安排a,c,f,g四个活动,因为这几个活动不冲突。也可以安排a,e,g三个活动。那么活动选择问题就是选择能够安排最多活动的算法。当然最多的活动可能不止一个。
三、贪心算法:
a. 定理:假设所有活动按照结束时间排序。那么当前可选活动中,最早结束的活动一定存在于某个最优解中。
b. 定理证明:我们假如已经存在一个最大活动序列,且总序列最早结束的活动不在该序列中。而该最大活动序列中的最早结束的活动可以用总序列的最早结束的活动来替换。
因此我们采用循环不变式的思想,可以证明根据这个算法选择可以得到的一定是最优解。
c. 贪心算法正确性证明:
假如a,b,c,d....已经按照结束时间排序。
那么根据上面的定理,a一定是最优解中的一个活动。那么其余的活动一定存在b,c,d....的最优解中。而因为a已经存在于最优解的序列中,那么和起始时间小于a的结束时间的均不能符合要求。假设i是除了a第一个满足的活动,因此最优解的剩余序列一定只能从i这个活动开始的最优解中。我们选择i,因为i一定存在于后续的最优解中。反复可以保证得到的一定是最优解。
四、实现算法:
#include <iostream>
using namespace std;
//贪心算法之活动选择问题 《算法导论》 第222页问题
//活动结构体
struct activity
{
//活动开始时间
int start;
//活动结束时间
int finish;
};
//采用递归算法
void re(activity value[],int i,int start)
{
if(i<=10)
{
//一直找到符合条件的活动
while(value[i].start<start)
{
i++;
}
cout<<"活动序号:"<<i+1<<endl;
start=value[i].finish;
i=i+1;
//递归处理下一个活动
re(value,i,start);
}
}
//采用迭代算法
void wh(activity value[])
{
cout<<"活动序号:"<<1<<endl;
int i=1;
int start=value[0].finish;
while(i<=10)
{
//一直找到符合条件的活动
if(value[i].start<start)i++;
//处理后,继续
else
{
cout<<"活动序号:"<<i+1<<endl;
start=value[i].finish;
}
}
}
int main()
{
//初始化活动
activity act[11]={
{1,4},{3,5},{0,6},{5,7},
{3,8},{5,9},{6,10},{8,11},
{8,12},{2,13},{12,14}
};
re(act,0,0);
cout<<"---------------"<<endl;
wh(act);
return 0;
}
五、输出:
六、总结
从本质来说,贪心算法效率高的原因在于每步只做一次选择,而动态规划中,每步需要从若干个备选方案中选择最优的一个解。因此贪心算法的贪心策略显得非常重要,因为要保证算法的每一步选择,都将可以确定出正确解。否则,贪心算法就没有意义了。
从这个方面来说,贪心算法的使用范围并不是很广,只能使用于一些特定的条件。
文档下载:http://pan.baidu.com/share/link?shareid=1332093411&uk=2903070410
文章来自IT部落格:www.itbuluoge.com