问题:
有一个需要使用每个资源的n个活动组成的集合S= {a1,a2,···,an },资源每次只能由一个活动使用。每个活动a都有一个开始时间和结束时间,且 0<= s < f 。一旦被选择后,活动a就占据半开时间区间[s,f]。如果[s,f]和[s,f]互不重叠,则称两个活动是兼容的。该问题就是要找出一个由互相兼容的活动组成的最大子集。
定义子集合Sij = { ak S : f i <= sk < f k <= s j}, 即每个活动都在ai结束之后开始,在aj开始之前结束,亦即Sij包含了所有和ai和aj兼容的活动。
假设S中的活动已按照结束时间递增的顺序排列,则Sij具有如下的性质:
1.当i <= j时,Sij 为空,
2.假设ak属于Sij,那么ak将把Sij分解成两个子问题,Sij包含的活动集合就等于Sik中的活动+ak+Skj中的活动。从这里就可以看出Sij的最优子结构性质:Sij的最优解包含了子问题Sik和Skj的最优解。假设Sij的最大兼容活动子集为Aij,那么有Aij = Aik U ak U Akj。整个活动选择问题的最优解也是S0,n+1的解。
假设c[i,j]为Sij中最大兼容子集中的活动数。则有如下递归式:
C[i,j] = 0 如果 Sij 为空
C[i,j] = max{c[i,k] + c[k,j] +1 } i < k < j & ak Sij 如果Sij 不为空
根据这个递归式,可以得到一个动态规划解法。
// greedy_algorithm.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
#include<queue>
using namespace std;
#define NofActivity 11 //有效的活动数
int c[NofActivity + 2][NofActivity + 2]; //c[i][j]存放第i个结束后第j个开始前兼容的活动个数
int reme[NofActivity + 2][NofActivity + 2]; //记录哪几个活动满足条件
//活动的结构/
struct Activity
{
int num; //活动的标号
int start;
int finish;
};
//活动已经按结束时间的早晚排好序
//初始化活动结构数组,注意添加了了头和尾,活动有效数据只有11组,添加到13组的意思是找第0组结束之后,第12组开始之前可以兼容的那些活动组合
Activity Act[NofActivity+2] = { {0,0,0},{ 1,1,4 },{2,3,5 },{3, 0,6 },{4, 5,7 },{5, 3,9 },{6, 5,9 },{7, 6,10 },{8, 8,11 },{9, 8,12 },{10, 2,14 },{11, 12,16 }, {12,24,24}};
///用队列来存储符合条件的活动,递归版本//
queue<Activity> select;
void Recursive_activity_selector(Activity* Act, int k, int n)
{
//查找k结束之后第一个结束的活动
int m = k + 1;
while (m <= n&&Act[m].start < Act[k].finish)
m++;
//如果找到就把它入队,递归调用查找下一个
if (m <= n)
{
select.push(Act[m]);
Recursive_activity_selector(Act, m, n);
}
}
///活动选择的迭代版本/
void Greedy_activity_selector(Activity* Act)
{
//先把第一个入队
int n = NofActivity;
while (!select.empty())select.pop();
select.push(Act[1]);
//循环查找下一个满足条件的活动入队
int k = 1;
for (int i = 2; i <= n; i++)
{
if (Act[i].start > Act[k].finish) {
select.push(Act[i]);
k = i;
}
}
}
/活动选择的动态规划版本//
void activity_selector(Activity* Act)
{
//初始化
for (int i = 0; i <= NofActivity+1; i++)
{
for (int j = 0; j <= NofActivity + 1; j++)
{
c[i][j] = 0;
reme[i][j] = 0;
}
}
//从长度为2的开始查找
for(int l=2;l<=NofActivity+2;l++)
for (int i = 0; i <= NofActivity-l+3; i++) //注意开始要从虚拟的活动0开始,到虚拟的活动NofActivity+1结束,这是由c[i][j]的定义所决定的
{
int j = i + l - 1;
bool flag=false; //标志i,j之间是否含有兼容的活动
for (int k = i + 1; k < j; k++)
{
if (Act[k].start > Act[i].finish&&Act[k].finish < Act[j].start) //c[i][j]定义的条件
{
if (c[i][j] < c[i][k] + c[k][j] + 1)
{
c[i][j] = c[i][k] + c[k][j] + 1;
//注意这里reme[i][j]的赋值,因为c[i][j] < c[i][k] + c[k][j] + 1,是小于号而不是小于等于,所以reme[i][j]会记录第一个满足条件的k,如reme[0][12]=1;
reme[i][j] = k;
}
flag = true; //有置1
}
}
if (!flag)c[i][j] = 0;
}
//打印出c[i][j];
for (int i = 0; i <= NofActivity + 1; i++) {
for (int j = 0; j <= NofActivity + 1; j++)
cout << c[i][j] << ' ';
cout << endl;
}
}
//打印所选择的活动,act = reme[act][j]是由上诉对reme[i][j]的赋值规律所决定
void printSelect(int i, int j)
{
int act = reme[i][j];
while (act)
{
cout << act << '\t';
act = reme[act][j];
}
}
int main()
{
//Recursive_activity_selector(Act, 0, NofActivity);
/*
Greedy_activity_selector(Act);
while (!select.empty())
{
cout << select.front().num<< '\t';
select.pop();
}
*/
activity_selector(Act);
printSelect(0, 12);
while (1);
return 0;
}