6.1 贪心法
贪心法在紫书那里就学过了,就是将一个问题分割成若干个部分,然后在某些部分中采取对于当前部分或者进行到当前部分的整体最优的策略,知道所有的部分处理完全;在每一步的进行中都不考虑对后续步骤的影响,也不往回更改之前的策略。
如果只使用贪心法,贪心法的适用性并没有那么高,但如果将贪心法结合其他的暴力算法,往往能带来意想不到的结果(例如A*算法,最小生成树算法,Dijkstra算法)。
6.1.1 常见问题
黑书上是以最少硬币问题为例的,但事实上最少硬币问题,如果稍微改一下参数,就不一定能够通过贪心法来得到最优解,我个人更倾向于将它归为动态规划类的问题。
那么什么样的问题能够用贪心法来解决呢?一般需要满足以下几个条件:
1.最优子结构性质:当一个问题的最优解包括其分割下来的子问题的最优解时,称其满足最优子结构性质。也就是说局部的最优能够扩展到全局的最优。
2.贪心选择性质:问题的整体最优解可以通过一系列局部最优的选择来得到。
活动安排问题
有很多电视节目,给出它们的开始和结束时间。有很多节目的时间冲突,问能够完整看完的节目最多有多少?
分析:这个问题又叫区间调度问题,实际上就是我们紫书那里提到过的“选择不相交区间问题”。问题的策略很简单,按照区间右端点的大小进行排序,然后从小到大的过一遍,尽可能地选取不重叠的区间即可。
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int start,end;}tv[101];//start是开始时间,end是结束时间
bool cmp(const node& a,const node& b){
return a.end<b.end;}
int main(){
int n;
while (scanf("%d",&n)&&n!=0){
for (int i=0;i<n;i++) scanf("%d%d",&tv[i].start,&tv[i].end);
sort(tv,tv+n,cmp); int ans=0,last_end=-1;//last_end存储当前选择的电视节目中最后一个节目的结束时间
//贪心算法,从左到右尽可能地选取不重合的区间,即新遍历到的区间的左端点>=最后一个区间的右端点
for (int i=0;i<n;i++) if (tv[i].start>=last_end){
ans++; last_end=tv[i].end;
} printf("%d\n",ans);
} return 0;
}
当时说的是每选择一个电视节目,将后面与它时间冲突的电视节目删除,实际上由于已经排好序了,只需要向后遍历找第一个与它时间不冲突的节目,其他的不予考虑即可。
区间覆盖问题
给定一个长度为n的区间,再给出m条线段的左右端点,问最少用多少条线段能够将整个区间覆盖。
这个紫书那里也是讲过了:
1.将每个线段按照左端点递增排序
2.设已经覆盖的区间为[L,R],在剩下的线段中找左端点小于等于R且右端点尽可能大的端点来覆盖尽可能长的区间。然后更新已经覆盖的区间,即更新R值。重复操作直到覆盖完全。
事实上如果是我来编这本书,我会将区间覆盖问题作为介绍贪心法的例题,我个人觉得区间覆盖问题既不容易直观的看出解法,同时也非常适合初学者来思考贪心法解决问题的策略,并且最后这个策略还是比较容易想到和证明正确的。
最优装载问题
紫书的第一个问题,太水了,就不再做介绍了。
多机调度问题
设有n个独立的作业,由m台相同的计算机进行加工。作业i的处理时间为ti,,且每个作业只能在任何一台计算机上加工处理,不能间断或者拆分。要求给出一种作业的调度方案,在尽可能短的时间内,由m台计算机加工处理完成这n个作业。
分析:如果n<=m,那没什么好说的,一个萝卜一个坑的安排作业,n>m时,将时间最长的作业分配给最先空闲的计算机,让处理时间长的作业得到优先的处理。
至于为什么这么做,感觉是一个直观的做法,但是仔细想确实想不到证明的方法。
6.1.2 Huffman编码
有关概念看这篇博客,做一下例题。
poj 1521 “Entropy”
输入一个字符串,分别用普通ASCII编码(每个字符8bit)和Huffman编码,输出编码后的长度,并输出压缩比。
分析:统计一下每个字符的个数,用优先队列做就可以了。统计字符个数的方法有很多种,书上就是排序了一下:
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
string s; priority_queue<int,vector<int>,greater<int> >q;
while(getline(cin,s)&&s!="END"){
sort(s.begin(),s.end()); int t=1;//将字符串中的字符进行排序
for (int i=1;i<s.length();i++){
//每出现一次不同,说明相同的字符个数已经记录完毕,将字符个数放入优先队列中
if (s[i]