活动选择问题:
Given a set S of n activities with and start time, Si and fi, finish time of an ith activity. Find the maximum size set of mutually compatible activities.
兼容活动概念:
Activities i and j are compatible if the half-open internal [si, fi) and [sj, fj) do not overlap, that is, i and j are compatible ifsi ≥ fj and sj ≥ fi
算法导论上提供了两种方法,一种是DP方法, 另一种是贪心算法,同时网上还提供了另一种DP的方法。下面对三种方法进行讲解:
1,算法导论上的DP方法:
先对每个活动ai按照活动结束时间fi从小到大排序,之后按照递归方程进行动态规划
/*动态规划解法- 算法导论上的思路 S(i,j)={ak,fi<=sk<fk<=sj}表示在活动ai结束之后,在aj开始之前的活动集,则整个问题空间可表示为S(0,n+1), 其中添加活动a0和an+1,s0=f0=0,sn+1=fn+1=2^32-1 根据dp[i][j]的含义,假设S(i,j)中不包含任何的活动序列(即满足S(i,j)定义的活动不存在),则dp[i][j]=0; 否则,假设ak时S(i,j)中存在的一个兼容活动,那 么这里存在问题S(i,j)的最优子结构:S(i,k)和S(k,j). 根据上面叙述,可以定义问题的递归解结构: dp[i][j]=0,如果S(i,j) =NULL dp[i][j]=max{dp[i][k]+dp[k][j]+1},i<k<j 最后要求的结果是dp[0][n+1] 需要注意的是: 在对区间队列进行动态规划之前,要对区间的结束时间按照时间从小到大进行排序,保证f1 <= f2 <= f3 <= ...<= fn */ #include <iostream> #define MAX 105 using namespace std; struct interval { int s; int f; int p; }inter[MAX]; int cmp(const void* a, const void* b) { return ((interval*)a)->f - ((interval*)b)->f; } int N; int dp[MAX][MAX]; int tracert[MAX][MAX]; int output[MAX]; void ACTIVITY_SELECTION_DYNAMIC() { int i, j, k, l; for(i = 0; i<= N+1; i++) dp[i][i] = 1; dp[0][0] = dp[N+1][N+1] = 0; for(l = 1; l<= N+1; l++) { for(i = 0; i<=N+1-l; i++) { j = i+l; dp[i][j] = 0; tracert[i][j] = -1; for(k = i+1; k < j; k++) { if(inter[k].s >= inter[i].f && inter[k].f <= inter[j].s) { if(dp[i][k] + dp[k][j] + 1 > dp[i][j]) { dp[i][j] = dp[i][k] + dp[k][j] + 1; tracert[i][j] = k; } } } } } } void printout(int s, int f) { if(tracert[s][f] == -1) return; cout << tracert[s][f] << ' '; printout(s, tracert[s][f]); printout(tracert[s][f], f); } int main() { cin >> N; int i, j, k; inter[0].s = inter[0].f = 0; inter[0].p = 1; for(i = 1; i<= N; i++) cin >> inter[i].s; for(i = 1; i<= N; i++) { cin >> inter[i].f; inter[i].p = 1; } inter[N+1].s = inter[N+1].f = (2<<29)-1; inter[N+1].p = 1; //要对输入的区间队列按照结束的时间f从小到大的排序,这是必须的 qsort(inter, N+2, sizeof(interval), cmp); ACTIVITY_SELECTION_DYNAMIC(); cout << dp[0][N+1] << endl; printout(0, N+1); cout << endl; return 0; }
2. 算法导论上的贪心算法:
先对每个活动ai按照活动结束时间fi从小到大排序,每次选择最早结束的活动,
而这个活动的选择需要与前面所选的活动相容。
原始问题是S= S[0,n+1],设活动am1是S[0,n+1]中具有最早结束时间的活动,
下一个问题是S[m1, n+1],设选择活动am2为S[m1,n+1]中最早结束时间的活动,
则下一个子问题是S[m2,n+1],如此继续:
#include <iostream> #define MAX 105 using namespace std; struct interval { int s; int f; int p; }inter[MAX]; int cmp(const void* a, const void* b) { return ((interval*)a)->f - ((interval*)b)->f; } int N; int tracert[MAX]; //如果tracert[i] = 1表示活动i被选择,如果=0表示不选择 int ACTIVITY_SELECTION_GREEDY() { memset(tracert, 0, N); tracert[0] = 1; int i, m; m = 1; int count= 1; for(int i = 1; i< N; i++) { if(inter[i].s >= inter[m].f) { tracert[i] = 1; m = i; count ++; } } return count; } void printout() { int i, j; for(i = 0; i< N; i++) if(tracert[i] == 1) cout << i+1 << ' '; cout << endl; } int main() { cin >> N; int i, j, k; for(i = 0; i< N; i++) cin >> inter[i].s; for(i = 0; i< N; i++) { cin >> inter[i].f; inter[i].p = 1; } qsort(inter, N, sizeof(interval), cmp); cout << ACTIVITY_SELECTION_GREEDY() << endl; printout(); return 0; }
3. 网上提供的DP方法:
Before starting the dynamic programming algorithm itself, we do some precomputation, as follows. We first sort the activities according to fi nish time, so that f1 <=f2 <=.. <= fn. We also compute, for every activity i, a number H(i) defi ned as H(i) = max{l<=i-1&& l>=0 && fl<=si} .the maximum value of the empty set is 0. We can compute each value H(i) in time O(log n) using binary search, so all of the precomputation can be done in time O(n log n).
We now perform the four steps of the dynamic programming method.
Step 1:
Describe an array of values we want to compute.
For every integer i, 0 <=i<=n, de fine A(i) = the largest pro fit we can get by (feasibly) scheduling activities from {1; 2,.., i}.
The value we are ultimately interested in is A(n).
Step 2:
Give a recurrence.
A(0) = 0.
This is true because if we are not allowed to schedule any activities at all, then the highest pro fit we can make is 0.
Let 1 <=i <=n. Then A(i) = max{A(i-1); gi + A(H(i))}:
/* 动态规划思路: 用数组A[i] 表示活动0->i范围内的最大兼容活动数 A[0] = 0; A[i] = max{A[i-1], gi+A[H[i]]} 0<i <= n; 其中H[i]表示在0->n-1所有活动中与活动i兼容的活动的最大下标 最后结果A[n]; */ #include <iostream> #define MAX 105 using namespace std; struct interval { int s; int f; int p; }inter[MAX]; int cmp(const void* a, const void* b) { return ((interval*)a)->f - ((interval*)b)->f; } int N; int A[MAX]; int tracert[MAX]; void ACTIVITY_SELECTION_DYNAMIC1() { memset(A, 0, N); memset(tracert, 0, N); int i, j, k; for(i = 1; i< N; i++) { for(j = i-1; j >= 0; j--) if(inter[j].f <= inter[i].s) break; A[i] = max(A[i-1], 1+A[j]); } } void printout() { int i = N-1, j; while(i >= 0) { if(A[i] == A[i-1]) { tracert[i] = 0; i--; } else { tracert[i] = 1; for(j = i-1; j>= 0; j--) if(inter[j].f <= inter[i].s) break; i = j; } } for(i = 0; i< N; i++) if(tracert[i] == 1) cout << i+ 1 << ' '; cout << endl; } int main() { cin >> N; int i, j, k; for(i = 0; i< N; i++) cin >> inter[i].s; for(i = 0; i< N; i++) { cin >> inter[i].f; inter[i].p = 1; } qsort(inter, N, sizeof(interval), cmp); ACTIVITY_SELECTION_DYNAMIC1(); cout << A[N-1] << endl; printout(); return 0; }
.