贪心算法—活动选择问题

问题描述:
假定有一个n个活动的集合S={a1,a2,......an},这些活动使用同一个资源(如一个阶梯教室),而这个资源在某个时刻只能供一个活动使用。每个活动有一个开始时间si和结束时间fi,其中0<=si<=fi<∞。如果被选中,任务ai发生在半开区间[si,fi)期间。如果两个活动ai和aj满足其两个区间不重叠,则称它们是兼容的。在活动选择问题中,我们希望选出一个最大兼容活动集。
给出一个样例集合S:
i    1    2    3    4    5    6    7    8    9    10    11
si  1    3    0    5    3    5    6    8    8     2     12
fi   4    5    6    7    9    9   10  11  12   14    16
问题的考虑:
虽然该问题是一个经典的贪心算法的样例问题,但是考虑这件事还是可以从动态规划的角度去入手。
动态规划的角度:
动态规划即要找到活动选择问题的最优子结构,若用Sij表示在ai结束之后开始,且在aj开始之前结束的那些活动的集合。假定我们希望求Sij的一个最大的相互兼容的活动子集,进一步假定Aij就是这样一个子集,包含活动 ak。由于最优解包含活动ak,,我们得到两个子问题:寻找Sik中的兼容活动(即在ai结束之后开始且在ak开始之前结束的那些活动)以及寻找Skj中的兼容活动(在ak结束之后开始且在aj开始之前结束的那些活动)。令Aik=Aij∩Sik和Akj=Aij∩Skj,这样Aik包含Aij中那些在ak开始之前结束的活动,Akj包含Aij中那些在ak结束之后开始的活动。因此,我们有Aij = Aik∪{ak}∪Akj,而且Sij中最大兼容任务子集Aij包含|Aij|=|Aik|+|Akj|+1个活动。这一过程可以用剪切—粘贴法去证明,这里不再赘述。
这样刻画活动选择问题的最优子结构,就可以用动态规划方法来求解了,如果用c[i,j]表示集合Sij的最优解的大小,则可得递归式:
c[i,j]=c[i,k]+c[k,j]+1
当然,我们并不知道Sij的最优解包含活动ak,所以需要考察Sij中的所有活动,寻找哪个活动可以获得最优解,于是:
c[i,j]=max{c[i,k]+c[k,j]+1}(ak∈Sij)
接下来就可以用自带备忘机制的递归算法或者自底向上法去求解。但是在选择子问题的时候,我们并不是真不知道选择哪一个活动加入到最优解中去,下面就是贪心算法的说明。
贪心选择:
对于该问题,直观上我们应选择这样一个活动,选出它后剩下的资源能应能尽量多的被其他任务所用。现在考虑可选的活动,其中必然有一个最先结束。因此直觉告诉我们,应该选择S中最早结束的活动,因为它剩下的资源可供它之后尽量多的活动使用。换句话说,由于活动已经按照结束时间单调递增的顺序排序(如果没有的话,我们也可以用nlogn的时间去将它排序),贪心选择就是活动a1.
当做出贪心选择后,只剩下一个子问题求解:寻找在a1结束后开始的活动。(因为a1是最早结束的活动,因此要使与它兼容的活动都必须在a1结束之后开始)而且之前,我们已经证明活动选择问题具有最优子结构性质,那么原问题的最优解由活动a1及其子问题S1中的活动组成。
现在剩下最后一个大问题:我们的贪心选择——最早结束的活动——总是最优解的一部分吗?
答案是肯定的,给出这个定理: 考虑任意非空子问题Sk,令am是Sk中结束时间最早的活动,则am在Sk的某个最大兼容活动子集中。
因此该活动我们可以用贪心算法来解决,这里给出迭代贪心算法的代码,已在VS2015通过该样例测试:
#include
#include
using namespace std;

typedef struct array1 {                 //用来记录和输出活动
	int start;
	int finish;
};

void greedy_activity_selector(int *s, int *f) {
	int n = sizeof(s) / sizeof(s[0]);
	struct array1 a[11];
	a[0].start = s[0];                 //由排序知必然可以包含第一个结束的活动,因此选择活动a1(即a[0])
	a[0].finish = f[0];
	int j;
	for (j = 1; j <= 10;++j) {         //将结构体数组中除a[0]外每个元素赋值为0
		a[j].start = 0;
		a[j].finish = 0;
	}
	int k = 1, m = 2; 
	for (m; m <= 10;m++)                //贪心选择
		if (s[m] >= f[k]) {
			a[m].start = s[m];
			a[m].finish = f[m];
			k = m;
		}
	int i;
	cout << "活动最大兼容集合为:" << endl;
	for (i = 0; i <= 10; ++i)
		if (a[i].start != 0) {
			cout <<'a'<< i+1 << ':' << a[i].start <<' '<< "to" <<' '<< a[i].finish << endl;
		}
		else
			continue;
}

int main() {
	int s[11] = { 1,3,0,5,3,5,6,8,8,2,12 };
	int f[11] = { 4,5,6,7,9,9,10,11,12,14,16 };
	greedy_activity_selector(s, f);
	system("pause");
	return 0;
}
运行结果:
贪心算法—活动选择问题_第1张图片












你可能感兴趣的:(贪心算法,动态规划)