活动选择问题:就是给定一组活动的开始时间和结束时间,然后他们都需要使用到一个资源,这个资源每次只有一个活动可以用,要求求出一个最大的相互兼容的活动子集。
首先定义了一个集合Sij = {ak∈ S :fi ≤ sk < fk ≤ sj} , 其中S就是所有活动的集合,fi是活动ai的完成时间si是活动ai的开始时间。
这道题如果是用DP来解的话,就需要找到最优解的递归方程(其中c[i,j]是集合Sij中的最大兼容活动的个数):
然后根据DP自底向上的解答方法进行求解,过程和矩阵连乘的差不多。
然而,根据下面的定理,我们可以用贪心的策略来解答这道题目:
这样子就将DP的两个子问题变成了只有一个子问题,而且由于将结束时间已升序排序,所以要求出fm也是很简单的。
接下来的工资就变得很简单了,看下面的伪代码和图片即可
RECURSIVE-ACTIVITY-SELECTOR(s, f, i, j)
1 m ← i + 1
2 while m < j and sm < fi ▹ Find the first activity in Sij.
3 do m ← m + 1
4 if m < j
5 then return {am} ∪ RECURSIVE-ACTIVITY-SELECTOR(s, f, m, j)
6 else return Ø
下面的图片是利用上面的递归算法求解的过程
下面是代码,将递归改为迭代来实现。
#include <iostream>
#include <algorithm>
using namespace std;
struct Activity
{
int s; //start time
int f; //finish time
};
bool cmp(Activity A,Activity B)
{
return A.f<B.f;
}
int main()
{
Activity act[100];
Activity temp[100];
int n;
cout<<"一共有多少个活动:";
cin>>n;
cout<<"输入每个活动的开始时间和结束时间:"<<endl;
for(int i=0;i<n;i++)
cin>>act[i].s>>act[i].f;
sort(act,act+n,cmp);
int k=0;
temp[k]=act[0];
for(i=1;i<n;i++)
{
while(act[i].s >= temp[k].f)
{
k++;
temp[k]=act[i];
}
}
cout<<"最后选择的兼容的活动的开始时间和结束时间如下:"<<endl;
for(i=0;i<=k;i++)
cout<<temp[i].s<<" "<<temp[i].f<<endl;
return 0;
}
运行结果:
运用贪心解决的问题具有两个特点:贪心选择性质和最优子结构
贪心选择性质:
一个全局最优解可以通过局部最优选择来达到,就是说,当我们做选择的时候,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。这个和DP是不一样的,在DP中,每一步都要做出选择,但是这些选择依赖于子问题的解。但是在贪心算法中,所做的当前选择可能会依赖于已经做出的所有选择,但不依赖于有待做出的选择或者是子问题的解,所以DP是自底向上的,而贪心是自顶向下的。
最优子结构:
对于一个问题来说,如果它的一个最优解包含了其子问题的最优解,则说该问题具有最优子结构。
在贪心中,假设一个贪心选择后得到一个子问题,真正要做的是证明这个子问题的最优解与所做的贪心选择合并后,的确可以得到原问题的一个最优解。