目录
一、概述
- 适合于贪心算法求解的问题具有:贪心选择性质、最优子结构性质
- 贪心算法可以获取到问题的局部最优解,不一定能获取到全局最优解
- 贪心算法总是作出在当前看来最好的选择;并且每次贪心选择都能将问题化简为一个更小的与原问题具有相同形式的子问题。
- 贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。
- 虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题(具有最优子结构和贪心性质的问题)它能产生整体最优解。如单源最短路径问题,最小生成树问题等。
- 在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的一个很好近似。
- 贪心算法具有一定的速度优势,如果一个问题可以同时使用几种方法解决,贪心算法应该是最好的选择之一。
4.贪心算法和动态规划算法比较
共同点:
都是求最优解的选择性算法
所解决的问题都具有最优子结构性质,即全优一定包含局优
不同点:
- 贪心算法的选择策略即贪心选择策略,通过对候选解按照一定的规则进行排序,然后就可以按照这个排好的顺序进行选择了,选择过程中仅需确定当前元素是否要选取,与后面的元素是什么没有关系。
(2)动态规划的选择策略是试探性的,每一步要试探所有的可行解并将结果保存起来,最后通过回溯的方法确定最优解,其试探策略称为决策过程。 - 贪心算法具有贪心选择特性。贪心算法求得局部最优解(局部最优,不一定是全局最优);动态规划算法从全局最优考虑问题
5.利用贪心策略解题,需要解决以下两个问题:
(1)该题是否适合于用贪心策略求解(贪心选择性质+最优子结构性质)
(2)如何选择贪心标准,以得到问题的最优/较优解
- 使用贪心算法解决问题时,关键是确定贪心策略/标准,即按什么标准进行排序进行贪心选择
- 使用贪心算法解决:活动安排问题、背包问题、最优装载问题(件数最多)、删数问题、汽车加油问题、果子合并(openjudge题目)
- 知道单源最短路径(Dijkstra算法)、最小生成树(Prime法、Kruskal方法)使用了贪心算法即可
二、活动安排问题
描述
学校的小礼堂每天都会有许多活动,有时间这些活动的计划时间会发生冲突,需要选择出一些活动进行举办。小刘的工作就是安排学校小礼堂的活动,每个时间最多安排一个活动。现在小刘有一些活动计划的时间表,他想尽可能的安排更多的活动,请问他该如何安排。
输入
每组测试数据的第一行是一个整数n(1 随后的n行,每行有两个正整数Bi,Ei(0<=Bi,Ei<10000),分别表示第i个活动的起始与结束时间(Bi<=Ei)
输出
对于每一组输入,输出最多能够安排的活动数量。
每组的输出占一行
分析:
- 根据活动的结束时间对活动进行排序
- 如果a[i+1].s>=a[i].f,则可以进行下一个活动,sum++
- 如果要输出活动编号,加index,输入的时候赋值即可
代码:
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct action
{
int s;//活动起始时间
int f;//活动结束时间
int index;//活动编号
}a[101];
bool cmp(const action&a,const action &b)
{
if(a.f<=b.f)
return true;
else
return false;
}
int main()
{
int n,b[101];
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i].s>>a[i].f;
a[i].index=i+1;
}
sort(a,a+1+n,cmp);
int sum=0;
for(int i=2;i<=n;i++)
{
if(a[i].s>=a[i-1].f)
{
sum++;
cout<<a[i].index<<" ";
}
}
cout<<endl;
cout<<sum<<endl;
}
三、背包问题
给定一个载重量为M的背包,考虑n个物品,其中第 i 个物品的重量 wi ,价值vi (1≤i≤n),要求把物品装入背包,且使背包内的物品价值最大。物品可分割
单位重量价值:vi/wi,把单位重量价值加性价比,即按性价比从高到低的顺序选取物品
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct bag
{
int w;//物品的重量
int v;//物品的价值
double c;//单位重量的价值,v/w
}a[101];
bool cmp(bag a,bag b)
{
if(a.c>=b.c)
return true;
else
return false;
}
int main()
{
int n;//物品数量
int c;//背包容量
cin>>n>>c;
double b=0;//装入背包的价值
int cleft=c;//背包剩余容量
for(int i=0;i<n;i++)
{
cin>>a[i].w>>a[i].v;
a[i].c=a[i].v/a[i].w;
}
sort(a,a+n,cmp);
for(int i=0;i<n;i++)
{
if(a[i].w<cleft)//第i个物品重量小于背包剩余容量
{
cleft-=a[i].w;//装入,剩余容量减少
b+=a[i].v;//物品全部装入
}
else
{
//需要分割
b+=cleft*a[i].v/a[i].w;
}
}
cout<<b<<endl;
}
如果要同时得到解向量,即每件物品装了多少到背包,结构体加
double x; //装入背包的量,0≤x≤1,初值都为0
int index; //物品编号
- 如果第i件物品全部装入:a[a[i].index].x = 1.0;
- 如果第i件物品部分装入:
a[a[i].index].x = cleft/a[i].w;
b += a[a[i].index].x*a[i].v;
四、最优装载问题(件数最多)
有一批集装箱要装上一艘载重量为 c 的轮船,其中集装箱 i 的重量为 wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船(件数最多)
输入:
重量c,集装箱个数n;
接下来分别输入n个集装箱重量a[i]
输出:
第一行:能装的最多件数
第二行:装入的集装箱编号
分析:
既然要求件数最多,即采用重量最轻者先装的贪心选择策略
代码:
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct load
{
int index;//编号
int w;//重量
}a[101];
bool cmp(load a,load b)
{
if(a.w<b.w)
return true;
else
return false;
}
int main()
{
int n;
int c;
int x[101];
cin>>c>>n;
memset(a,0,sizeof(a));
memset(x,0,sizeof(x));//初值都是0
for(int i=1;i<=n;i++)
{
cin>>a[i].w;
a[i].index=i;
}
sort(a,a+n+1,cmp);
if(a[1].w>c)//一个都装不进去
{
cout<<"No answer!"<<endl;
// continue;
}
int i;
for(i=1;i<=n&&a[i].w<=c;i++)
{
x[a[i].index]=1;//装过的标志1
c-=a[i].w;
}
//输出装载的集装箱数量
cout<<i-1<<endl;
//输出装载的集装箱编号
for(i=1;i<=n;i++)
if(x[i]) cout<<i<<" ";
cout<<endl;
}
五、删数问题
给定n位正整数a,去掉其中任意k≤n个数字后,剩下的数字按原次序排列组成一个新的正整数。对于给定的n位正整数a和正整数k,设计一个算法找出剩下数字组成的新数最小的删数方案(顺序不改变)
输入:
第1行是1个正整数a,第2行是正整数k。//k是删除数字的个数
例如:178543 4
输出: 13
对于给定的正整数a,编程计算删去k个数字后得到的最小数。
分析:
删数问题找第一个不下降的数,删掉 即寻找最近下降点
输入:字符串格式
存储:数组,整数数组或者字符数组
代码:
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct load
{
int index;//编号
int w;//重量
}a[101];
bool cmp(load a,load b)
{
if(a.w<b.w)
return true;
else
return false;
}
int main()
{
string a;
int k;
cin>>a>>k;
if(k>=a.size())
a.erase();//如果k≥n,所有数字均被删除
else
{
while(k>0)
{
int i;
for(i=0;(i<a.size()-1)&&(a[i]<=a[i+1]);i++);
//如果不满足条件就找到最近下降点,删除
a.erase(i,1);//从i开始,删除一个,即删除i
k--;
}
}
while(a.size()>1&&a[0]=='0')//删除前导数字0
a.erase(0,1);
cout<<a<<endl;
}
六、汽车加油问题
一辆汽车加满油后可行驶n公里。旅途中有若干个加油站。设计一个有效算法,指出应在哪些加油站停靠加油,使沿途加油次数最少。对于给定的n(n <= 5000)和k(k <= 1000)个加油站位置,编程计算最少加油次数。
输入:
第一行有2个正整数n和k,表示汽车加满油后可行驶n公里,且旅途中有k个加油站
接下来的1行中,有k+1个整数,表示第k个加油站与第k-1个加油站之间的距离。第0个加油站表示出发地,汽车已加满油。第k+1个加油站表示目的地。
输出:
最少加油次数。如果无法到达目的地,则输出”No Solution”。
分析:
1、到达加油站之后,看看剩余的油能否跑到下一个加油站;
能,则不用加油 否则,加油
2、一个变量表示到达该站前需要行驶距离 s1
3、一个变量表示到达下一个还需要行驶距离s2即station[i]
如果n-s1<s2,则需要加油,返之,不需要加油
代码中:如果s1>n,加油,s1=station[i]
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int n,k;// 加满油可行驶距离、k个加油站
cin>>n>>k;
int station[k+1];//加油站之间的距离,有k+1个
for(int i=0;i<=k;i++)
cin>>station[i];
int s1=0,number=0;//s1到达该站前需要行驶距离,number记录加油的次数
s1=station[0];//到达第一个加油站需要走s1
for(int i=1;i<=k;i++)
{
if(s1>n)//走不过去
{
cout<<"No solution!";
break;
}
else
{
s1=s1+station[i];
if(s1>n)
{
number++;//加油
s1=station[i];//加满油之后s1重新赋值,表示到下一站要行驶的距离
cout<<"在第"<<i<<" 个加油站加油"<<endl;
}
}
}
cout<<number<<endl;
return 0;
}
七、果子合并(openjudge题目)
描述:
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 11 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 33 种果子,数目依次为 11 , 22 , 99 。可以先将 11 、 22 堆合并,新堆数目为 33 ,耗费体力为 33 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 1212 ,耗费体力为 1212 。所以多多总共耗费体力 =3+12=15=3+12=15 。可以证明 1515 为最小的体力耗费值。
输入:共两行。
第一行是一个整数 n(1≤n≤10000) ,表示果子的种类数。
第二行包含 n 个整数,用空格分隔,第 i 个整数 a_i(1≤ai≤20000) 是第 i 种果子的数目。
输出:
一个整数,也就是最小的体力耗费值。
代码:
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int n,a[101],sum=0;//几堆果子,每堆果子数目
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);//从小到大排序
for(int i=2;i<=n;i++)
{
sort(a+i-1,a+n+1);//每操作完一次 就排一次序
a[i]+=a[i-1];//将第a[i]变为两堆消耗体力值的和
sum+=a[i];
}
cout<<sum<<endl;
}