概念
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
基本思路
1. 建立数学模型来描述问题;
2. 把求解的问题分成若干个子问题;
3. 对每一子问题求解,得到子问题的局部最优解;
4. 把子问题的解局部最优解合成原来解问题的一个解。
算法实现
1. 从问题的某个初始解出发。
2. 采用循环语句,当可以向求解目标前进一步时,就根据局部最优策略,得到一个部分解,缩小问题的范围或规模。
3. 将所有部分解综合起来,得到问题的最终解。
实例1 背包问题
• 问题描述
有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
• 问题分析
1.目标函数: ∑pi最大,使得装入背包中的所有物品pi的价值加起来最大。
2.约束条件:装入的物品总重量不超过背包容量:∑wi<=M( M=150)
3.贪心策略:
○ 选择价值最大的物品
○ 选择重量最小的物品
○ 选择单位重量价值最大的物品
有三个物品A,B,C,其重量分别为{30,10,20},价值分别为{60,30,80},背包的容量为50,分别应用三种贪心策略装入背包的物品和获得的价值如下图所示:
三种策略
• 算法设计:
我们采用先拿单位价值最大的物品的策略,输入n和m后,我们计算每个物品的单位价值,将其进行逆序排序,接着依次拿单位价值最大的物品,直到到达背包的容量为止。
• 代码实现
#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
//背包结构体
struct bag{
int w; //物品重量
int v; //物品总价值
double w_v; //物品单位价值
};
//实现降序排列
bool cmp(bag x,bag y){
return x.w_v>y.w_v;
}
int main()
{
//n个物品,背包容量为m
int n,m;
while(cin>>n>>m){
struct bag b[n];
for(int i=0;i<n;i++){
cin>>b[i].w>>b[i].v;
b[i].w_v=b[i].v/(b[i].w)*1.0;
}
sort(b,b+n,cmp);
int maxV=0;
for(int i=0;i<n;i++){
if(b[i].w<=m){
m-=b[i].w;
maxV+=b[i].v;
}else
break;
}
cout<<"背包最大价值为:"<<maxV<<endl;
}
return 0;
}
/*
7 150
35 10
30 40
60 30
50 50
40 35
10 40
25 30
*/
• 运行结果
2 活动安排问题
• 问题描述:
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动 i 都有一个要求使用该资源的起始时间si和一个结束时间 fi ,且si小于fi ,要求设计程序,使得安排的活动最多。(ps:活动结束时间按从小到大排序)
• 问题分析:
活动安排问题要求安排一系列争用某一公共资源的活动。用贪心算法可提供一个简单、漂亮的方法,使尽可能多的活动能兼容的使用公共资源。设有n个活动的集合{0,1,2,…,n-1},其中每个活动都要求使用同一资源,如会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间starti和一个结束时间endi,且 starti小于endi。如选择了活动 i,则它在半开时间区间[starti,endi)内占用资源。若区间[starti,endi)与区间[startj,endj)不相交,称活动i与活动j是相容的。也就是说,当 startj≥endi 或 starti≥endj 时,活动i与活动j相容。活动安排问题就是在所给的活动集合中选出最多的不相容活动。
• 算法设计:
若被检查的活动i的开始时间starti小于最近选择的活动j的结束时间endj,则不选择活动i,否则选择活动i加入集合中。运用该算法解决活动安排问题的效率极高。当输入的活动已按结束时间的升序排列,算法只需O(n)的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)的时间重排。
• 代码实现:
代码
#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
struct pro{
int s; //开始时间
int e; //结束时间
};
bool cmp(pro a,pro b){
return a.e<b.e;
}
int main()
{
int n; //活动的数量
while(cin>>n){
struct pro p[n];
struct pro t[n];
for(int i=0;i<n;i++){
cin>>p[i].s>>p[i].e;
}
sort(p,p+n,cmp);
int maxN=0;
int q=-1;
for(int i=0;i<n;i++){
if(q<=p[i].s){
maxN++;
q=p[i].e;
t[i].s=p[i].s;
t[i].e=p[i].e;
}
}
cout<<"最多的活动个数是:"<<maxN<<endl;
//cout<<"每个活动的开始时间和结束时间是:"<<endl;
//for(int i=0;i<n;i++){
//cout<<t[i].s<<" "<<t[i].e<<endl;
//}
}
return 0;
}
/*
11
1 4
3 5
0 6
5 7
3 8
5 9
6 10
8 11
8 12
2 13
12 14
*/
运行结果:
3 最小生成树(克鲁斯卡尔算法)
• 问题描述
求一个连通无向图的最小生成树的代价(图边权值为正整数)。
输入
输出
每个用例,用一行输出对应图的最小生成树的代价。
样例输入
1
6
0 6 1 5 0 0
6 0 5 0 3 0
1 5 0 5 6 4
5 0 5 0 0 2
0 3 6 0 0 6
样例输出
15
• Kruskal算法简述
假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
• 算法难点:
(1)边的选择要求从小到大选择,则开始显然要对边进行升序排序。
(2)选择的边是否需要,则从判断该边加入后是否构成环入手。
• 算法设计:
(1)对边升序排序
在此采用链式结构,通过插入排序完成。每一结点存放一条边的左右端点序号、权值及后继结点指针
(2)边的加入是否构成环
一开始假定各顶点分别为一组,其组号为端点序号。选择某边后,看其两个端点是否在同一组中,即所在组号是否相同,如果是,表示构成了环,则舍去。 如果两个端点所在的组不同,则表示可以加入,则将该边两端的组合并成同一组。
• 代码实现:
#include <iostream>
using namespace std;
//图顶点的结构体
struct node{
int l;
int r;
int len;
node *next;
};
//插入排序
void insert(node *&h,node *p){
node *q=h;
while(q->next && q->next->len<=p->len)
{
q=q->next;
}
p->next=q->next;
q->next=p;
}
int main(){
node *h,*p;
int n,m,x,temp;
int *a;
int i,j;
int sum;
cin>>n;
while(n--){
sum=0;
cin>>m;
a=new int[m+1];
for (i=1; i<=m; i++){
a[i]=i;
}
h=new node;
p=h;
p->next=NULL;
for (i=1; i<=m; i++)
for (j=1; j<=m; j++){
cin>>x;
if (i>j && x!=0){
p=new node;
p->l=i;
p->r=j;
p->len=x;
p->next=NULL;
insert(h,p); //调用插入排序
}
}
p=h->next;
while (p){
if (a[p->l]!=a[p->r]){
sum+=p->len;
temp=a[p->l];
for(i=1; i<=m; i++)
if (a[i]==temp){
a[i]=a[p->r];
}
}
p=p->next;
}
cout<<sum<<endl;
}
return 0;
}
/*
1
6
0 6 1 5 0 0
6 0 5 0 3 0
1 5 0 5 6 4
5 0 5 0 0 2
0 3 6 0 0 6
0 0 4 2 6 0
*/
运行结果