贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。1
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。1
基本要素贪心选择贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。1通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。34
最优子结构当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是处理一维问题。2
基本思路思想贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加使算法停止。3
过程建立数学模型来描述问题;3
把求解的问题分成若干个子问题;3
对每一子问题求解,得到子问题的局部最优解;4
把子问题的解局部最优解合成原来解问题的一个解。3
算法特性贪心算法可解决的问题通常大部分都有如下的特性:3
随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。2
有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。4
还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。4
选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。4
最后,目标函数给出解的值。4
为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪心算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。3如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪心算法正确工作,那么找到的第一个解通常是最优的。1
应用举例与示例代码背包问题有一个背包,背包容量是M=150kg。有7个物品,物品不可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。1
物品 A B C D E F G2
重量 35kg 30kg 6kg 50kg 40kg 10kg 25kg2
价值 10$ 40$ 30$ 50$ 35$ 40$ 30$2
分析:2
目标函数:∑pi最大1
约束条件是装入的物品总重量不超过背包容量:∑wi 64输出一个解,返回上一步骤c--2
(x,y) ← c2
计算(x,y)的八个方位的子结点,选出那些可行的子结点2
循环遍历所有可行子结点,步骤c++重复22
显然⑵是一个递归调用的过程,大致如下:2
C++程序:2
#define N 8void dfs(int x,int y,int count){ int i,tx,ty; if(count>N*N) { output_solution();//输出一个解 return; } for(i=0; i10)or(y>10)or(x1>10)or(y1>10)Thenwriteln('Error'){判断是否越界}ElseFind(x,y);Writeln('Total:',total){打出总数}END.这样做是完全可行的,它输入的是全部解,但是马遍历当8×8时解是非常之多的,用天文数字形容也不为过,这样一来求解的过程就非常慢,并且出一个解也非常慢。2
怎么才能快速地得到部分解呢?4
【贪心算法】4
其实马踏棋盘的问题很早就有人提出,且早在1823年,J.C.Warnsdorff就提出了一个有名的算法。在每个结点对其子结点进行选取时,优先选择‘出口’最小的进行搜索,‘出口’的意思是在这些子结点中它们的可行子结点的个数,也就是‘孙子’结点越少的越优先跳,为什么要这样选取,这是一种局部调整最优的做法,如果优先选择出口多的子结点,那出口少的子结点就会越来越多,很可能出现‘死’结点(顾名思义就是没有出口又没有跳过的结点),这样对下面的搜索纯粹是徒劳,这样会浪费很多无用的时间,反过来如果每次都优先选择出口少的结点跳,那出口少的结点就会越来越少,这样跳成功的机会就更大一些。4这种算法称为为贪心算法,也叫贪婪算法或启发式算法,它对整个求解过程的局部做最优调整,它只适用于求较优解或者部分解,而不能求最优解。这样的调整方法叫贪心策略,至于什么问题需要什么样的贪心策略是不确定的,具体问题具体分析。实验可以证明马遍历问题在运用到了上面的贪心策略之后求解速率有非常明显的提高,如果只要求出一个解甚至不用回溯就可以完成,因为在这个算法提出的时候世界上还没有计算机,这种方法完全可以用手工求出解来,其效率可想而知。4
均分纸牌#include#include#includeint a[1000];using namespace std;int f(int n){ int ave=0; int f=0; for (int i=1;iprice/$temp->weight; if($res>$temres){ $x[$j]=$x[$j+1]; $x[$j+1]=$temp; } } } }//贪心算法function tanxin($x,$totalweight=50){ $len=count($x); $allprice=0; for($i=1;$iweight>$totalweight) break; else{ $allprice+=$x[$i]->price; $totalweight=$totalweight-$x[$i]->weight; } } if($iprice*($totalweight/$x[$i]->weight); return $allprice;}tsort($x);//按非递增次序排序display($x);//显示echo '0-1背包最优解为:';echo tanxin($x);本词条内容贡献者为:
孙锐 - 教授 - 合肥工业大学