很多情况下局部最优解合起来不一定构成整体最优解(除非满足最优子结构性质)。所以使用贪心法必须证明其可行性,具体就是证明两个性质:
- 贪心选择性质:使用数学归纳法证明->先考虑问题的一个整体最优解,并证明可以修改这个最优解,使其从贪心选择开始(第一步的选择就要体现贪心),在做出贪心选择后原问题转化为规模较小的类似问题,通过每一步的贪心选择,最后可得到问题的整体最优解
- 最优子结构性质:使用反证法证明->先假设由问题的最优解导出的子问题的解不是最优的,然后证明在这个假设下可以构造出比原问题的最优解更好的解,从而导致矛盾
此外需要注意的是,每次做出贪心选择后,都会做出调整!!
以活动安排为例:n个活动共享一个资源,求解能安排下的活动时间不重叠的最多活动个数->贪心策略:将所有活动按结束时间排序,每次选取使剩余时间最长的活动
#include<iostream>
#include<algorithm>
using namespace std;
//贪心法求解活动安排问题
struct Action
{
int begin;
int end;
bool operator<(const Action& e) const //重载<运算符号
{
return e.end>=end;
}
};
bool choose[12]={0};
solve(Action Actions[],int n)
{
//输入排序后的结构体数组,n代表数组长度,0号位置不使用
int preEnd=Actions[1].end;
choose[1]=1;
for(int i=2;i<n;i++)
{
if(Actions[i].begin>=preEnd)
{
preEnd=Actions[i].end; //每次贪心选择后,都会有对应的调整更新
choose[i]=1;
}
}
}
int main()
{
Action Actions[]={{0,0},{5,7},{3,8},{1,4},{3,5},{0,6},{5,9},{8,11},{6,10},{2,13},{8,12},{12,15}};
sort(Actions+1,Actions+11);
solve(Actions,12);
for(int i=1;i<12;i++)
cout<<Actions[i].end<<" ";
cout<<"\n";
for(int i=0;i<12;i++)
{
if(choose[i]==1)
{
cout<<i<<" ";
}
}
return 0;
}
证明
1.贪心选择证明
证明贪心选择的正确性实际只需证明:总是存在以 该贪心策略选取的以第一个解 开始的最优解??;eg.设X是问题A的最优解,X=X’+{1},这里需要证明总是存在以活动1开始的最优解
证明如下:若果第一个选中的活动为k(k≠1),可以构造另一个最优解Y,Y中的活动是兼容的,Y与X的活动数相同。那么用活动1取代活动k得到Y’,因为e1≤ek,所以Y’中的活动是兼容的,即Y’也是最优的,所以总存在一个以活动1开始的最优解
以上证明了第一个贪心选择可导致问题的整体最优解(这样便证明了贪心策略的正确性,进而可以等到规模更小的子问题,继续贪心)
2.最优子结构证明
最优子结构需要证明:若X是问题A的最优解,X=X’+{1},则X’是问题A’的最优解(A’={i
∈
\in
∈A|ei≥b1},注意A’的约束条件,这样子问题的解才能与活动1兼容!!)
反证如下:如果能够找到一个A’含有比X’更多活动的解Y’,则将活动1加入Y‘后就得到一个包含比X跟多活动的解Y,这就与X是最优解的假设矛盾,所以X’是子问题A’的最优解