算法设计与分析论文.docx
《算法设计与分析论文.docx》由会员分享,可在线阅读,更多相关《算法设计与分析论文.docx(19页珍藏版)》请在冰豆网上搜索。
算法设计与分析论文
贪心算法
——不在贪心中爆发,就在贪心中灭亡
武汉理工大学计算机科学与技术学院班
摘要
ﻩ本文介绍贪心算法的基本意义以及算法的使用范围,并通过具体的案例来分析贪心算法的具体应用,从而指出其特点和存在问题.
关键字:
贪心算法,贪心策略,TSP、0/1背包
引言
我们用了13周的时间学完了《算法设计与分析》这本书。
这本书中涵盖了大量的常见算法,包括蛮力法、分治法、动态规划法、贪心算法等等。
我最有印象的就是贪心算法。
贪心算法是一种有合理的数据组织和清晰高效的算法,它简单有效。
下面我们来详细解读一下这个算法。
1.贪心算法的含义
贪心算法可以简单描述为:
对一组数据进行排序,找出最小值,进行处理,再找出最小值,再处理。
也就是说贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望得到结果是最好或最优的算法。
2.贪心算法的基本思想
贪心算法,法如其名,每次都贪心的选取当前最优解,一旦确定了当前解,不管将来有什么结果,之后都不会再修正,这一点与动态规划法比起来稍有逊色。
如果一个问题的最优解只能用蛮力法穷举得到,则贪心法不失为寻找问题近似最优解的一个较好办法。
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。
每一步只考虑一个数据,他的选取应该满足局部优化的条件.若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
3.贪心算法的基本要素
3.1贪心选择
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题.对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。
通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。
然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解.
3.2最优子结构
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质.运用贪心策略在每一次转化时都取得了最优解。
问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。
贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。
贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。
动态规划主要运用于二维或三维问题,而贪心一般是一维问题。
4.贪心算法的核心
贪心算法的核心问题是选择能产生问题最优解的最优度量标准,即具体的贪心策略。
贪心策略决定着贪心算法是爆发或者是灭亡.所以,选择一个合理、正确的贪心策略是至关重要的。
下面用例子说明:
第一个例子我们选用大家熟知的0/1背包问题。
给定
种物品和一个背包.物品
的重量是
其价值为
,背包的容量为
。
在选择物品
装入背包时,不可以选择物品
的一部分,一定要全部装入背包,
.应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
设
表示物品
装入背包的情况,根据问题的要求,有如下约束条件和目标函数:
ﻩ
ﻩ
ﻩ
于是,背包问题归结为寻找一个满足约束条件式,并使目标函数式达到最大的解向量
。
现在有一个容量为50的背包,共有三个物品,重量为别为20、30、10,价值分别为60、120、50。
现使用贪心算法来放置物品,贪心策略也对应有三种,分别是根据重量、价格、重量与价格比。
根据重量的贪心策略,即优先放置重量小的物品,以此来达到放置更多个物品的目的。
首先需要将物品按照重量从小到大重新排序,然后从小到大的放置物品,直至下个物品无法放进背包为止。
伪代码如下:
publicclass worseGreedy {
ﻩpublicstaticvoidDepWeight(double[][]a,doublec, int[]ans)// depend
//on
ﻩ//the
//weight toselect
//goods
{
ﻩdouble[] w=newdouble[a[0].length];
ﻩSystem。
arraycopy(a[0], 0, w,0,w.length);
ﻩEnumerationSearchsea= newEnumerationSearch();
ﻩﻩQuick.Sort(w);
ﻩfor (int i =0;w[i]<=c&&i<w.length; i++){
ﻩc=c- w[i];
ﻩﻩﻩint j =sea.search(a[0], w[i]);
ﻩans[j]=1;
}
}
程序的运行结果见附录。
根据价格的贪心策略,即优先放置价格比较高的物品,以此来达到背包价格更高的目的。
首先需要将物品按照价格从大到小重新排序,然后依次放置物品,直至下个物品超出背包容量为止.伪代码如下:
publicstaticvoid DepPrice(double[][]a,doublec,int[]ans) // dependon
// theprice
ﻩ//toselectgoods
ﻩ{
ﻩdouble[]p=newdouble[a[1].length];
ﻩSystem。
arraycopy(a[1],0,p,0,p.length);
ﻩﻩEnumerationSearchsea=new EnumerationSearch();
ﻩQuick.Sort(p);
ﻩint i=(p。
length—1);
ﻩintj=sea.search(a[1], p[i]);
ﻩﻩwhile(a[0][j]<= c&&i〉=0) {
ﻩﻩc=c-a[0][j];
ﻩﻩans[j]= 1;
ﻩﻩi--;
ﻩj= sea。
search(a[1], p[i]);
}
ﻩ}
程序的运行结果见附录.
根据重量与价格比值的贪心策略,即优先放置“性价比”高的物品,以此来达到背包价格最大的目的.首先需要求出各个物品的价格与重量的比值,然后按照这个参数来重新排序物品,价重比高的放前面。
最后一次放入物品,直至下个物品超出背包容量为止。
伪代码如下:
publicstaticvoid DepWePr(double[][] a, doublec, int[]ans){//dependon
ﻩﻩﻩﻩﻩﻩﻩ// the
ﻩﻩﻩﻩﻩﻩﻩ//weight
ﻩﻩﻩﻩﻩﻩﻩﻩ//andprice
ﻩdouble[]w =newdouble[a[0]。
length]; //theweight ofgoods
ﻩSystem。
arraycopy(a[0],0,w,0, w.length);//copy the array
ﻩdouble[] p=newdouble[a[0]。
length];//thepriceofgoods
ﻩSystem.arraycopy(a[1],0,p, 0, p.length);//copythe array
double[]pw=newdouble[w.length];
ﻩfor(int i =0;i 〈w.length;i++)
ﻩﻩ//price/weight
ﻩﻩpw[i]= p[i]/w[i];
ﻩﻩdouble[][]wpw= new double[2][w.length];//record thetableof
ﻩﻩﻩﻩﻩﻩﻩﻩﻩﻩ// weightandpw
ﻩSystem.arraycopy(w,0,wpw[0],0,w.length);
ﻩSystem.arraycopy(pw,0,wpw[1], 0,w.length);
ﻩEnumerationSearchsea = newEnumerationSearch();
Quick.Sort(pw);
ﻩint i=(pw。
length—1);
intj= sea.search(wpw[1], pw[i]);
ﻩwhile(wpw[0][j]<=c&&i〉=0){
c= c-wpw[0][j];
ﻩﻩans[j]=1;
ﻩﻩi—-;
ﻩj =sea。
search(wpw[1],pw[i]);
ﻩﻩ}
}
程序运行结果见附录。
根据程序运行的结果,我们分别得到三个答案.放置的物品方案分别为:
、
、
。
如此看来,根据价格的贪心策略是最“贪心”的。
其实不然,根据价格与重量比值的贪心策略的适应范围更加广阔,效果一般来说也更加的好。
本例中受到小数据的局限性,无法发挥出其真正的效果。
贪心策略之所以能成为贪心算法的核心,就是因为它决定着贪心算法是爆发还是灭亡,以及爆发的程度。
上面的例子用三种不同的贪心策略,结果也截然不同。
当扩大问题规模时,这种差距就更加的明显。
5.贪心算法的特点
货郎担问题(或称旅行商问题或巡回售货员问题),是NP问题中的著名问题。
它通常的提法是:
货郎到各村去卖货,再回到出发点处,每村到且仅到一次。
为其设计一种路线,使得所用旅行售货的时间最短。
建立数学模型时,把村庄设为结点,村庄与村庄之间的路线设为结点之间的带权的边,则货郎担问题转化为在图上寻求最优
圈问题。
即在加权图上求一个
圈
使得
ﻩ
贪心算法每选入一边都保证了是当前可选边中权值最小的边,这便是“贪心”的含义.基本思想为首先选择图中任意顶点u,并将u置于顶点集合的子集S中。
如果S是顶点集合V的真子集,就继续选择顶点j添加到子集S中,c[i][j]是权值最小的边。
按照同样的步骤不断进行贪心选择,直到子集S=V为止.此时,选取到的所有的边就构成了一棵最小生成树。
伪代码如下:
publicvoidfind(int[][] distance,int[]ans)
{
ﻩﻩintj=1;//startingcity
ﻩfor(inti=0;i<distance。
length;i++)
{
ﻩﻩans[i]=j;
j=min(distance[j-1],ans);
ﻩ}
}
privateintmin(int[]a,int[]b)
ﻩ{
ﻩﻩintminN=0;
ﻩﻩinttemp=100;
EnumerationSearchsea=newEnumerationSearch();
for(inti=0;i<a。
length;i++)
ﻩ{
ﻩif(sea。
search(b,(i+1))==—1)
ﻩif(a[i]〈temp)
ﻩﻩﻩ{
ﻩﻩﻩtemp=a[i];
ﻩﻩﻩﻩﻩminN=i;
ﻩﻩﻩ}
}
ﻩminN++;
ﻩreturnminN;
ﻩ}
程序运行结果见附录.
同时我们运用动态规划法来求解同样的问题,问题规模为8个城市.动态求解过程需要使用10ms的时间,而贪心算法只需要使用1ms以内的时间即可完成计算,我们可以理解到贪心算法的爆发力了.但是,它们的结果并不相同。
动态规划法求解的答案为:
,而贪心算法的答案为:
.动态规划法需要走的路程为21,而贪心算法需要走的路程为25。
这就是贪心算法的特点,虽快但不一定准.贪心算法每次都是局部寻优,很容易会陷入局部最优解的波谷。
所以,贪心算法总结起来一共有三个小缺点.
1)不能保证求得的最后解是最佳的。
由于贪心策略总是采用从局部看来是最优的选择,因此并不从整体上加以考虑.
2)贪心算法只能用来求某些最大或最小解的问题.从前面的讨论中,背包问题要求得到最大价值是可行的,但是在另外一个求解最小路径时采用贪心算法得到的结果并不是最佳。
3)贪心算法只能确定某些问题的可行性范围。
贪心算法的优点已经提到了,就是快,通常是线性到二次式,不需要多少额外的内存。
一般二次方级的存储要浪费额外的空间,而且那些空间经常得不出正解.但是,使用贪心算法时,这些空间可以帮助算法更容易实现且更快执行.如果有正确贪心性质存在,那么一定要采用!
因为它容易编写,容易调试,速度极快,并且节约空问。
几乎可以说,此时它是所有算法中最好的。
6.结束语
贪心算法是很常见的算法,贪心策略是最接近人的日常思维的一种解题策略,虽然它不能保证求得的最后解一定是最佳的,但是它可以为某些问题确定一个可行性范围。
贪心算法所作的选择依赖于以往所作过的选择,但决不依赖于将来的选择,这使得算法在编码和执行过程中都有一定的速度优势。
对于一个问题的最优解只能用穷举法得到时,用贪心算法是寻找问题最优解的较好算法。
对一个问题可以同时用几种方法解决,贪心算法并不是对所有的问题都能得到整体最优解或是最理想的近似解时,就需判断贪心性质的正确性了.与回溯法、动态规划法等比较,它的适用区域相对狭窄许多。
总之,如果一个贪心解决方案存在,就使用它。
7.参考文献:
[1]肖衡。
浅析贪心算法[J]。
荆楚理工学院学报。
2009.09
[2]常友渠,肖贵元,曾敏.贪心算法的探讨与研究[J].重庆电力高等专科学校学报. 第13卷,第3期
[3]汪莹.论贪心算法在图论中的应用[J].南京邮电大学学报
[4]董军军。
动态规划算法和贪心算法的比较与分析[J]。
软件导刊.ﻩﻩ 第7卷 第2期
[5]林章美.货郎担问题的若干解法[J]。
[6]王红梅.算法设计与分析[M].北京:
清华大学出版社.2006.7
[7]江红,余青松.
程序设计教程[M].
8.附录
源代码:
packageAlgorithm.ZeroPack。
Console;
importjava。
util.Scanner;
importjava.util。
Arrays;
importAlgorithm。
ZeroPack.Greedy.*;
publicclassConsole {
/**
* @paramargs
*/
ﻩpublic static voidmain(String[]args){
ﻩ//TODO Auto-generatedmethodstub
ﻩScanner in= newScanner(System。
in);//inputsomething
ﻩint num; //thenumberof items
ﻩﻩdoublec;//the capacityof therucksack
ﻩSystem。
out.println("Howbigis thisrucksack capacity?
”);
ﻩﻩc=in.nextDouble();
ﻩﻩSystem.out。
println(”Howmanuitems?
”);
ﻩnum=in.nextInt();
intans1[]=newint[num]; // the answer
ﻩint ans2[]= newint[num];// theanswer
ﻩﻩintans3[]=newint[num];//theanswer
ﻩﻩfor(inti= 0;i <num;i++)//initialization
ﻩ{
ﻩﻩans1[i]=0;
ﻩans2[i]= 0;
ﻩﻩans3[i] = 0;
ﻩ}
doubletable[][]=new double[2][num];//theweightandprice
ﻩﻩSystem.out。
println(”pleaseinputthe table”);
ﻩﻩfor (inti=0;i〈num; i++){ﻩﻩ//inputthe table
ﻩSystem。
out.println("Theweight ofNO."+(i+ 1) +”is:
");
table[0][i] =in.nextDouble();
ﻩSystem.out.println("ThepriceofNO."+(i+1)+ "is:
");
table[1][i] = in.nextDouble();
ﻩ}
ﻩworseGreedy。
DepWeight(table, c,ans1);
ﻩSystem。
out.println(”Dependon theweight”+Arrays。
toString(ans1));
ﻩworseGreedy.DepPrice(table,c,ans2);
ﻩSystem.out。
println(”Depend on the price"+Arrays.toString(ans2));
ﻩbetterGreedy.DepWePr(table,c, ans3);
ﻩﻩSystem。
out。
println("Depend ontheweightandprice”+Arrays.toString(ans3));
ﻩﻩSystem.out。
println(”over");
}
}
packageAlgorithm。
ZeroPack。
Greedy;
import QuickSort.Quick;
importSearch.EnumerationSearch;
publicclassbetterGreedy{
publicstaticvoidDepWePr(double[][]a,doublec,int[]ans){//depend on
ﻩﻩﻩﻩﻩﻩﻩﻩﻩﻩ//the
ﻩﻩﻩﻩﻩﻩﻩﻩ//weight
ﻩﻩﻩﻩﻩﻩﻩﻩﻩﻩﻩ//and price
ﻩdouble[]w=new double[a[0]。
length];//theweightofgoods
ﻩﻩSystem.arraycopy(a[0], 0, w,0,w.length);//copythearray
ﻩdouble[]p=newdouble[a[0].length];// thepriceofgoods
System。
arraycopy(a[1],0,p,0, p.length); // copythearray
double[]pw= newdouble[w.length];
for(inti=0; i〈w。
length;i++)
ﻩﻩ//price/weight
ﻩﻩﻩpw[i]=p[i]/w[i];
ﻩﻩdouble[][]wpw=newdouble[2][w.length]; //recordthe table of
ﻩﻩﻩﻩﻩﻩﻩﻩﻩﻩ//weightandpw
ﻩSystem.arraycopy(w,0,wpw[0],0,w.length);
ﻩSystem.arraycopy(pw, 0,wpw[1],0,w.length);
ﻩﻩEnumerationSearch sea=newEnumerationSearch();
ﻩﻩQuick。
Sort(pw);
ﻩinti=(pw。
length-1);
ﻩﻩintj=sea.search(wpw[1],pw[i]);
while(wpw[0][j]<=c &&i>=0){
c=c—wpw[0][j];
ﻩﻩans[j]=1;
ﻩi-—;
ﻩﻩj=sea.search(wpw[1],pw[i]);
ﻩ}
}
}
packageAlgorithm。
ZeroPack。
Greedy;
import QuickSort.Quick;
importSearch.EnumerationSearch;
publicclassworseGreedy{
ﻩpublicstaticvoid DepWeight(double[][]a,doublec,int[]ans)//depend
//on
//the
//weighttoselect
//goods
ﻩ{
double[]w= newdouble[a[0].length];
ﻩSystem.arraycopy(a[0],0, w, 0,w.length);
EnumerationSearch sea = newEnumerationSearch();
ﻩﻩQuick。
Sort(w);
ﻩﻩfor(int i= 0;w[i]〈= c&&i<w.length;i++) {
ﻩc= c —w[i];
ﻩintj=sea。
search(a[0],w[i]);
ﻩﻩans[j]=1;
ﻩ}
}
publicstaticvoidDepPrice(double[][]a,doublec,int[]ans)//depend on
ﻩ//theprice
ﻩ// toselectgoods
{
ﻩﻩdouble[]p =newdouble[a[1]。
length];
System.arraycopy(a[1],0,p,0,p.length);
ﻩEnumerationSearchsea =newEnumerationSearch();
ﻩﻩQuick.Sort(p);
ﻩinti=(p.length— 1);
ﻩintj=sea。
search(a[1],p[i]);
ﻩﻩwhile (a[0][j] 〈= c&&i〉=0) {
ﻩc=c-a[0][j];
ﻩans[j]= 1;
ﻩﻩﻩi--;
ﻩj=sea。
search(a[1],p[i]);
ﻩ}
ﻩ}
}
package QuickSort;
public class Quick {
publicstaticvoidSort(double[]a){
ﻩﻩQuickSort(a,0,a.length—1);
ﻩ}ﻩ
privatestatic voidQuickSort(doubler[],intfirst ,intend)
{
ﻩint pivot;
if(first〈end)
ﻩ{
ﻩpivot=Partition(r,first,en