- 这是《算法笔记》的读书记录
- 本文参考自4.4节
1. 简单贪心
- 贪心法是求解一类最优化问题的方法他,它总是考虑当前状态下的局部最优的策略,来使全局结果达到最优
- 不是所有问题都能用贪心法解决,因此要严谨地使用贪心算法,需要对策略进行证明。证明的一般思路是使用反证法及数学归纳法,先假设策略不能达到最优,然后根据一系列推到得出矛盾,从而证明策略是最优的,最后通过数学归纳法保证全局最优
- 真正做题的时候,往往没有时间严谨证明(给出一个严谨的证明往往比写出算法本身更难)。所以一般只要想到贪心似乎可行,且举不出反例,就可以用贪心做一下试试
- 示例:
2. 区间贪心
(1)区间不相交问题
-
来看一个稍微复杂一点的问题,现有多个开区间,要从中找出尽量多的区间,使得找出部分两两互不相交
-
先考虑最简单的情况,如下图,I1被I2包含,这时应该选I1,给其他区间留出更大的空间
-
现在假设通过上述方法去除了所有发生包含的区间,把剩下的区间按左端点从大到小排序,从上往下画如下。 这样每一个区间,都可以被它相邻那个的右端点分成两段(如I1被I2右端点分成两段),其中右边部分是不和任何区间重合的,左边部分和相邻区间又组成了上面简单情况的关系,因此应该选上面的那个区间(I1)
-
现在考察一个一般情况,这里没有去除发生包含的区间,先按左端点从大到小排序,再按右端点从小到大排序,从上往下画如下。根据上面的分析,这里应该选E1,E2
-
总结一下,总是选择左端点最大的区间(当左端点相同时选右端点最小的),依次往后找出所有不相交的区间即可
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; struct Inteval { int x,y; //左右端点 }I[110]; bool cmp(Inteval a,Inteval b) { if(a.x!=b.x) return a.x>b.x; //左端点大到小 return a.y<b.y; //右端点小到大 } int main() { int n; //区间个数 while(scanf("%d",&n),n!=0) { for(int i=0;i<n;i++) scanf("%d%d",&I[i].x,&I[i].y); sort(I,I+n,cmp); int ans = 1; //不相交区间计数 int lastX = I[0].x; //记录上一个区间左端点 for(int i=1;i<n;i++) { if(I[i].y<=lastX) { lastX = I[i].x; ans++; } } printf("%d",ans); } return 0; }
(2)区间选点问题
- 区间选点问题和区间不相交问题类似:给定若干区间,问至少要几个点,才能使每个区间中都至少存在一个点
- 先考虑最简单的情况,如下图,I1被I2包含,这时应在I1中选一个点
- 现在假设通过上述方法去除了所有发生包含的区间,把剩下的区间按左端点从大到小排序,从上往下画如下。现在对于I1来说,选哪个点可以尽量多地覆盖其他区间?显然是左端点。
- 事实上,这个问题和区间不相交问题的代码也几乎完全一致,只要把
if(I[i].y<=lastX)
改成if(I[i].y<lastX)
就行了。我们只要仿照上面的方法找出所有不相交区间,可以保证其他区间总有一部分和找出的区间重合,所以在每个找出的区间上放一个点就行了,只不过这里不能出现两个区间公用端点的情况
3. 小结
- 总的来说,贪心是用来解决一类最优化问题,并希望由局部最优解来推全局最优解的算法思想。
- 贪心算法适用的问题一定满足最优子结构性质:即一个问题的最优解可以由其子问题的最优解有效构建出来
- 不是所有问题都适用与贪心算法