活动选择的贪心算法与动态规划
问题:
有一个需要使用每个资源的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; }