理论知识
贪心算法是指是这样一种方法,分步骤实施,它在每一步仅作出当时看起来最佳的选择,即局部最优的选择,希望这样的选择能导致全局最优解。在贪心算法的每一步所做的局部最优选择就叫做贪心选择。
其中贪心算法中贪心选择性质和最优子结构性是两个关键要素!
贪心选择性质:即是可以通过贪心选择来构造全局最优解
最优子结构性质上文已述。
贪心求解一般有下面几个步骤
做出贪心选择,用反证法假设当前步骤不是最优,推出矛盾,得到其是最优;再用数学归纳法证明其全局最优。
这里证明比较复杂,算是一个难点。
动态规划和贪心算法都能解决一些最优性问题,那么二者异同在哪?
基本上能用贪心也就能用动态规划,二者应用的问题都具有最优子结构,如果说动态规划是在所有子问题最优解的基础上推出更大的子问题的最优解直到得到答案,那么贪心就省略了所有子问题这个过程,它是做出一个选着再求解剩下的那个子问题,自顶向下。
一、活动选择
问题描述
假定有一个n个活动的集合S={a1,a2,…,an},这些活动都要求使用同一资源(如演讲会场),而这个资源在某个时刻只能供一个活动使用。每个活动ai都有一个开始时间si和一个结束时间fi,且0≤si<fi<∞。若两个活动ai和aj满足[si, fi)与区间[sj, fj)不重叠,则称它们是兼 容的。活动选择问题就是从活动集合中选出最大兼容活动的集合。
这个问题是典型的区间贪心问题
贪心选择:如果我们把各个活动按照结束时间排序,要求最大兼容集合,我们总是想着在兼容条件内选择最早结束的时间
证明:略
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000;
struct node{
int x,y; //定义活动结构体,x,y分别代表起始时间
bool operator <(const node &a)const{
return y<a.y;
}
}a[maxn];
int main(){
int n;scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d%d",&a[i].x,&a[i].y);
sort(a,a+n) ;//将其结束时间排序
int sum=0,end;
if(n) {
sum=1;end=a[0].y; //第一次贪心选择
}
for(int i=1;i<n;i++) //贪心选择到结束
if(a[i].x>=end){
sum++;
end=a[i].y;
}
printf("%d",sum);
return 0;
}
一个结果:
二、Huffman编码
数据结构里老生常谈的问题
贪心选择:每次选择待选集合中最小两个做叶子节点,其和为根结点加入待选集合。
代码思路:有n个结点,最后和就n-1个,可以用一个2n-1大的结构数组来构造Huffman树,用一个优先队列来存放集合
#include<iostream>
#include<queue>
#include<string>
using namespace std;
#define N 1000
struct node{ //哈夫曼树的结构,采用静态二叉树
int weight;
int parent;
int lchild,rchild;
} Node[N]; //定义一个足够大的静态哈夫曼树
struct cmp{ //将优先队列按从小到大排列的比较函数
bool operator ()(int a,int b){
return Node[a].weight>Node[b].weight;
}
};
priority_queue<int,vector<int>,cmp>q;
//弄成int型方便修改Node里的值
string s[100];
void createhuff(int n){ //创建哈夫曼树
for(int i=1;i<=n;i++)
q.push(i);
int i,j,m=n+1;
while(q.size()>1){
i=q.top();q.pop();
j=q.top();q.pop();
Node[m].weight=Node[i].weight+Node[j].weight;
Node[m].lchild=i;Node[m].rchild=j;
Node[i].parent=Node[j].parent=m;
q.push(m++);
}
}
void preorder(int root){ //前序遍历
if(root==0)
return;
printf("%d ",Node[root].weight);
preorder(Node[root].lchild);
preorder(Node[root].rchild);
}
void encodehuff (int n) {//生成编码,从下往上
for(int i=1;i<=n;i++){
int p=Node[i].parent,k=i;
string str;
while(p){
if(Node[p].lchild==k){ //这里生成的是逆向代码
str+="1";}
else {
str+="0";}
k=p;
p=Node[k].parent;
}
for(int f=str.length();f>0;f--) //将其编码正向
s[i]+=str[f-1];
}
}
void printfcode(int n) {
for(int i=1;i<=n;i++)
printf("%s\n",s[i].c_str());
//这里用printf输出字符串需要将它转化为字符数组,用cout不用
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){ //从1开始,0值与空对应
scanf("%d",&Node[i].weight);
Node[i].parent=Node[i].lchild=Node[i].rchild=0;
}
createhuff(n);
encodehuff(n);
printfcode(n);
return 0;
}
此外还有最小生成树问题、Dijkstra 算法等也应用了贪心算法稍后再论。