1、1实验 4 组合优化1. 1 八皇后问题 .11.1 八皇后问题及其串行算法 11.2 八皇后问题的并行算法 22. 2 SAT 问题 42.1 SAT 问题及其串行算法 .42.2 SAT 问题的并行算法 .53. 3 装箱问题 .73.1 装箱问题及其串行算法 73.2 装箱问题的并行算法 84. 4 背包问题 .104.1 背包问题及其串行算法 104.2 背包问题的并行算法 125. 4 TSP 问题 125.1 TSP 问题及其串行算法 .125.2 TSP 问题的并行算法 .136. 小结 157. 参考文献 168. 附录 八皇后问题并行算法的 MPI 源程序 .16组合优化问题
2、在实践中有着广泛的应用,同时也是计算机科学中的重要研究课题。本章对于八皇后问题、SAT 问题、装箱问题、背包问题及 TSP 问题等五个经典的组合优化问题,给出其定义、串行算法描述、并行算法描述以及并行算法的 MPI 源程序。1. 1 八皇后问题1.1 八皇后问题及其串行算法所谓八皇后问题(Eight Queens Problem) ,是在 8*8 格的棋盘上,放置 8 个皇后。要求每行每列放一个皇后,而且每一条对角线和每一条反对角线上最多只能有一个皇后,即对同时放置在棋盘的任意两个皇后 和 ,不允许 或者 的情况出现。1()ij2,)ij1212()()ij12()()ijij2八皇后问题的串
3、行解法为如下的递归算法:算法 16.1 八皇后问题的串行递归算法/* 从 chessboard 的第 row 行开始放置皇后 */procedure PlaceQueens(chessboard, row)Beginif row 8 thenOutputResult(chessboard) /* 结束递归并输出结果 */elsefor col = 1 to 8 do /* 判断是否有列、对角线或反对角线冲突 */(1)no_collision = true(2)i = 1(3)while no_collision and i 0 do(2.1)从某个从进程 i 接收信号 signal(2.2)
4、if signal = Accomplished then 从从进程 i 接收并记录解 end if(2.3)if has_more_boards then()向从进程 i 发送 NewTask 信号()向从进程 i 发送一个新棋盘else()向从进程 i 发送 Terminate 信号()active_slaves = active_slaves - 1end ifend whileEnd /* EightQueensMaster */(2)从进程算法procedure EightQueenSlaveBegin(1)向主进程发送 Ready 信号(2)finished = false(3)w
5、hile not finished do(3.1)从主进程接收信号 signal(3.2)if signal = NewTask then()从主进程接收新棋盘()if 新棋盘合法 then在新棋盘的基础上找出所有合法的解,并将解发送给主进程end ifelse /* signal = Terminate */finished = trueend if4end whileEnd /* EightQueenSlave */MPI 源程序请参见章末附录。2. 2 SAT 问题2.1 SAT 问题及其串行算法1.SAT 问题描述所谓可满足性问题(Satisfiability Problem)即 SA
6、T 问题,其定义为:对于一个命题逻辑公式,是否存在对其变元的一个真值赋值公式使之成立。这个问题在许多领域都有着非常重要的意义,而且对其快速求解算法的研究成为计算机科学的核心课题之一。例如在机器定理证明领域,某命题是否是一个相容的公理集合的推论,这个问题归结为该命题的反命题与该公理集合一起是否是不可满足的。特别是 1971年 Cook 首次证明了 SAT 是 NP-完全的,从而大量的计算问题都可以归结到 SAT。正是由于 SAT 重要的理论和应用地位,众多学者对它进行了广泛而深入的研究。由命题逻辑知识可以知道,任何命题逻辑公式都逻辑等价与一个合取范式(Conjunctive Normal For
7、m,简写为 CNF),因此本文只讨论 CNF 的 SAT 求解问题。下面给出几种常见的 SAT 问题化简策略,以下均假定 F 是 CNF 范式:单元子句规则:若C= L是 F 的子句,则 F 变为 F=FF/1;纯文字规则:若文字 L 出现在 F 中,而 L 不出现 F 中,则 L 称为 F 的纯文字。F 变为 F=FF/1;重言子句规则 :若 CF 且 C 是重言子句,则从 F 中删去 C得 F=F-C; 两次出现规则:若 LC 1, LC 2,且 L 和 L 都不在其它子句中出现,则将 C1 ,C 2合并为 C=( C1-L )( C2- L),F 变为 F=F- C1, C2C ;包含规
8、则:若 C1 ,C 2 是 F的子句,且 C1C 2,则 F 中删去 C2,得 F=F-C 2 。2.SAT 问题串行算法SAT 问题的 DP 算法是由 M.Davis 和 H.Putnam 于 1960 年首次提出 2,现在已经成为最著名的 SAT算法,其描述如下:算法 16.3 SAT 问题的 DP 算法输入:合取范式 F。输出:F 是否可满足。procedure DP(F)Begin(1) if F 为空公式 then 5return Yesend if(2) if F 包含一个空子句 then return No end if(3) if F 包含一个子句 l then /* 单元子句
9、规则 */return DP(Fl/1) end if (4) if F 包含一个文字 l,但不包含 l then /* 纯文字规则 */return DP(Fl/1) end if (5) 选择一个未赋值的文字 l(6) /* 拆分 */if DP(F l/1) = Yes then return Yes else return DP(F l/0) end ifEnd /* DP */可以看出,DP 算法是对回溯算法的精化,其中应用了我们在前面提到的化简策略单元子句规则和纯文字规则。前面已经介绍过,这些策略并不能在数量级上降低算法的时间复杂度,但实验证明这些策略的应用可以极大的改善平均性能。
10、其实,上面介绍的策略都可以应用于 SAT 的求解,而且已经有了这方面的工作。2.2 SAT 问题的并行算法1.并行化思路通过我们在前面对串行 DP 算法的介绍可以看出,实际上 DP 算法仍然是利用深度优先方法对可能的解空间做深度优先搜索,这样我们仍然可以把这个解空间看成一棵二叉树。而它与回溯搜索算法的区别仅仅在于它利用了 SAT 的一些性质巧妙的找到了一些仅有一种赋值可能的文字,这样就有效地减少了搜索开销。同时在搜索一棵子树时,对某个文字的赋值可能导致新的单元子句的产生,这样,从平均意义上考虑,对这一性质的反复利用可以极大地加快搜索速度。容易知道,对于寻找单元子句和纯文字的工6作是在多项式时间
11、内完成的,因此我们可以由主进程预先把 CNF 的所有单元子句和纯文字找出来,对它们分别赋上可能使 CNF 得到满足的值,并按照某种策略选取 n 个文字对他们预先赋值,共得到 2n 组解。然后把这些信息分别发送给各个从进程进行计算,并收集运算结果。这样既避免了各个从进程重复寻找单元子句和纯文字,又有可能通过对选出的 n 个文字的赋值产生了新的单元子句,从而加快了整个程序的搜索速度。2.并行 DP 算法算法 16.4 SAT 问题的并行 DP 算法输入:合取范式 F。输出:F 是否可满足。(1)主进程算法procedure PDPMaster(F)Begin(1)找出 n 个文字,并初始化任务队列
12、(2)j = 0(3)向每个从进程发送一个任务(4)while true do(4.1)某个从进程 pi 接收结果(4.2)if result = true then(i)向所有从进程发送终止信号(ii)return trueelse if (j 2n) then(i)向所有从进程发送终止信号(ii)return falseelse(i)向 pi 发送下一个任务end ifend ifend whileEnd /* PDPMaster */7(2)从进程算法procedure PDPSlaveBeginfor each processor pi,where do1ikwhile true do
13、(1)从主进程接收信号(2)if signal = task then(i)用该任务更新 F(ii)将 DP(F)的结果发送给主进程else if signal = terminal thenreturnend ifend ifend whileend forEnd /* PDPSlave */3.算法实现的说明在这里我们实际上利用了集中控制的动态负载平衡技术,由主进程控制一个任务队列。首先给每个从进程发送一个任务,然后不断从这个队列中取出任务,并通过与相应的进程应答决定是发送给它新的任务,还是结束所有进程。而从进程不断从主进程中接收信号,决定是执行新的计算任务还是结束。众所周知,DP 算法中
14、的拆分文字是非常重要的,特别是早期的文字拆分更显得举足轻重,因为早期的错误会导致多搜索一个子树,工作量几乎增加一倍。例如,Gent 和 Walsh 就给出了一个这样的例子,早期的文字选择错误导致了先求解一个具有 350,000,000 个分枝的不可满足的子问题。如果在初始的文字拆分中,提高 DP 算法的拆分文字的命中率,无疑会达到事半功倍的效果。为了提高拆分文字的命中率,编程实现中,主进程划分任务的时候,预先找出 CNF 范式中的纯文字和单元子句,这样就大大减少了需要搜索的子树数目。MPI 源程序请参见所附光盘。83. 3 装箱问题3.1 装箱问题及其串行算法经典的一维装箱问题(Bin Pac
15、king Problem)是指,给定 件物品的序列 ,物品n12,nnLa的大小 ,要求将这些物品装入单位容量 1 的箱子 中,使得每个箱)1(niai1,0()ias 1,mB子中的物品大小之和不超过 1,并使所使用的箱子数目 最小。m下次适应算法(Next Fit Algorithm)是最早提出的、最简单的一个在线算法, Joh73首次仔细分析了下次适应算法的最坏情况性能。下次适应算法维持一个当前打开的箱子,它依序处理输入物品,当物品到达时检查该物品能否放入这个当前打开的箱子中,若能则放入;否则把物品放入一个新的箱子中,并把这个新箱子作为当前打开的箱子。算法描述如下:算法 16.5 装箱问
16、题的下次适应算法输入:输入物品序列 。12,nLa输出:使用的箱子数目 m,各装入物品的箱子 。12,mPBprocedure NextFitBegin(1)s(B) = 1 /* 初始化当前打开的箱子 B,令 B 已满 */(2)m = 0 /* 使用箱子计数 */(3)for i = 1 to n doif s(ai) + s(B) 1 then(i) s(B) = s(B) + s(ai) /* 把 ai 放入 B 中 */else(i) m = m + 1 /* 使用的箱子数加一 */(ii) B = Bm /* 新打开箱子 Bm */(iii )s( B) = s(ai) /* 把
17、ai 放入 B 中 */end ifend forEnd /* NextFit */93.2 装箱问题的并行算法算法 16.5 使用一遍扫描物品序列来实现,本身具有固有的顺序性,其时间复杂度为 O(n)。我们使用平衡树和倍增技术对其进行并行化。首先利用前缀和算法建立一个链表簇,令 Ai为输入物品序列中第件物品的大小 ,如果链表指针由 Aj指向 Ak,则表示 Aj+Aj+1+Ak1 且 Aj+Aj+1+Ak-1i1;其后利用倍增技术计算以 A1为头的链表的长度,而以 A(1)为头的链表的长度即是下次适应算法所使用的箱子数目。接下来利用在上一步骤中产生的中间数据反向建立一棵二叉树,使该二叉树的叶节
18、点恰好是下次适应算法在各箱子中放入的第一个物品的序号;最后,根据各箱子中放入的第一个物品的序号,使用二分法确定各物品所放入的箱子的序号。算法 16.6 并行下次适应算法输入:数组 A1,n,其中 Ai为输入物品序列中第 i 个物品的大小。输出:使用的箱子数目 m,每个物品应放入的箱子序号数组 B1,n。procedure PNextFitBegin(1)调用求前缀和的并行算法计算 Fj= A1+A2+Aj(2)for j = 1 to n pardo借助 Fj,使用二分法计算 C0,j= maxk|Aj+Aj+1+Ak 1end for/* 以下(3)-(8) ,计算下次适应算法使用的箱子数目
19、 m */(3)C0, n+1 = n(4)h = 0(5)for j=1 to n pardo E0, j=1 end for(6)while Ch,1 n do(6.1)h = h + 1(6.2)for j = 1 to n pardoif Ch - 1, j = n then(i)Eh, j = Eh-1, j(ii)C h,j = Ch - 1,jelse(i)Eh, j = Eh - 1, j + Eh - 1,Ch - 1, j + 1(ii)C h, j = Ch - 1,Ch - 1, j + 110end ifend forend while(7)height = h(8)
20、m = E height, 1(9)/* 计算 D0, j=第 j 个箱子中第一件物品在输入序列中的编号 */for h = height downto 0 dofor j = 1 to n / 2h pardo(i)if j = even then Dh,j = Ch,Dh-1,j/2+1 endif(ii)if j = 1 then Dh,1 = 1 endif(iii )if j = odd 1 then Dh,j = Dh-1,j+1/2 endifend forend for(10)for j=1 to n pardo /* 计算 Bj = 第 j 个物品所放入的箱子序号 */使用二
21、分法计算 Bj = max k | D0,k j , D0,k+1 j 或者 k = m end forEnd /* PNextFit */MPI 源程序请参见所附光盘。4. 4 背包问题4.1 背包问题及其串行算法0-1 背包问题(0-1 Knapsack Problem)的定义为:设集合 代表 m 件物品,正整数,12Aa分别表示第 件物品的价值与重量,那么 0-1 背包问题 KNAP(A,c)定义为,求 A 的子集,使得重量之,irpwi和小于背包的容量 c,并使得价值和最大。也就是说求:(16.1)1maximisubjectowxc其中 。0,i11解决 0-1 背包问题的最简单的方
22、法是 动态规划(Dynamic Programming)算法。我们先看看KNAP(A,c)的一个子问题 KNAP( ),其中 。显然,若 并不在最优解中,,jAy12,j jayc ja那么 KNAP 的解就是 KNAP 的解。否则,KNAP 就是 KNAP 的解。1(,)jy(j )(1jjwA),(yAj设 是 KNAP 的最优解,我们得到下面的最优方法:)fj j(16.2)0110max,j jjijifyyfffwporlcndm设 ,那么,动态规划算法就是依次地计算)(,)(,0()cffFjjjj mj0来求解问题。其中 是 KNAP( ,c) 的最大价值向量。,(21cm )(
23、FjA动态规划算法是基于 Bellman 递归循环,它是求解决背包问题的最直接的方法。基于(16.2)式我们可以写出串行算法如下:算法 16.7 0-1 背包问题的串行动态规划算法输入:各件物品的价值 p1,p.m,各件物品的重量 w1,wm,背包的容量 c。输出:能够放入背包的物品的最大总价值 fm(c)。procedure Knapsack(p, w, c)Begin(1)for i = 0 to c do f0(i) = 0 end for(2)for i = 1 to m do(2.1)f i(0) = 0(2.2)for j = 1 to c doif wi j thenif pi
24、+ fi-1(j - wi) fi-1(j) thenfi(j) = pi + fi-1(j wi)else fi(j) = fi-1(j)end ifelse fi(j) = fi-1(j)end ifend forend for12End /* Knapsack */可以看出,串行的动态规划算法需要占用很大的空间,其本质是 Bellman 递归,最坏和最好的情况下的时间差不多相同。虽然,它在问题规模比较小时,可以很好的解决问题,但是,随着问题规模的扩大,串行动态规划算法就显得力不从心了。4.2 背包问题的并行算法现在,我们要做的是把串行程序改成并行程序。首先,我们分析一下串行程序的特点。注
25、意到第(2.2)步的 for 循环,f i(j)只用到上一步 fi(y)的值,而与同一个 i 循环无关,这样,可以把第(2.2)步直接并行化。得到下面的并行算法:算法 16.8 0-1 背包问题的并行算法输入:各件物品的价值 p1,p.m,各件物品的重量 w1,wm,背包的容量 c。输出:能够放入背包的物品的最大总价值 fm(c)。procedure ParallelKnapsack(p, w, c)Begin(1)for each processor do end for0jc0()fj(2)for i = 1 to m pardo(2.1)for each processor doijw1
26、()()iifjfend for(2.2)for each processor doiwjc1()max(),)iiiiifjfpend forend forEnd /* ParallelKnapsack */MPI 源程序请参见所附光盘。135. 4 TSP 问题5.1 TSP 问题及其串行算法TSP 问题(Traveling-Salesman Problem)可描述为:货郎到各村去卖货,再回到出发处,每村都要经过且仅经过一次,为其设计一种路线,使得所用旅行售货的时间最短。TSP 问题至今还没有有效的算法,是当今图论上的一大难题。目前只能给出近似算法,其中之一是所谓“改良圈算法” ,即已知
27、是 G 的 Hamilton 圈,用下面的算法把它的权减小:12.v算法 16.9 TSP 问题的改良圈算法输入:任意的一个 Hamilton 圈。输出:在给定的 Hamilton 圈上降低权而获得较优的 Hamilton 圈。procedure ApproxTSPBegin(1)任意选定一个 Hamiltion 圈,将圈上的顶点顺次记为 ,12,v则该圈为 1231,vCv(2)while 存在 ij 使得( , ) doijC1(ijijvEGif then 11)()()ijijijwvvw(2.1) 1(,ijijijCv(2.2) 将新的圈 上的顶点顺次记为 2,vend ifend
28、 whileEnd /* ApproxTSP */5.2 TSP 问题的并行算法并行算法实际上是对串行算法的改进,主要是在算法 16.9 的步骤(1)上的改进。每个从进程检查原来的圈上不同的对边,即对进程 ,检查下标索引为 i,j 的对边,(1)kn是否成立;如果成立,则记下减小的权;在每一轮上,1 1(,)(,),ijijijwvvwv选择权减小最多的对边来改进原圈,得到新的改良圈;直到不能有任何改进为止。得到的改进算法仍然14是近似算法。算法 16.10 并行 TSP 算法输入:任意不同两点之间的距离 w(i,j)。输出:在这些给定点上的一个较优的回路。(1)主进程算法procedure
29、ParallelApproxTSPMaster(w)Begin(1)任意选定一个 Hamilton 圈,将圈上的顶点顺次记为 ,12,v则该圈为 1231,vCv(2)将 C 发送给每个从进程 ()kn(3)while true do(3.1)从每个从进程 接收 (1)kw(3.2) ,min|kwn并记录最小的 所对应的处理器编号,记为 m(3.3)if then /*没有任何改进 */0向所有从进程发送终止信号return Celse将 m 发送到各处理器end ifend whileEnd /* PrallelApproxTSPMaster */(2)从进程算法procedure Par
30、allelApproxTSPSlaveBegin /* 设当前从进程的编号为 */(1)kn(1)从主进程接收 Hamilton 圈,将圈上的顶点顺次记为 ,12,v则该圈为 1231,vCv(2)accomplished = false15(3)while not accomplished do(3.1)for all 且 do1,ijv1jiif (i + j) mod n = k then111(,)(,)(,)(,)ij ijijtempwvwvvif temp thenkktepiqjend ifend ifend for(3.2) ,11(,),pqpqCvv将新的圈 上的顶点顺次
31、记为 2v(3.3)将 发送给主进程kw(3.4)从主进程接收最优改良对应的处理器编号 m(3.5)if k = 0 thenaccomplished = trueelse if k = m then向其它所有从进程发送改良圈 Celse从从进程 m 接收改良圈 Cend ifend ifend whileEnd /* ParallelTSPSlave */MPI 源程序请参见所附光盘。166. 小结本章主要讨论了八皇后、SAT、装箱、背包和 TSP 等经典组和优化问题的并行算法及其 MPI 编程实现。对这些问题读者如欲进一步学习可参考文献1、2、3、4 和5。7. 参考文献1. 朱洪 , 陈
32、增武 , 段振华, 周克成 编著. 算法设计和分析. 上海科学技术文献出版社, 19892. Davis M, Putnam H. A Computing Procedure for Quantification Theory. J. of the ACM, 1960, 7: 2012153. Gu xiaodong, Chen Guoliang, Gu Jun, Huang Liusheng, Yunjae Jung. Performance Analysis and Improvement for Some Liner on-line Bin-Packing Algorithms. J.
33、of Combinatorial Optimization, 2002,12, 6:4554714. 陈国良 , 吴明 , 顾钧. 搜索背包问题的并行分支界限算法. 计算机研究与发展, 2001.6, 38(6):7417455. 万颖瑜 , 周智 , 陈国良, 顾钧. Sizescale: 求解旅行商问题 (TSP)的新算法. 计算机研究与发展, 2002.10, 39(10):129413028. 附录 八皇后问题并行算法的 MPI 源程序1.源程序 Pqueens.c#include #include #define QUEENS 8#define MAX_SOLUTIONS 92typ
34、edef int bool;const int true = 1;const int false = 0;enum msg_contentREADY,ACCOMPLISHED,NEW_TASK,TERMINATE;enum msg_tagREQUEST_TAG,SEED_TAG,REPLY_TAG,NUM_SOLUTIONS_TAG,SOLUTIONS_TAG;int solutionsMAX_SOLUTIONSQUEENS;int solution_count = 0;17bool collides(int row1, int col1, int row2, int col2)return
35、(col1 = col2)| (col1 - col2 = row1 - row2) | (col1 + row1 = col2 + row2); /* collides */int generate_seed()static int seed = 0;doseed+; while (seed QUEENS * QUEENS - 1)return 0;elsereturn seed; /* generate_seed */void print_solutions(int count,int solutionsQUEENS)int i, j, k;for (i = 0; i solutionsi
36、j; k-)printf(“- “);printf(“n“);printf(“n“); /* print_solutions */bool is_safe(int chessboard, int row, int col)int i;for (i = 0; i = QUEENS)/* 记录当前解 */for (i = 0; i 0) MPI_Recv(solutionssolution_count,QUEENS * num_solutions,MPI_INT,child,SOLUTIONS_TAG, MPI_COMM_WORLD,19solution_count += num_solution
37、s;seed = generate_seed();if (seed) /* 还有新的初始棋盘 */* 向子进程发送一个合法的新棋盘 */MPI_Send(MPI_Send(else /* 已求出所有解 */* 向子进程发送终止信号 */MPI_Send(active_slaves-; /* while */print_solutions(solution_count, solutions); /* eight_queens_master */void eight_queens_slave(int my_rank)MPI_Status status;int ready = READY;int a
38、ccomplished = ACCOMPLISHED;bool finished = false; int request;int seed;int num_solutions = 0;int chessboardQUEENS;MPI_Send(while (! finished)/* 从主进程接收消息 */MPI_Recv(if (request = NEW_TASK)/* 从主进程接收初始棋盘 */MPI_Recv(/* 在初始棋盘基础上求解 */chessboard0 = seed / QUEENS;chessboard1 = seed % QUEENS;solution_count =
39、 0;place_queens(chessboard, 2);/* 将解发送给主进程 */MPI_Send(MPI_Send(if (solution_count 0)MPI_Send(*solutions,QUEENS * solution_count,MPI_INT,0,SOLUTIONS_TAG, MPI_COMM_WORLD);else /* request = TERMINATE */finished = true; /* whlie */ /* eight_queens_slave */* main */int main(int argc, char* argv)int nodes
40、, my_rank;MPI_Init( MPI_Comm_size(MPI_COMM_WORLD, MPI_Comm_rank(MPI_COMM_WORLD,if (nodes = 1)sequential_eight_queens();if (! my_rank)eight_queens_master(nodes);elseeight_queens_slave(my_rank);MPI_Finalize();return 0; /* main */212.运行实例编译:mpicc queen.c o queen运行:mpirun np 4 queen运行结果:Solution 1 :0 *
41、- - - - - - - 4 - - - - * - - - 7 - - - - - - - * 5 - - - - - * - - 2 - - * - - - - - 6 - - - - - - * - 1 - * - - - - - - 3 - - - * - - - - Solution 2 :0 * - - - - - - - 6 - - - - - - * - 3 - - - * - - - - 5 - - - - - * - - 7 - - - - - - - * 1 - * - - - - - - 4 - - - - * - - - 2 - - * - - - - - Solu
42、tion 3 :0 * - - - - - - - 6 - - - - - - * - 4 - - - - * - - - 7 - - - - - - - * 1 - * - - - - - - 3 - - - * - - - - 5 - - - - - * - - 2 - - * - - - - - 22(略)Solution 91 :1 - * - - - - - - 6 - - - - - - * - 2 - - * - - - - - 5 - - - - - * - - 7 - - - - - - - * 4 - - - - * - - - 0 * - - - - - - - 3 - - - * - - - - Solution 92 :1 - * - - - - - - 6 - - - - - - * - 4 - - - - * - - - 7 - - - - - - - * 0 * - - - - - - - 3 - - - * - - - - 5 - - - - - * - - 2 - - * - - - - - 说明:若指定进程数为 1,则使用串行算法 sequential_eight_queens()求解。