贪心法——活动安排问题
贪心法
贪心法的本质可以认为是动态规划在特定条件下的优化。贪心法满足两个性质,最优子结构性质和贪心选择性质。
满足最优子结构性质意味着问题可以被层层分解为子问题,这些子问题构成树状结构,意味着贪心算法可以求解的问题可以使用动态规划算法求解。对应到树状结构的话,就是从底向上求解,需要预先求出所有子问题的解以期求出父问题的解,这就意味着要遍历所有的从叶节点到根节点的路径。
贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的贪心选择来达到。满足贪心选择性质,意味着对于每个内节点处,选择哪个孩子节点将会有明确的统一的模式(比如全部取最大的,这种方法通常是只顾眼前的),每作一次贪心选择就将所求问题简化为规模更小的子问题,同时该选择将成为最终解的一部分。这样依据这种模式,只需要从上到下按此模式从根节点开始求解出到叶节点的一条路径,这条路径就被保证为最优解,如此将简化动态规划的复杂度。
使用贪心法需要证明其贪心选择性质。贪心选择性质对应于数学归纳法的k+1步推导为k步,可以表示如下:
k+1 —(T)—> k
其中k+1为父问题的最优解;T为贪心选择,即某种减小子问题的模式,每次贪心选择会产生最终解的一部分(这种模式一般会将原问题减去贪心选择生成子问题,这样当子问题最小时所有的贪心选择刚好构成了原问题,这一点由最优子结构说明);k为子问题的最优解,是k+1经过T后减小成为的问题,假如证明了k还是最优解,那么就可以继续推证k-1,k-2…一直到1都可以通过同样的贪心选择迭代得到。因此求原问题的最优解就成为初始条件,假如满足贪心选择性质就可以一路迭代下去,迭代到最小问题时刚好一路的贪心选择可以组成最优解。
活动安排问题
问题:
有n个活动申请使用同一个礼堂,每项活动有一个开始时间和一个截止时间,如果任何两个活动不能同时举行,问如何选择这些活动,从而使得被安排的活动数量达到最多?
设S={1, 2, …, n}为活动的集合,si和fi分别为活动i的开始和截止时间,i=1, 2, …, n。定义活动i与j相容:si ≥ fj或sj ≥fi, i≠j求S最大的两两相容的活动子集。
问题分析:
先按照结束时间非降序排序。
贪心选择:每次选择结束时间最早的可选活动。
构造父子问题:父问题A为待选活动集合中的最优安排,子问题A’为比A中结束时间最早的可选活动结束的晚或相同的活动集合中的最优安排。
证明其贪心选择性质:首先证明A为所有活动集合的最优安排时,A’为结束时间不早于第一个活动的所有活动的集合的最优安排,就是数学归纳法的初始条件。为此需证明两点:1.A经过贪心选择会生成A’,这一点只需证明第一个活动在A内即可,此处易证;2.A最优,那么A’也最优,这一点根据最优子结构性质证明。接着证明A为已经选了部分活动后剩下的活动集合的最优安排,A’为结束时间不早于A中第一个活动的活动集合的最优安排,同样类似的方法证明两点即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define MAXN 105
struct activity{
int start;
int end;
activity(int _start,int _end){
start = _start;
end = _end;
}
};
bool cmp(activity x,activity y){
return x.end <= y.end;
}
// 贪心法求解最大相容活动集合
// 输入:排序完成的活动安排集合a[],活动数量n,结果记录数组A[]
void GreedySelector(activity a[], int n,int A[]){
// 第一次贪心选择
A[0] = 1;
// 后续的每个活动都有两种情况,是贪心选择和不是贪心选择
// 假如是贪心选择,那它就是第一个和上一个贪心选择相容的活动
// 为此需要记录最近的一次贪心选择以便确定下一个贪心选择
int j = 0;
for(int i = 1;i < n;i++){
if(a[i].start >= a[j].end){
j = i;
A[i] = 1;
}else{
A[i] = 0;
}
}
}
int main(){
int n = 11,A[11];
memset(A, 0, sizeof(A));
activity a[11] = {{1,4},{3,5},{0,6},{5,7},{3,8},{5,9},{6,10},{8,11},{8,12},{2,13},{12,14}};
sort(a,a+n,cmp);
GreedySelector(a, n, A);
for(int i = 0;i < n;i++){
if(A[i])
cout<<i+1<<": "<<a[i].start<<","<<a[i].end<<endl;
}
}
复杂度:
时间:排序复杂度O(nlogn) + 贪心迭代复杂度O(n)
空间:O(n)