贪婪算法是一种改进了的分级处理方法。其核心是根据题意选取一种量度标准。然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。这种能够得到某种量度意义下最优解的分级处理方法称为贪婪算法。
贪心算法总是作出在当前看来是最好的选择。也就是说贪心算法不从整体最优上加以考虑,它所作出的选择只是在某种意义上的局部最优选择。虽然贪心算法不是对所有问题都能得到整体最优解(0/1背包问题),但对范围相当广的许多问题它都能产生最优解。如单源最短路径问题,最小生成树问题等。
贪心方法适合的问题:它有n个输入,而它的解就由这n个输入满足某些事先给定的约束条件的某个子集组成,而把满足约束条件的子集称为该问题的可行解。显然,可行解一般来说是不唯一的。那些使目标函数取极值(极大或极小)的可行解,称为最优解。
贪心方法是求解这一类需求取最优解的问题的一种直接有效的方法。贪心方法是一种分级处理方法,它首先根据题意,选取一种量度标准。然后按这种量度标准对这n个输入排序,并按序一次输入一个量。如果这个输入量的加入,不满足约束条件,则不把此输入加到这部分解中。
对于一个给定的问题,往往可能有好几种量度标准。用其中的大多数量度标准作贪心处理所得到该量度意义下的最优解并不是问题的最优解,而是次优。选择能产生问题最优解的最优量度标准是使用贪心法设计求解的核心问题。
贪心算法设计的基本控制路线
procedure GREEDY(A,n) //A(1:n)包含n个输入// solutions←φ //将解向量solution初始化为空/ for i←1 to n do x←SELECT(A) if FEASIBLE(solution,x) then solutions←UNION(solution,x) endif repeat return(solution) end GREEDY |
SELECT 按照某种量度标准从A中选择一个输入,把它的值赋给X,并将其从A中删除
FEASIBLE 判定X是否可以包含在解向量中
UNION 将X与解向量合并,并修改目标函数
可以用贪心算法求解的问题中可以看到它们一般具有两个重要的性质:贪心选择性质和最优子结构性质。
适合用贪心法的问题应具有最优子结构性质:原问题的最优解包含了其子问题的最优解。例如背包问题,原问题的最优解是在背包的容积的限定下利润达到最大,实际上是一个单位容积利润最大的问题。它包含子问题的最优。带限期的作业调度,是在限期的约束下,利润最大。子问题得最优符合这一原则。但不是所有的问题都能找到贪心算法。例如,0/1背包问题。
已知有n种物品和一个可容纳M重量的背包,每种物品i的重量为wi。假定将物品i的一部分xi放入背包就会得到pixi的效益,这里,0≤xi≤1,pi>0。如果这些物品重量的和大于M,要求所有选中要装入背包的物品总重量不得超过M,而装入背包物品获得的总效益最大
n=3,M=20,P=(25,24,15),W (18,15,10)。最优解为(0,1,1/2) 最大效益为31.5
贪心策略: 利润/重量为量度 即每一次装入的物品应使它占用的每一单位容量获得当前最大的单位效益。这就需使物品的装人次序按pi/wi比值的非增次序来考虑。
背包问题的贪心算法
procedure KNAPSACK(P,W,M,X,n) //P(1:n)和W(1;n)分别含有按 P(i)/W(i)≥P(i+1)/W(i+1)排序的n件物品的效益值 和重量。M是背包的容量大小,而x(1:n)是解向量// real P(1:n),W(1:n),X(1:n),M,cu; integer i,n; X←0 //将解向量初始化为零// cu←M //cu是背包剩余容量// for i←1 to n do if W(i)>cu then exit endif X(i) ←1 cu←cu-W(i) repeat if i≤n then X(i) ←cu/ W(i) endif end GREEDY-KNAPSACK |
假定只能在一台机器上处理n个作业,每个作业均可在单位时间内完成,假定作业i有一个截止期限di>0(它是整数),当且仅当作业i在它的期限截止以前被完成时,则获得pi>0的效益。这个问题的一个可行解是这n个作业的一个子集合J,J中的每个作业都能在各自的截止期限之前完成。可行解的效益值是J中这些作业的效益之和,即。具有最大效益值的可行解就是最优解。
选择下一个作业的量度标准:按pi 的非增次序来考虑这些作业
n=4,(p1,p2,p3,p4)=(100,10,15,20)和 (d1,d2,d3,d4)=(2,1,2,1)
最优的处理次序是:先处理作业4,再处理作业1。
J={1}, p1=100 作业1有最大效益
J={1,4} p1+p4=120 作业4有第二大效益
作业3,2都被舍弃
算法描述
procedure GREEDY-JOB(D,J,n) //作业按p1≥p2≥…≥pn的次序输入,它们的期限值D(i)≥1,1≤i≤n,n≥1。J是在它们的截止期限前完成的作业的集合// 1 J←{1} 2 for I←2 to n do 3 if J∪{i}的所有作业都能在它们的截止期限前完成 then J←J∪{i} 4 endif 5 repeat end GREEDY-JOB |
如果J是作业的可行子集,那末以可以按如下规则来确定这些作业中的每一个作业的处理时间:若还没给作业I分配处理时间,则分配给它时间片[a-1,a],其中a应尽量取大且时间片[a-1,a]是空的(如果没有这样的时间片[a-1,a]可分配,则作业不不能计入J)。此规则就是尽可能推迟对作业的的处理。于是,在将作业一个一个地装配互J中时,就不必为接纳新作业而去移动J中那些已分配了时间片的作业。
设n=5,(p1,p2,…,p5)=(20,15,10,5,1)和(d1,d2,…,d5)=(2,2,1,3,3)
使用上述可行性规则,得:
J | 已分配的时间 | 正被考虑的作业 | 动作 |
空 | 无 | 1 | 分配[1,2] |
{1} | [1,2] | 2 | 分配[0,1] |
{1、2} | [0,1],[1,2] | 3 | 舍弃 |
{1,2} | [0,1],[1,2] | 4 | 分配[2,3] |
{1,2,4} | [0,1],[1,2] ,[2,3] | 5 | 舍弃 |
所以,最优解是J={1,2,4}..
将两分别有n个记录和m个记录的已分类文件可以在O(n+m)时间内归并成一个已分类文件。但现在假定X1,X2,X3,X4,X5是需要归并,从而得到想要的归并文件。-如何归并?(归并需要移动记录—归并的代价, 移动记录总量最少的归并方法为最优归并—代价最小
二元归并树生成算法
Procedure TREE(L,n) //L是n个单结点的二元树的表// 1 for i←1 to n-1 do 2 call getnode(T) //生成一个结点T,用于归并两棵树// 3 Lchild(T) ←LEAST(L) 4 Rchild(T) ←LEAST(L) 5 Weight(T) ←WEIGHT(Lchild(T))+WEIGHT(Rchild(T) ) 6 call INSERT(L,T) 7 repeat 8 return (LEAST(L)) 9 end TREE |
结点的带权路径长度是从根结点到该结点之间的路径长度与结点权的乘积。树的带权路径长度定义为树中所有叶子结点的带权路径长度之和WPL = ∑WiLi 1≤i≤n其中:n为叶子结点个数,Wi为第i个叶子结点的权,Li为从根到第i个叶子结点路径的长度。当引入以上概念以后,求最佳编码方案实际上就抽象为求在叶子结点个数与权确定时带权路径长度最小的二叉树。那么什么样的树带权路径长度最小呢?
对于给定n个权值w1, w2, … wn(n≥2),求一棵具有n个叶子结点的二叉树,使其带权路径长度∑WiLi最小。由于Huffman给出了构造具有这种树的方法,因此这种树称为Huffman树。
Huffman树:它是由n个带权叶子结点构成的所有二叉树中带权路径长度最小的二叉树,Huffman 树又称最优二叉树。
构造 Huffman树的算法步骤如下:
①根据给定的n个权值,构造n棵只有一个根结点的二叉树, n个权值分别是这些二叉树根结点的权,F是由这n棵二叉树构成的集合;
②在F 中选取两棵根结点树值最小的树作为左、右子树,构造一颗新的二叉树,置新二叉树根的权值=左子树根结点权值+右子树根结点权值;
③从F中删除这两颗树,并将新树加入F;
④重复②③,直到F中只含一棵树为止。
直观地看,先选择权值小的,所以权值小的结点被放置在树的较深层,而权值较大的离根较近,这样自然在Huffman树中权越大的叶子离根越近,这样一来,在计算树的带权路径长度时,自然会具有最小的带权路径长度,这种生成算法就是一种典型的贪心算法。