您的位置:首页 > 其它

贪心算法及几个经典例子

2018-02-28 15:05 387 查看
贪心算法适用的问题      贪心策略适用的前提是:局部最优策略能导致产生全局最优解。    实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。例题分析[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。物品 A  B  C  D  E  F  G重量 35 30 60 50 40 10 25价值 10 40 30 50 35 40 30记得当时学算法的时候,就是这个例子,可以说很经典。分析:目标函数: ∑pi最大约束条件是装入的物品总重量不超过背包容量,即∑wi<=M( M=150)(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?(2)每次挑选所占重量最小的物品装入是否能得到最优解?(3)每次选取单位重量价值最大的物品,成为解本题的策略?贪心算法是很常见的算法之一,这是由于它简单易行,构造贪心策略简单。但是,它需要证明后才能真正运用到题目的算法中。一般来说,贪心算法的证明围绕着整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。对于本例题中的3种贪心策略,都无法成立,即无法被证明,解释如下:(1)贪心策略:选取价值最大者。反例:W=30物品:A  B  C重量:28 12 12价值:30 20 20根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。(3)贪心策略:选取单位重量价值最大的物品。反例:W=30贪心算法适用的问题
      贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
    实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。
例题分析
[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品 A  B  C  D  E  F  G
重量 35 30 60 50 40 10 25
价值 10 40 30 50 35 40 30
记得当时学算法的时候,就是这个例子,可以说很经典。
分析:
目标函数: ∑pi最大
约束条件是装入的物品总重量不超过背包容量,即∑wi<=M( M=150)
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占重量最小的物品装入是否能得到最优解?
(3)每次选取单位重量价值最大的物品,成为解本题的策略?
贪心算法是很常见的算法之一,这是由于它简单易行,构造贪心策略简单。但是,它需要证明后才能真正运用到题目的算法中。一般来说,贪心算法的证明围绕着整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。
对于本例题中的3种贪心策略,都无法成立,即无法被证明,解释如下:
(1)贪心策略:选取价值最大者。反例:
W=30
物品:A  B  C
重量:28 12 12
价值:30 20 20
根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。
(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。
(3)贪心策略:选取单位重量价值最大的物品。反例:
W=30
物品:A  B  C
重量:28 20 10
价值:28 20 10
根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。
值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。比如,求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。[均分纸牌]有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数.可以在任一堆上取若干张纸牌,然后移动。移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。例如:n=4,4堆纸牌分别为:① 9 ② 8 ③ 17 ④ 6 移动三次可以达到目的:从③取4张牌放到④ 再从③区3张放到②然后从②去1张放到①。
输入输出样例:4
9 8 17 6
屏幕显示:3
算法分析:设a[i]为第I堆纸牌的张数(0<=I<=n),v为均分后每堆纸牌的张数,s为最小移动次数。
我们用贪心算法,按照从左到右的顺序移动纸牌。如第I堆的纸牌数不等于平均值,则移动一次(即s加1),分两种情况移动:
1.若a[i]>v,则将a[i]-v张从第I堆移动到第I+1堆;
2.若a[i]<v,则将v-a[i]张从第I+1堆移动到第I堆。
为了设计的方便,我们把这两种情况统一看作是将a[i]-v从第I堆移动到第I+1堆,移动后有a[i]=v; a[I+1]=a[I+1]+a[i]-v.
在从第I+1堆取出纸牌补充第I堆的过程中可能回出现第I+1堆的纸牌小于零的情况。
如n=3,三堆指派数为1 2 27 ,这时v=10,为了使第一堆为10,要从第二堆移9张到第一堆,而第二堆只有2张可以移,这是不是意味着刚才使用贪心法是错误的呢?
我们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张,第二堆剩下-7张,在从第三堆移动17张到第二堆,刚好三堆纸牌都是10,最后结果是对的,我们在移动过程中,只是改变了移动的顺序,而移动次数不便,因此此题使用贪心法可行的。
【乘船问题】
贪心法:
描述 
进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别。一条独木舟最多只能乘坐两个人,且乘客的总重量不能超过独木舟的最大承载量。我们要尽量减少这次活动中的花销,所以要找出可以安置所有旅客的最少的独木舟条数。现在请写一个程序,读入独木舟的最大承载量、旅客数目和每位旅客的重量。根据给出的规则,计算要安置所有旅客必须的最少的独木舟条数,并输出结果。
输入 
第一行输入s,表示测试数据的组数; 
每组数据的第一行包括两个整数w,n,80<=w<=200,1<=n<=300,w为一条独木舟的最大承载量,n为人数; 
接下来的一组数据为每个人的重量(不能大于船的承载量); 
输出 
每组人数所需要的最少独木舟的条数。
样例输入 

85 6 
5 84 85 80 84 83 
90 3 
90 45 60 
100 5 
50 50 90 40 60
样例输出 


3
详细的解说可以见《算法竞赛入门》P231; 
思路很简单, 
1)将所有人的重量进行排序; 
2)从当前最轻的重量i开始考虑,找能跟其坐一只船的最重的人j; 
3)比那个最重的人j都重的人都单独坐一个船;
现在关键的问题是,用这么简单的贪心法得到的是否是最优解? 
可以用反证法: 
1.假设i不与任何人同船,如果将j拉来与其同船,用的船数会小于等于原来的船数; 
2.假设i与k同船,因为j是与i匹配的最重的,所以w(k)<=w(j),则j加入其它船可能会使其它船超重,用的船数会变多; 


#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

int main(){
freopen("a.txt","r",stdin);//

int s, w, n;
cin >> s;
while (s--){
cin >> w >> n;

vector<int> weights(n);
for (int i = 0; i < n;i++){
cin >> weights[i];
}

sort(weights.begin(),weights.end());

int res = 0;

int i = 0, j = n - 1;
while (i<=j){//
//用i<j的循环终止条件不行;
//比方说,当满足第一个if条件导致j--,使i==j,但此时i表示的人还有分配船,所以遗漏了;
if (i==j){
res++;
break;
}
if (weights[i]+weights[j]>w){
res++;
j--;
}
else {
res++;
i++;
j--;
}
}

cout << res<<endl;
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: