16.1-1
当活动
k
在集合活动
增加两个虚拟的活动
a0,f0=0;an+1,sn+1=∞
。现在变成从活动
a0...an+1
中选择最大的可兼容集合
S0,n+1
。使用两个表
c[0..n+1,0..n+1],act[0..n+1,0..n+1]
分别记录活动个数和选择的活动
k
。当
DYNAMIC-ACTIVITY-SELECTOR(s, f, n)
let c[0..n + 1,0..n + 1] and act[0..n + 1,0..n + 1] be new tables
for i = 0 to n
c[i, i] = 0
c[i, i + 1] = 0
c[n + 1, n + 1] = 0
for l = 2 to n + 1
for i = 0 to n - l + 1
j = i + l
c[i, j] = 0
k = j - 1
while f [i] < f [k]
if f[i] ≤ s[k] and f[k] ≤ s[j] and c[i, k] + c[k, j] + 1 > c[i, j]
c[i, j] = c[i, k] + c[k, j] + 1
act[i, j] = k
k = k - 1
print “A maximum size set of mutually compatible activities has size ” c[0, n + 1]
print “The set contains ”
PRINT-ACTIVITIES(c, act, 0, n + 1)
PRINT-ACTIVITIES(c, act, i, j)
if c[i, j] > 0
k = act[i, j ]
PRINT-ACTIVITIES(c, act, i, k)
PRINT-ACTIVITIES(c, act, k, j)
print k
动态规划是
O(n3)
,贪心算法是
O(n)
。
附上实际运行代码:
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
struct Activities
{
int start_time;
int finish_time;
Activities():start_time(0),finish_time(0){}
};
void RECURSIVE_ACTIVITY_SELECTOR(Activities *act,int k,int n,vector<int> &activities)
{
int m = k + 1;
while(m < n && act[m].start_time < act[k].finish_time)
m += 1;
if(m < n)
{
activities.push_back(m);
RECURSIVE_ACTIVITY_SELECTOR(act,m,n,activities);
}
else return;
}
void GREEDY_ACTIVITY_SELECTOR(Activities *act,int n,vector<int> &activities)
{
activities.push_back(1);
int k = 1;
for(int m = 2; m < n; m++)
{
if(act[m].start_time >= act[k].finish_time)
{
k = m;
activities.push_back(m);
}
}
}
void PRINT_ACTIVITY(int **c,int **activities,int i,int j)
{
if(c[i][j] > 0)
{
int k = activities[i][j];
PRINT_ACTIVITY(c,activities,i,k);
PRINT_ACTIVITY(c,activities,k,j);
cout << k << ' ';
}
}
void DYNAMIC_ACTIVITY_SELECTOR(Activities *act,int n)
{
int **c = new int *[n+2];
int **activities = new int *[n+2];
for(int i = 0; i <= n + 1; i++)
{
c[i] = new int[n+2];
activities[i] = new int[n+2];
}
for(int i = 0; i <= n; i++)
{
c[i][i] = 0;
c[i][i+1] = 0;
}
c[n+1][n+1] = 0;
for(int l = 2; l <= n + 1; l++)
{
for(int i = 0; i <= n - l + 1; i++)
{
int j = i + l;
c[i][j] = 0;
int k = j - 1;
while(act[i].finish_time < act[k].finish_time)
{
if(act[i].finish_time <= act[k].start_time && act[k].finish_time <= act[j].start_time && c[i][k]+c[k][j]+1 > c[i][j])
{
c[i][j] = c[i][k] + c[k][j] + 1;
activities[i][j] = k;
}
k--;
}
}
}
cout << "A maximum size set of mutually compatible activities has size " << c[0][n+1] << endl;
cout << "Using DYNAMIC_ACTIVITY_SELECTOR, the set contains ";
PRINT_ACTIVITY(c,activities,0,n+1);
for(int i = 0; i <= n + 1; i++){
delete []c[i];
delete []activities[i];
}
delete []c;
delete []activities;
}
int main()
{
int start[] = {0,1,3,0,5,3,5,6,8,8,2,12,INT_MAX};//添加两个虚拟活动
int end[] = {0,4,5,6,7,9,9,10,11,12,14,16,INT_MAX};
Activities *act = new Activities[13];
for(int i = 0; i < 13; i++){
act[i].start_time = start[i];
act[i].finish_time = end[i];
}
vector<int> recursive,greedy;//保存结果
//这里活动数是11个,加上虚拟的第0个结点,因此调用是12,下面的greedy调用同
RECURSIVE_ACTIVITY_SELECTOR(act,0,12,recursive);
GREEDY_ACTIVITY_SELECTOR(act,12,greedy);
cout << "A maximum size set of mutually compatible activities has size " << recursive.size() << endl;
cout << "Using RECURSIVE_ACTIVITY_SELECTOR, the set contains ";
for(int i = 0; i < recursive.size(); i++)
cout << recursive[i] << ' ';
cout << endl << endl;
cout << "A maximum size set of mutually compatible activities has size " << recursive.size() << endl;
cout << "Using GREEDY_ACTIVITY_SELECTOR, the set contains ";
for(int i = 0; i < recursive.size(); i++)
cout << recursive[i] << ' ';
cout << endl << endl;
DYNAMIC_ACTIVITY_SELECTOR(act,11);//一共11个活动,这里调用是11
cout << endl << endl;
//注:使用动态规划得到的2,4,9,11,和贪心算法的答案不一样,但是都是对的
delete []act;
return 0;
}
16.1-2
设现在有
n
个活动
GREEDY-ACTIVITY-SELECTOR-JMC(s,f)
n = s.length
A = {a_n}
for m=n-1 to 1
if f[m]<=s[k] //greedy step
A={a_m} U A
k=m
return A
证明类似书中定理16.1,在此略。
16.1-3
1) 持续时间最短
i | 1 | 2 | 3 |
---|---|---|---|
开始时间 | 0 | 2 | 3 |
结束时间 | 3 | 4 | 6 |
持续时间 | 3 | 2 | 3 |
选择持续时间最短应该选活动 2,实际上是选择活动 1、3。
2) 重叠最少者
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|
开始时间 | 0 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 5 | 5 | 6 |
结束时间 | 2 | 3 | 3 | 3 | 4 | 5 | 6 | 7 | 7 | 7 | 8 |
重叠个数 | 3 | 4 | 4 | 4 | 4 | 2 | 4 | 4 | 4 | 4 | 3 |
先选 6,再选 1、3。实际最优是 1、 5、 7、 11.
3) 最早开始
参考16.1中给的例子,然后我们在那个例子中添加一个活动
a12
,开始结束时间分别是 0,14。选择这个活动就不能再选其他活动了。
16.1-4
设
S
是
首先,最直观的是先选择最大可兼容的活动集合
S1
,将集合
S1
中的活动放在第一个教室,然后从集合
S−S1
中选择最大可兼容集合
S2
放在第二个教室….这个方法是
Θ(n2)
。但是有个问题是可能用到的教室会比实际确实需要的教室多,比如
{[1,4),[2,5),[6,7),[4,8)}
。按上述方法分别选出
{[1,4),[6,7)}
以及
[2,5)
和
[4,8)
,需要三个教室,实际上选择
{[1,4),[4,8)}
和
{[2,5),[6,7)}
只需两个教室。
换个思路,基本思想是按照开始时间顺序,将每个活动放入任意一个在那个时间可以使用的教室中。按时间顺序(包括开始和结束时间)建立一个活动集合,维护两个链表:在
t
时刻忙的教室和在
为了尽可能减少教室使用量,如果可能的话,在选择从没使用过的教室前,总是先选择已经有过活动的教室。
时间复杂度:
n
个活动有
16.1-5
此题不能用贪心算法,但是可以用动态规划。
按书中本节的定义定义一个活动集合
Sij
,一个最优的解是
Sij
中具有最大值的可兼容集合的子集。设
Aij
是
Sij
的一个最优解,设
ak
在
Aij
中,则
Aij=Aik∪ak∪Akj
。所以
Aij
的最大值等于
Aik
的最大值加
Akj
的最大值加
ak
。
记集合
Sij
的最优解的值是
val[i,j]
得:
val[i,j]=val[i,k]+vk+val[k,j]
。由于我们不知道集合
Sij
中有活动
ak
,我们必须检查集合
Sij
中的所有活动然后决定选择哪一个。所以:
在执行递归的时候,如果活动 k 在集合
增加两个虚拟的活动 a0,f0=0;an+1,sn+1=∞ 。现在变成从活动 a0...an+1 中选择可兼容集合具有最大值的 A0,n+1 。使用表 val[0..n+1,0..n+1],act[0..n+1,0..n+1] 分别记录递归值和选择放入集合 A0,n+1 的活动 k 。当
MAX-VALUE-ACTIVITY-SELECTOR(s, f, v, n)
let val[0..n + 1,0..n + 1] and act[0..n + 1,0..n + 1] be new tables
for i = 0 to n
val[i, i] = 0
val[i, i + 1] = 0
val[n + 1, n + 1] = 0
for l = 2 to n + 1
for i = 0 to n - l + 1
j = i + l
val[i, j] = 0
k = j - 1
while f [i] < f [k]
if f[i] ≤ s[k] and f[k] ≤ s[j] and val[i, k] + val[k, j] + v_k > val[i, j]
val[i, j] = val[i, k] + val[k, j] + v_k
act[i, j] = k
k = k - 1
print “A maximum-value set of mutually compatible activities has value ” val[0, n + 1]
print “The set contains ”
PRINT-ACTIVITIES(val, act, 0, n + 1)
PRINT-ACTIVITIES(val, act, i, j)
if val[i, j] > 0
k = act[i, j ]
PRINT-ACTIVITIES(val, act, i, k)
PRINT-ACTIVITIES(val, act, k, j)
print k
时间复杂度是 O(n3) 。具体实现和练习16.1-1差不多。