第八章 分支限界法 §1 算法基本思想 本章叙述中为了区别图中的顶点和解空间树中的顶点, 凡是在解 空间树中出线队顶点一律称为结点。 分支限界法同回溯法类似, 它也是在解空间中搜索问题的可行解 或最优解,但搜索的方式不同。回溯法采用深度优先的方式,朝纵深 方向搜索,直至达到问题的一个可行解,或经判断沿此路径不会达到 问题的可行解或最优解时,停止向前搜索,并沿原路返回到该路径上 最后一个还可扩展的结点。从该结点出发朝新的方向纵深搜索。分支 定界法则采用宽度优先的方式搜索解空间树, 它将活结点存放在一个 特殊的表中。其策略是:在扩展结点处 ,先生成其所有的儿子结点, 将那些导致不可行解或导致非最优解的儿子舍弃, 其余儿子加入活结 点表中。此后,从活结点表中 取出一个结点 作为当前扩展结点。并重 复上述结点扩展过程。所以,分支限界法与回溯法的本质区别在于搜 索方式的不同。回溯法更适于处理那些求所有可行解的问题,而分支 限界法更适于处理那些只确定一个可行解,特别是最优化问题。 从活结点表中选择下一扩展结点的不同方式导致不同的分支限 界法。最常见的有以下两种方式: 1).队列式(FIFO)分支限界法:将活结点表组织成一个队列, 并按队列的 先进先出 原则选取下一个结点作为当前扩展结点。 2).优先队列式分支限界法: 将活 结点表组织成一个优先队列, 并按优先队列给结点规定的优先级选取 优先级最高的下一个结点作 为当前扩展结点。 队列式分支限界法的搜索解空间树的方式类似于解空间树的宽 度优先搜索,不同的是队列式分支限界法不搜索以不可行结点(已经 被判定不能导致可行解或不能导致最优解的结点)为根的子树。这是 因为,按照规则,这样的结点未被列入活结点表。 优先队列式分支限界法的搜索方式是根据活结点的优先级确定 下一个扩展结点。 结点的优先级常用一个与该结点有关的数值p 来表 示。最大优先队列规定p值较大的结点点的优先级较高。在算法实现 时通常用一个最大堆来实现最大优先队列, 用最大堆的 Deletemax 运 算抽取堆中的下一个结点作为当前扩展结点, 体现最大效益优先的原 则。类似地,最小优先队列规定p值较小的结点的优先级较高。在算 法实现时,常用一个最小堆来实现,用最小堆的Deletemin 运算抽取 堆中下一个结点作为当前扩展结点,体现最小优先的原则。采用优先 队列式分支定界算法解决具体问题时, 应根据问题的特点选用最大优 先或最小优先队列,确定各个结点点的p 值。 例1 旅行商问题,n=4,其解空间树是一棵排列树。如文档“旅 行商搜索树 ” 。赋权图G 给出如下: 30 6 10 求赋权图G的 5 4 最小权Hamilton圈 20 采用队列式分支限界法以排列树中的结点B 作为初始扩展结点。 1 4 2 3 此时,活结点队列为空。由于从图 G 的顶点 1 到顶点 2、3 和 4 均有 边相连,B 的儿子 C、D 和 E 都是可行结点,它们被依次加入到活结 点队列中,并舍弃当前扩展结点 B。当前活结点队列中的队首结点 C 成为下一个扩展结点。由于图G的顶点2到顶点3和4 有边相连,故 结点C 的二个儿子 F 和G 均为可行结点,可以加入活结点队列。接下 来,结点 D和结点 E相继成为扩展结点。此时活结点队列中的结点依 次为 F、G、H、I、J、K。结点 F 成为下一个扩展结点,但其儿子 L 是解空间树的叶结点,我们找到了一条Hamilton圈(1,2,3,4,1) , 其费用为 59。下一个扩展结点 G 的儿子 M 也是叶结点,得到另一条 Hamilton 圈(1,2,4,3,1) ,其费用为 66。结点 H 成为当前扩展 结点,其儿子N也是叶结点,得到第三条Hamilton圈,其费用为 25, 这是当前知道的最短 Hamilton 圈。下一个扩展结点是 I,由于从根 结点到结点 I 的费用 26 已经超过当前最优值,故没有必要扩展 I, 以I为根的子树被剪掉。最后J和K 被依次扩展,活结点队列称为空 集,算法终止。算法搜索到的最优值是 25,相应的最优解是从根结 点到结点N 的路径(1,3,2,4,1) 。 采用优先队列式分支限界法,用一个最小堆存储活结点表,优先 级函数是结点的当前费用。 算法还是排列树的结点B和空优先队列开 始。结点 B 被扩展后,它的 3 个儿子 C、D 和 E 被依次插入堆中。此 时,由于 E 是堆中具有最小当前费用(为4)的结点,所以处于堆顶 的位置,它自然成为下一个扩展结点。结点E 被扩展后,其儿子 J 和 K 被插入当前堆中,它们的费用分别为 1 4 和 24。此时,堆顶元素是 D,它成为下一个扩展结点。它的2 个儿子H 和I被插入堆中。此时, 堆中元素是结点 C、H、I、J、K。在这些结点中,H 具有最小费用, 成为下一个扩展结点。扩展结点H后得到一条 Hamilton圈(1,3,2, 4,1) ,相应的费用为25。接下来,结点J 成为扩展结点,并由此得 到一条Hamilton圈(1,4,2,3,1) ,费用仍为25。此后的两个扩 展结点是 K 和 I。由结点 K 得到的 Hamilton 圈的费用高于当前所知 最小费用,结点I 当前的费用已经高于当前所知最小费用,因而,它 们都不能得到最优解。最后,优先队列为空,算法终止。 §2 优先级的确定与 LC-检索 对于优先队列式分支限界法, 结点优先级的确定直接影响着算法 性能。我们当然希望这样的活结点X 成为当前扩展结点: 1).以X 为根的子树中含有问题的答案结点; 2).在所有满足条件1)的活结点中,X 距离答案结点“最近” 。 但是,要给出可能导致答案结点的活结点附以优先级,必须要附加若 干计算工作,即要付出代价。对于任一结点,要付出的代价可以使用 两种标准来度量: (i).在生成一个答案结点之前,子树X 需要生成的结点数; (ii).在子树X 中离X 最近的那个答案结点到X 的路径长度。 容易看出,如果使用度量(i), 在对于每一种分枝限界算法,总是生 成最小数目的结点;如果采用度量(i i),则要成为扩展结点的结点只 是由根到最近的那个答案结点路径上的那些结点。用c(·)表示一个 理想的优先级函数,定义如下: a).如果 X 是答案结点,则 c(X)是解空间树中,由根结点到 X 的 成本(即所用的代价,如级数、计算复杂度等); b).如果X不是答案结点,而且以 X为根的子树中不含答案结点, 则c(X)定义为 ∞; c).如果X 不是答案结点,但是以 X 为根的子树中含答案结点,则 c(X)是具有最小成本的答案结点的成本。 如果能够获得这样的优先级函数, 则优先队列式分支限界法将以 最少的搜索时间找到问题的解。然而,这样的优先级函数的计算工作 量不亚于解原问题。实际问题中都是采用一个估计函数 ?,它由两部 分决定:解空间树的根结点到 X 的成本 f(X)和 X 到答案结点的估计 成本g(X),即 ?(X)= f(X)+g(X). ? 称为成本估计函数。根据成本估 计函数选择下一个扩展结点的策略总是选取 ?(·)值最小的活结点作 为下一个扩展结点。这种搜索策略称为最小成本检索(Least Cost search) ,简称LC-检索。如果取g=0,f(X)等于X在解空间树中的级, 则LC-检索即是宽度优先搜索(BFS);如果f=0,且g 满足: Y是X的儿子 ? g(Y)≤ g(X) (8.2.1) 则LC-检索即是深度优先搜索(DFS).在以后所提到的成本函数 ? 中, 函数g 都满足(8.2.1)式。 例子 15 迷问题 在一个分成 4×4 的棋盘上排列 15 块号牌,如图(a)。其中会出 现一个空格。 棋盘上号牌的一次合法移动是指将位于空格上、 下、 左、 右的一块号牌移入空格。要求通过一系列合法移动,将号牌的初始排 列转换成自然排列,如图(b). 1 3 4 15 1 2 3 4 # # 2 5 12 5 6 7 8 # # 7 6 11 14 9 10 11 12 # # 8 9 10 13 13 14 15 # # 图 (a) 图 (b) 图 (c) 如果将棋盘的各个位置按照号牌的自然排列发给序号, 右下角发 给 16 号。则号牌的每一种排列都可以看作是 0,1,…,15 这 16 个 数的排列,其中,0 的位置代表空格。如图(a)对应的排列是 (1,3,4,15,2,0,5,12,7,6,11,14,8,9,10,13).事实上, 不是号牌的每 一种排列都能够经过一系列合法移动转换成自然排列的。用Less(i) 记排列P 中位于i 后面且号比i 小的号牌数, 则排列P 可以经一系列 合法移动转换成自然排列的充要条件是 ∑ ≤≤ + 151 )( i XiLess 是偶数 (8.2.2) 其中,当空格在图(c)的某个#位置时, X=1; 否则 X=0.以后记 ∑ ≤≤ = 151 )()( i iLessPτ ,称为排列P的逆序数。例如,图(a)中排列的逆序 数为16, X=0,所以图(a)中排列可经一系列合法移动转换成自然排列。 在处理实际问题中,一般是根据具体问题的特性,确定成本估 计函数 ?(X)= f(X)+g(X)中的函数f(X)和g(X).在本例中, 我们令f(x) 是由根到结点X 的路径的长度,g(X)是以X 为根的子树中,由X 到目 标状态(自然排列)的一条最短路径长度的估计值。为此,g(X)至少应 是把状态 X转换成目标状态所需的最小移动数。 对它的一种可能的选 择是 g(X)= 排列X的不在自然位置的牌的数目 (8.2.3) 图(a)的排列中,不在自然位置的牌号分别是:3,4,15,2,5,12, 7,6,14,8,9,10,13,共13个。g(X)=13.将图(a)中的排列转换 成自然排列至少需要13次合法的移动。可见,估价函数 ?(X)是函数 c(X)的下界。按照这样成本估价函数,则 15 迷问题解空间树的搜索 过程可以从文档“15 迷空间树” 的图(1)中看出。 首先以结点1作为扩展结点,生成4 个儿子:2,3,4,5后死 去。这4 个儿子的成本估值分别为: ?(2)=1+4; ?(3)=1+4; ?(4)=1+2; ?(5)=1+4.因而结点 4 成为当前扩展 结点,生成 4 个儿子,但有一个 儿子同其祖父相同被舍弃。剩下的 3 个儿子的成本估值分别为 ?(10)=2+1; ?(11)=2+3; ?(12)=2+3,此时,活结点表中共有 6 个结 点:2,3,5,10,11,12,其中结点 10的成本估值最小。故以结点 10 作为新的扩展结点。它能生成 3 个儿子,其中有一个因同其祖父 相同而被舍弃。剩下的 2 个儿子是结点 22 和 23。但结点 23 所表示 的号牌排列是自然的,至此得到可行解。 如果采用队列式分枝限界法,即采用宽度优先搜索,势必将图 (1)中的所有状态全部搜索。如果采用深度优先搜索,则图(2)中也只 给出了一部分搜索步骤,而且结点 12 表示的号牌排列与自然排列还 相距甚远。 由此看出选择合适的优先级函数对于分枝限界法效率的影 响甚大。 值得注意的是按照成本估价函数 ?(X)确定的优先级进行搜索, 所得到的答案结点未必是最小成本答案结点。例如,下面的解空间树 中,每个结点X旁有两个数:上面的是最下成本c(X),下面的是成本 估价 ?(X)。整个解空间树中有两个答案结点。如果按照估价函数进 行搜索,根结点 1 首先成为扩展结点,然后是结点 2,在扩展结点 2 时,得到一个答案结点4,它时结点 2的一个儿子。这个答案结点的 成本是20,比另一个答案结点6 的成本大。 10 0 20 10 2 4 20 ∞ ∞ 10 20 ∞ ∞ 10 定理 8.1 在有限的解空间树中,如果对每对结点X 和Y,成本函 数c与成本估价函数 ? 均满足: “c(X) < c(Y)” => “?(X) < ?(Y)” , 则按照成本估价函数搜索能够达到最小成本答案结点。 一般情况下,不易找到定理 8.1 中要求的成本估价函数。对于成 本估价函数,一般有一个基本要求: ?(X)≤ c(X), 对任意结点 X; (8.2.4) ?(X)=c(X),当X是答案结点时。 (8.2.5) 在以下给出的算法中, 要求搜索一直继续到一个答案结点变成扩 展结点为止。 搜索最小成本答案结点LC-检索 LeastCost(T, ?)//T 是问题的解空间树 1 E=T; //第一个扩展结点 2 置活结点表为空; 1 2 3 65 4 7 3 loop 4 if E 是答案结点 then 5 输出从E 到T 的路径; return; 6 endif 7 for E的每个儿子X do 8 Add(X); Parent(X)=E; 9 endfor 10 if 不再有活结点 then 11 print(‘no answer node’); stop; 12 endif 13 Least(E); 14 endloop 15 end LeastCost 其中,Least(X)是从活结点表中找一个具有最小 ? 值的活结点,从活 结点表中删除该结点,再将该结点赋给变量X;Add(X)是将新的活结 点加到活结点表中。Parent(X)将活结点X 与它的父亲相连接。 在算法LeastCost中,由于答案结点 E是扩展结点,所以,对于 活结点表中的每个结点L均有 ?(E)≤ ?(L).再由假设 ?(E)=c(E), ?(L) ≤ c(L),得c(E) ≤ c(L),即E 是一个最小成本答案结点。 综上所述,采用优先队列式分枝限界 法,其搜索解空间树的算法 主要决定于三个因素:约束函数、限界函数和优先级函数。前两项主 要是限制被搜索的结点数量,而优先级 函数则是用来确定搜索的次 序。但是,在处理实际问题中,未必把这三种函数全部列出来,特别 是优先级函数往往同限界函数合为一体。 以下给出处理极小化最优问 题的优先队列式分枝限界法的一般流程。 找最小成本结点的LC-分枝限界算法 LCBB(T, ?,u, ε,cost) //假定解空间树 T 包含一个解结点且 ?(X) ≤ //c(X) ≤ u(X)。c(X)是最小成本函数, ?(X)是成本估价函数, //u(X)是限界函数;当X是答案结点时, cost(X)是X所对应的解 //的成本。 ε 是一个充分小的正数。 1 E=T; Parent(E)=0; 2 if T 是解结点 then U=min(cost(T),u(T)+ε); ans=T; 3 else U=u(T)+ε; ans=0; 4 endif 5 将活结点表初始化为空集; 6 loop 7 for E 的每个儿子X do 8 if ?(X)<U && X是一个可行结点 then 9 Add(X); Parent(X)=E; 10 case 11 :X是解结点 && cost(X)<U: 12 U=min(cost(X),u(X)+ε); ans=X; 13 :u(X)+ε<U: 14 U=u(X)+ε; 15 endcase 16 endif 17 endfor 18 if 不再有活结点 or 下一个扩展结点满足 ?≥U then 19 print(‘least cost=’,U); 20 while ans≠0 do 21 print(ans); ans=Parent(ans); 22 endwhile 23 return; 24 endif 25 Least(E); 26 endloop 27end LCBB 这里我们将选择扩展结点的任务交给函数Least(E).所谓的“成本” 是指目标函数的值。所以,当X是可行解的答案结点时,c(X)表示该 可行解的目标函数值;当X 是不可行结点时,c(X)= ∞;当 X 是可行结 点但不是答案结点时, c(X)表示以X 为根的子树中 最小成本结点 的成 本。此时,成本估价函数 ?(X)即变成对于结点 X(作为部分解)的目 标函数值的一个估计。我们要求 ?(X) ≤ c(X)。算法中的U 是一个上 界值,开始时,U 可以取为一个充分大的数。 例如 ,假定一个可行解需要确定 n 个数 n xxx ,,, 21 L,而每个取 值 i x 会使得成本开销增加 ii xc ,于是,一个可行解X= ),,,( 21 n xxxL的 总开销为 ∑ ≤≤ = ni ii xcXc 1 )( .若当前只确定了前 k 个数 k xxx ,,, 21 L,用 结点X 表示当前之一状态, 则以这k 个数为部分解的可行解的开销一 定不小于 ∑ ≤≤ = ki ii xcXc 1 )(? ,不大于 u(X)= + ∑ ≤≤+ njk jj xp 1 ∑ ≤≤ ki ii xc 1 ,其中 i x 是 i x 可取的最大值。这样选取的函数 ?(X)和 u(X)就会满足上面我 们提出的要求: ?(X) ≤ c(X)≤ u(X)。 从上面的例子可以看出:如果X 是成本值为cost(X)的解结点, 且cost(X)<U,则可用min{cost(X),u(X)}更新U的值; 或者X 不是解 结点,但 u(X)< U,可用u(X)更新U的值。如果 U是某个可行解(或 答案结点)的成本值(开销) ,则当 ?(X)≥ U 时,可以杀死结点 X。 但是,当 U 只是一个简单的上界,不 是可行解的成本值时,应该谨慎 考虑 ?(X)=U 的情况。因为,如果 X 不是答案结点,则 X 的某个子孙 Y可能是答案结点,以X为根的子树中含有答案结点。如果前面盲目 地杀死结点X,则可能丢失一些最小成本答案结点。所以,在这种情 况下,不能杀死结点X。为此,在用 u(X)更新U值时,常常多加一个 微量 ε( ε的选取应当满足: “u(X)<u(Y) ? u(X)+ ε <u(Y)” ) , 即用u(X)+ ε 更新U。于是,U 的更新可以描述为:设E 是当前扩展结点,X 是 E 的一个儿子: (1). 当 X 是一个解结点,而且 cost(X)<U 时,可用 min(cost(X),u(X)+ ε)来更新U; (2).当X不是解结点, 而且u(X)+ δ<U时, 直接用u(X)+ δ 更新U。 由于函数u(X)是对于结点X 的成本值上界的一个估计,具有某 种预测功能,常常用它来作为优先级函数。 §3. 0/1 背包问题的分枝限界法 我们分两种方式解这一问题: a).转化成极小化问题,利用 LC- 分枝限界法解决;b).采用优 先队列式分枝限界法,直接解极大化问 题。 (一).转化成极小化问题 nix Mxw xp i ni ii ni ii ,,2,1},1,0{ )min( 1 1 L=∈ ≤ ? ∑ ∑ ≤≤ ≤≤ (8.3.1) 解空间树是n+1 级的完整二叉树。 若从根结点到叶结点 X的路径形成 问题的一个可行解,则叶结点X是答案结点。为了使最小成本答案结 点与最优解相对应,需要对每个答案结点 X 定义 ∑ ≤≤ ?= ni ii xpXc 1 )( ; 对于其余的叶结点X则定义 ∞=)(Xc ;而对于非叶结点X将递归地定 义为 )}(),(min{)( rl XcXcXc = ,其中 rl XX , 分别是 X 的左儿子和右 儿子。以下构建限界函数 )(? Xc 和优先级函数 )(Xu ,使得 )()()(? XuXcXc ≤≤ .假设X 是j级上的一个结点,1 ≤j≤n+1。在结点 X 处已经对前 j-1 个变量 i x 确定取值,则当前背包的成本值 c 为 ∑ <≤ ? ji ii xp 1 ,背包中现有物品的重量w 为 ∑ <≤ ji ii xw 1 。在这种情况下,以 X 为根的子树中答案结点的成本应该不小于 LB,不大于 UB,其中 LB 和UB 分别由下面两个算法确定: 确定UB的算法 UBValue(c,w,j,M)//c,w,j,M的含义同前面说明, W[i],P[i]分别、 //代表第 i 件物品的重量和价值,并假定物品的序号是按 //W[i]/P[i]≥ W[i+1]/P[i+1]的次序排出的。 global W[1:n],P[1:n]; integer i,j,n; real a,b; a=w;b=c; for i from j to n do if a+W[i]≤ M then a=a+W[i]; b=b-P[i]; endif endfor return (b); end UBValue 确定LB 的算法 LBValue(c,w,j,M)//c,w,k,M 的含义同前面说明,W[i],P[i]分别 //代表第 i 件物品的重量和价值,并假定物品的序号是按 //W[i]/P[i]≥ W[i+1]/P[i+1]的次序排出的。 global W[1:n],P[1:n]; integer i,j,n; real a,b; a=w;b=c; for i from j to n do if a+W[i]≤ M then a=a+W[i]; b=b-P[i]; else return (b-(M-a)P[i]/W[i]); endif endfor return (b); end UBValue 于是,可以取LB 和UB 分别为 )(? Xc 和 )(Xu 。 例子 考虑 0/1 背包问题 : ),18,12,10,10(),,,(,4 4321 == ppppn 15),9,6,4,2(),,,( 4321 == Mwwww . 利用上面所定义的 )(? ?c 和 )(?u ,LC-分枝限界法 LCBB 搜索解空间 树过程如下: 开始将根作为 E-结点, 32)1(,38)1(? ?=?= uc .由于它不是答案 结点,因此 LCBB 置 ans=0 和 U=-32+ ε;扩展此结点,生成它的两个 儿子结点 2 和 3。经计算得 32)2(,38)2(? ?=?= uc ,32)3(? ?=c 22)3( ?=u 。将结点 2,3 放入活结点表,而且因为 )3()2( uu < ,结 点2被选为下一个扩展结点。扩展结点2,生成两个儿子结点4 和5。 计算得 32)4(,38)4(? ?=?= uc , ,36)5(? ?=c 22)5( ?=u 将结点 4,5 放入活结点表,目前活结点表中共有三个结点,而以结点4的成本上 界最小,所以,结点4被选为下一个扩展结点。扩展结点4,生成两 个儿子结点 6 和 7。计算得 ,38)6(? ?=c 32)6( ?=u , ,38)7(? ?=c 38)7( ?=u ,将它们放入活结点表。注意结点 6 和 7 的成本上界均小 于当前 U 值,U 的值应该更新。采用 ε+?38 更新 U 的值。目前活结 点表中共有四个结点,而以结点7 的成本上界最小,所以结点7 被选 为当前扩展结点。扩展结点 7,生成两个儿子结点 8 和 9。计算得 38)8(,38)8(? ?=?= uc , ,20)9(? ?=c 20)9( ?=u .因为结点 8 是答案结 点,而且由 Uct <?== 38)8(?)8(cos ,应该用-38 更新 U。由于 Uc >?= 20)9(? ,所以结点 9 被杀死,只有结点 8 放入活结点表。目 前,活结点表中共有4个活结点,依照它们的成本上界值从小到大排 列,依次是:8,6,3,5。下一个扩 展结点是 8。但是,8 是答案结 点,而且 Uc =)8(? ,程序终止检索,打印出值-38和路径8,7,4, 2,1,算法结束。由路径转换成解,还应该在算法 LCBB 的实现过程 中保留一些能反映 i x 取值情况的附加信息。 一种解决的办法是在每个 解增设一个位信息段tag,由答案结点到根结点的这一系列tag位给 出这些 i x 的值。例如,上面问题中,tag(2)=tag(4)=tag(6) =tag(8)=1;tag(3)=tag(5)=tag(7)=tag(9)=0.对应于路径8,7,4, 2,1 的一系列 tag 是 1011,因此,所得解是 1 4 =x , ,1,0 23 == xx 1 1 =x . -38 -32 x 1 =1 x 1 =0 x -38 -32 -32 -22 x 2 =1 x 2 =0 -38 -36 -32 -22 x 3 =1 x 3 =0 -38 -38 -32 -38 x 4 =1 x 4 =0 -38 -20 -38 -20 算法LCBB的执行过程 上述将0/1 背包问题化为极小化问题,采用LC-分枝限界法求解的算 法的详细步骤可以从下面直接解极大化问题的算法中获得,此略。 1 2 4 5 7 3 8 6 9 (二). 用优先队列式分枝限界法直接解极大化问题 LC-分枝限界法是优先队列式分枝限界法的基本形式之一,因为 任何极大化问题均可以转化成极小化问题, 正像前面解决0/1背包问 题一样。从上面的算法分析中可见,用优先队 列式分枝限界法解决 0/1 背包问题(作为极大化问题),需要确定以下四个问题:解空间树 中结点的结构、如何生成一个给定结点的儿子、如何组织活结点表、 如何识别答案结点。 我们采用完整的二叉树作为解空间树, 放在活结点表中的每个结 点具有6 个信息段:Parent、Level、Tag、Cu、Cv、Pvu。其中Parent 是结点X 的父亲结点连接指针;Level标志出结点X在解空间树中的 级数,通过置 1 )( = XLevel X 表示生成 X 的左儿子,置 0 )( = XLevel X 表 示生成X 的右儿子;信息段 Tag用来出处最优解各个分量 i x 的值;信 息段 Cu 记录背包在结点 X 处的可用空间(即剩余空间),在确定 X 左 儿子的可行性时用;Cv 记录在结点X 处背包中已装物品的价值(或效 益值),等于 ∑ <≤ )(1 XLeveli ii xp ;信息段Pvu表示在结点X所表示的状态下, 可行解所能达到的可能值的上界。也即是说,当 11 ,, ?l xxL的值确定 后,可行解 nll xxxxLL,,,, 11 ? 所能达到的效益值的上界。类似地, 当 11 ,, ?l xxL的值确定后,可行解 nll xxxxLL,,,, 11 ? 所能达到的最大 效益值的下界记做 Pvl。如果到目前为止所知道的可行解的最大效益 值不小于 CL,则当 Pvu<CL 时,就应该杀死结点 X。所以,Pvu(X)可 以作为限界函数。同时,我们将以Pvl 作为优先级函数。关于它们的 计算将由一个子程序给出。 作为极大化问题处理的优先队列 式分枝限界法解 0/1 背包问题 的程序 LCKNAP,采用了六个子程序:LUBound、NewNode、Finish、 Init、GetNode 和 Largest。子程序 LUBound 计算 Pvl 和 Pvu 之用; NewNode生成一个具有六个信息段的结点,给各个信息段置入适当的 值并将此结点加入结点表;Finish 打印出最优解的值和此最优解中 1= i x 的物品;Init 对可用结点表和活结点表置初值;GetNode 取一 个可用结点;Largest 在活结点表中取一个具有最大Pvl 值结点作为 下一个扩展结点。 0/1背包问题的优先队列式分子限界算法 LCKNAP(P,W,M,N,ε)//假定物品的排列顺序遵循 //P[i]/W[i]≥P[i+1]/W[i+1]; real P[1:N],W[1:N],M,CL,Pvl,Pvu,cap,prof; integer ANS,X,N; 1. Init;//初始化可用结点表及活结点表 2. GenNode(E);//生成根结点 3. Parent(E)=0;Level=1;Cu(E)=M;Cv(E)=0; 4. LUBound(P,W,M,N,1,Pvl,Pvu); 5. CL=Pvl- ε;UB(E)=Pvu; 6. loop 7. i=Level(E); cap=Cu(E);prof=Cv(E); 8. case 9. :i=N+1: //解结点 10. if prof>CL then 11. CL=prof; ANS=E; 12. endif 13. :else://E有两个儿子 14. if cap≥W[i] then //左儿子可行 15. NewNode(E,i+1,1,cap-W[i],prof+P[i],UB(E)); 16. endif 17. LUBound(P,W,cap,prof,N,i+1,Pvl,Pvu); 18. if Pvu>CL then //右儿子会活 19. NewNode(E,i+1,0,cap,prof,Pvu); CL=max(CL,Pvl-ε); 20. endif 21. endcase 22. if 不再有活结点 then exit endif 23. Largest(E);//取下一个扩展结点 24. until UB(E)≤ CL; endloop 25. Finish(CL,ANS,N); 26. end LCKNAP 算法中有两点值得注意: (1).第6~24行的循环依次检查所生成 的每个结点。此循环在以下两种情况下终止:或者不再剩有活结点, 或者为了扩展而选择的结点 E(扩展结点)满足UB(E) ≤ CL.在后一种 情况下,由扩展结点的选法可知,对所有的扩展结点 X 均有 UB(X) ≤ UB(E) ≤ CL,因而它们都不能导致其值比 CL 更大的解。(2).在左儿 子 X 可行的情况下,由 LUBound 算出它的上界,并由此而得 UB(X)=UB(E).因为UB(E)>CL或者CL=Pvu- ε<Pvu,所以将 X加入活结 点表。由于左儿子的下界、上界与E 的相同,因而不必再计算。但是 右儿子则不同,所以需要调用函数 LUBound 来获取 UB(Y)=Pvu.如果 Pvu≤CL,则杀死结点Y。否则,将结点Y 加入活结点表,并修改 CL 的 值(第19 行) 。以下附上前面提到的几个子程序。 计算结点状态下的可能取得最大效益值的上、下界 LUBound(P,W,rw,cp,N,k,Pvl,Pvu)//rw 是背包的剩余容量, cp是已 //取得的效益值,还有物品k,…,N 要考虑 Pvl=cp; c=rw; for i from k to N do if c<W[i] then Pvu=Pvl+c*P[i]/W[i]; for j from i+1 to N do if c≥W[j] then c=c-W[j]; Pvl=Pvl+P[j]; endif endfor return endif c=c-W[i]; Pvl=Pvl+P[i]; endfor Pvu=Pvl; end LUBound 生成新结点算法 NewNode(par,lev,t,cap,prof,ub)//生成一个新结点J,并把它加到 //活结点表 GetNode(J); Parent(J)=par;Level(J)=lev;Tag(J)=t; Cu(J)=cap; Cv(J)=prof; UB(J)=ub; Add(J); end NewNode 打印答案程序 Finish(CL,ANS,N)//输出解 real CL; global Tag,Parent; print(‘OBJECTS IN KNAPSACK ARE’) for j from N by –1 to 1 do if Tag(ANS)=1 then print(j); endif ANS=Parent(ANS); endfor end Finish §4. 电路板布线问题 印刷电路板将布线区域分成 n×m 个方格阵列,如下图(a).精确的电 路布线问题要求确定连接方格a的中点到方格b的中点的最短布线方 案。在布线时,电路只能沿直线或直角布线,如 下图(b)所示。为了 避免线路相交,已布了线的方格做了封锁标记,其它线路不允许穿过 被封锁的方格。 图(a) 图(b) 我们采用队列式分枝限界法解此问题,它的解空间树是图。首先结点 a作为第一个扩展结点。与该扩展结点相邻并且可以可达的方格成为 可行结点被加入到活结点队列,这些结点被加入队列的顺序是:右、 下、左、上。将这些方格标记为1,它表示从起始方格 a 到这些方格 的距离为1。 接着从活结点队列中取出队首结点作为下一个扩展结点, 并将与当前结点相邻且未标记过的方格标记 未 2,并以右、下、左、 上的顺序将这些结点存放到活结点队列中。 这个过程一直持续到算法 搜索到目标方格b 或者活结点队列为空时停止。注意到,搜索过程在 遇到标记封锁的方格时,自动放弃此方格。下图是在 7×7 方格阵列 中布线的例子。 其中, 起始点的位置是a=(3,2), 目标是位置b=(4,6). 有!号的方格表示被封锁。搜索过程如下图(c)所示。 a b 3 2 !! 2 1 !! !! 1 a 1 2 !! 2 1 2 !! !! b !! 2 3 4 !! !! !! !! 5 6 7 8 !! !! !! 6 7 8 图 (c) 图 (d) 本例中,a 到b 的最短距离是9。要构造出与最短距离相应的最短路 径,须从目标方格开始向起始方格回溯,逐步构造出最优解。每次向 比当前方格标号少 1的相邻方格移动, 直至到达起始方格为止。 图(d) 给出了该例子的最短路径,它是从目标方格 b 移动到(5,6),然后移 至(6,6),…,最终移至起始方格a。 在算法实现时,首先定义一个表示电路板上方格位置的类 Position,它有两个私有成员row 和 col,分别表示方格所在的行和 列。在电路板的任一方格处,布线可沿右、下、左、上四个方向进行, 分别记为移动 0,1,2,3。offs et[i].col=1 表示向右前进一步; offset[i].col=-1 表示向左前进一步。在这两种情况下,都有, offset[i].row=0。类似地讨论向下和向上前进的情况。 一般用一个二维数组 grid 表示所给的方格阵列。初始时, grid[i][j]=0,表示该方格允许布线, 而grid[i][j]=1表示该方格不 允许布线(有封锁标记) 。为了便于处理边界方格的情况,算法对所 给的方格阵列的四周设置一道“围墙” ,即增设标记为1 的附加方格。 算法开始时测试初始方格与目标方格是否相同。 若相同, 则不必计算, 直接返回最短距离 0。否则,算法设置方格阵列的围墙,初始化位移 !! !! !! a !! !! !! b !! !! !! !! !! !! !! !! 矩阵offset。算法将起始位置的距离记为2。因为数字 0和1用于表 示方格的开放或封闭状态,所以,表示距离不用这两个数字,因而将 所有的距离值都加 2。实际距离应为 标记距离减 2。算法从起始位置 start开始, 首先标记所有标记距离为3的方格, 并存入活结点队列, 然后依次标记所有标记距离为4,5,… 的方格,直至到达目标方格 finish 或活结点队列为空时为止。 布线问题的队列式分枝限界算法 bool FindPath(Position start,Position finish,int& PathLen, Position * &path) {//计算从起点位置start到目标位置finish的最短布线路径, 找到 //最短布线路径则返回true,否则返回false if((start.row==finish.row) && (start.col==finish.col) {PathLen=0; return true;} //start=finish //设置方格阵列“围墙” for(int I=0; I<= m+1; I++) grid[0][I]=grid[n+1][I]=1; //顶部和底部 for(int I=0; I<= n+1; I++) gridi][0]=grid[i][m+1]=1; //左翼和右翼 //初始化相对位移 Position offset[4]; offset[0].row=0; offset[0].col=1;//右 offset[1].row=1; offset[1].col=0;//下 offset[2].row=0; offset[2].col=-1;//左 offset[3].row=-1; offset[3].col=0;//上 int NumOfNbrs=4;//相邻方格数 Position here,nbr; Here.row=start.row; Here.col=start.col; Grid[start.row][start.col]=2; //标记可达方格位置 LinkedQueue<Position> Q; Do {//标记相邻可达方格 For(int I=0; I<NumOfNbrs; I++){ Nbr.row=here.row + offset[I].row; Nbr.col=here.col+offset[I].col; If(grid[nbr.row][nbr.col]==0){ //该方格未被标记 grid[nbr.row][nbr.col]=grid[here.row][here.col]+1; if((nbr.row==finish.row) && (nbr.col==finish.col)) break; //完成布线 Q.Add(nbr);} } //是否到达目标位置finish? If((nbr.row==finish.row) (nbr.col==finish.col)) break;//完成布线 //活结点队列是否非空? If(Q.IsEmpty()) return false;//无解 Q.Delete(here);//取下一个扩展结点 }while(true); //构造最短布线路径 PathLen=grid[finish.row][finish.col]-2; path=new Position[PathLen]; //从目标位置finish 开始向起始位置回溯 here=finish; for(int j=PathLen-1; j>=0; j--){ path[j]=here; //找前驱位置 for(int I=0; I<NumOfNbrs; I++){ nbr.row=here.row+offset[I].row; nbr.col=here.col+offset[I]; if(grid[nbr.row][nbr.col]==j+2) break; } here=nbr;//向前移动 } return true; } 由于每个方格成为活结点进入活结点队列最多 1 次, 活结点队列 中最多只处理O(mn)个活结点。扩展每个活结点需要O(1)时间,因此 共耗时O(mn)。构造相应的最短距离需要O(L)时间,其中,L是最短 路径的长度。 作业 :试写出0/1 背包问题的优先队列式分枝限界算法程序,并 找一个物品件数为16 的例子检验程序的运行情况。