贪心算法
基本思想
在对问题求解时,总是遵循某种规则做出在当前看来是最好的选择,期待通过所做的局部最优选择来产生一个全局最优解。
缺点
贪心算法不是对所有问题都能得到整体最优解。
例子
从前有一只鹅,一天可以下两个金蛋,但是在第一天直接杀了它可以拿到二十个金蛋。问如何在21天内拿到尽量多的金蛋?
动态规划:当n=21时会选择最后一天杀,能拿到40个金蛋;
贪心算法:第一天杀,能拿到20个金蛋。
结论:
贪心算法求解的结果不一定是全局最优解。但对于某些问题来说用贪心算法刚好能得到全局最优解。
基本要素
1)贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择来得到;
证明方法:
首先考察一个问题的最优解,证明可修改该最优解,使得其从贪心选择开始也是最优的,然后用数学归纳法证明每一步都可以通过贪心选择得到最优解:
①假定首选元素不是贪心选择所要的元素,证明将首元素替换成贪心选择所需元素,依然得到最优解;
②数学归纳法证明每一步均可通过贪心选择得到最优解
2)最优子结构性质:一个最优策略的子策略总是最优的。
解题思路
1)分析问题,选择合适的贪心策略;
2)证明两个性质;
3)求解。
活动安排问题
问题描述
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si <fi 。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。 若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。
活动安排问题就是在所给的活动集合中选出最大的相容活动子集合。
问题分析
可能的贪心选择策略:
1)每次选择开始时间最早的活动;
2)每次选择活动使用时间最少的活动;
3)每次选择结束时间最早的活动。
选择第3)作为贪心选择策略。
性质证明
贪心选择性质
证明:
假设有一个最优解A,其活动是以结束时间非减序进行排列的。再假设A中的第一个活动是K。通过贪心选择选择到的第一个活动记为活动1。
若k=1,则A是以活动1开始的;
若K≠1,则用活动1替换掉A中的活动K,因为end[1]≤end[K],活动1能与A中其他活动相容,所以证明了总存在一个以贪心选择开始的最优活动安排方案,即具有贪心选择性质。
最优子结构性质
证明:
通过贪心选择选择了活动1后,原问题化简为在剩下的活动中找与活动1相容的活动进行活动安排,也就是若A是原问题的最优解,则A’=A-{1}是活动安排问题E’=E-{1}的最优解,即贪心选择做出的每一次选择都会将问题化简为一个更小的与原问题具有相同形式的子问题。因此该问题具有最优子结构性质。
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
struct Activity{
int num,start,end,mark;
};
bool comp(Activity a,Activity b)
{
return a.end<b.end;
}
void Select(Activity act[],int n)
{
int begin=act[1].start;
for(int i=1;i<=n;i++)
if(act[i].start>=begin){//如果活动相容
begin=act[i].end;
act[i].mark=1;
}
}
int main()
{
int n;
cin>>n;
Activity act[n+1];
for(int i=1;i<=n;i++){
cin>>act[i].start>>act[i].end;
act[i].num=i;
act[i].mark=0;
}
sort(act+1,act+n,comp);
Select(act,n);
for(int i=1;i<=n;i++)
if(act[i].mark==1)
cout<<act[i].num<<" ";
}
/*
测试案例:
11
1 4
3 5
0 6
5 7
3 8
5 9
6 10
8 11
8 12
2 13
12 14
*/