活动选择的贪心算法与动态规划

问题:

      有一个需要使用每个资源的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;
}

  

posted @ 2017-04-07 21:41  lineaar  阅读(577)  评论(0编辑  收藏  举报