- 3.86 MB
- 2022-08-30 发布
- 1、本文档由用户上传,淘文库整理发布,可阅读全部内容。
- 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,请立即联系网站客服。
- 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细阅读内容确认后进行付费下载。
- 网站客服QQ:403074932
1S(n)=0n≤1\n第4章分治法91它比2logn小。如同4.4节所述,INSERTIONSORT在n小于16的情况下执行是相当快的,因此,QUICKSORT2可以在每当q-p<16时用INSERTIONSORT来加速。QUICKSORT和MERGESORT的平均情况时间都是O(nlogn),在平均情况下哪个更快一些呢?这里不给出其理论证明,而是给出一组在IBM370/158机上测试的数据来作近似的比较。这两个算法都是用PL/1编程的递归模型,对于QUICKSORT,PARTITION中划分元素采用了三者取中的规则(即划分元素是A(m),A(m+p-1)/2和A(p-1)中值居中者)。输入数据集由(0,10000)的随机整数组成。表4.3记录了以毫秒为单位的实际平均计算时间。研究这个表立即就可看出,对应于n的所有取值,QUICKSORT都比MERGESORT快。而且还可看到,每当n值增加500,QUICKSORT的平均计算时间大致增加250ms;MERGESORT则不规则一些,n每增加500,其平均计算时间大约增加350ms。表4.3分类算法的平均计算时间/msn10001500200025003000350040004500MERGESORT500750105014001650200022502650QUICKSORT40060085010501300155018002050n50005500600065007000750080008500MERGESORT29003450350038504250455049505200QUICKSORT230026502800300033503700390041004.6选择问题上述PARTITION算法也可用来求选择问题的有效解。在这一问题中,给出n个元素A(1∶n),要求确定第k小的元素,如果划分元素v测定在A(j)的位置上,则有j-1个元素小于或等于A(j),且有n-j个元素大于或等于A(j)。因此,若kj,则第k小元素是A(j+1∶n)中第(k-j)小元素。所导出的算法是过程SELECT(算法4.15)。此过程把第k小元素放在A(k),并划分剩余的元素,使得A(i)≤A(k),1≤i 0,使得k1k-ikTA(n)≤cn+∑TA(n-i)+∑TA(i-1)n≥2n1≤i n,因此SELECT2的复杂度不是O(n)。如果取r=9,则至少有2.5\n96计算机算法基础(n/9)个元素小于或等于v,且至少也有这么多个元素大于或等于v。因此,对于n≥90,|S|1和|R|都至多是n-2.5(n/9)+[2.5(n/9)]=n-1.25(n/9)≤31n/36+1.25≤63n/72。2从而可以得到下面的递归式:c1nn<90T(n)=T(n/9)+T(63n/72)+c1nn≥90其中,c1是一个适当的常数。可以施归纳法于n证明:对于n≥1,有T(n)≤72c1nSELECT2所需要的附加空间除了几个简单变量所需要的空间外,还需要作为递归栈使用的空间。由于步骤7的递归调用是这次执行SELECT2的最后一条语句,故容易将它消去递归。因此,仅步骤4的递归需要栈空间。递归的最大深度是logn,所以需要O(logn)的栈空间。4.6.3SELECT2的实现在用SPARKS写实现SELECT2的算法以前,需要解决两个问题:①怎样找大小为r的集合的中间值?②将步骤3所找到的n/r个中间值放在什么地方?由于所使用的r值都不大(例如r=5或9),因此,可用算法4.7的改进型INSERTIONSORT(A,i,j)对每组的r个元素实行有效的分类。分类后的在A(i,j)中间的那个元素就是这r个元素的中间值。把各组所找到的中间值存放在包含所有组全部元素的那个数组的前部,这对于处理步骤4是很方便的。例如,如果正在找A(m∶p)的第k小元素,就将这些中间值依次存放在A(m),A(m+1),A(m+2),⋯中。实现SELECT2的SPARKS描述是过程SEL(算法4.17)。在SEL中,通过第6行和21行的loop—repeat以及16行到21行的语句等价代换了步骤7的递归调用。INTERCHANGE(x,y)仅是交换x和y的值。算法4.17SELECT2的SPARKS描述lineprocedureSEL(A,m,p,k)∥返回一个i,使得i∈[m,p],且A(i)是A(m∶p)中第k小元素,r是一个全程变量,其取值为大于1的整数∥1globalr2integern,i,j3ifp-m+1≤rthencallINSERTIONSORT(A,m,p)4return(m+k-1)5endif6loop7n←p-m+1∥元素数∥8fori←1to|n/r|do∥计算中间值∥9callINSERTIONSORT(A,m+(i-1)*r,m+i*r-1)∥将中间值收集到A(m∶p)的前部∥10callINTERCHANGE(A(m+i-1),A(m+(i-1)*r+|r/2|-1))11repeat\n第4章分治法9712j←SEL(A,m,m+|n/r|-1,||n/r|/2|)∥mm∥13callINTERCHANGE(A(m),A(j))∥产生划分元素∥14j←p+115callPARTITION(m,j)16case17∶j-m+1=k∶return(j)18∶j-m+1>k∶p←j-119∶else∶k←k-(j-m+1);m←j+120endcase21repeat22endSEL4.7斯特拉森矩阵乘法二维数组无论在数值还是在非数值计算领域中都是一种相当基本而又极其重要的抽象数据结构,矩阵则是它的数学表示,因此,在研究矩阵的基本运算时,尽可能改进运算的效率无疑是件非常重要的工作。矩阵加和矩阵乘是两种最基本的矩阵运算。设A和B是两个n×n矩阵,这两个矩阵相加指的是它们对应元素相加作为其和矩阵的相应元素,因此它们的和矩阵仍是一个n×n2矩阵,记为C=A+B。其时间显然为Θ(n)。如果将矩阵A和B的乘积记为C=AB,那么C也是一个n×n矩阵,乘积C的第i行第j列的元素C(i,j)等于A的第i行和B的第j列对应元素乘积的和,可表示为C(i,j)=∑A(i,k)B(k,j)1≤i,j≤n(4.6)1≤k≤n2按上式计算C(i,j)需要做n次乘法和n-1次加法,而乘积矩阵C有n个元素,因此,由矩3阵乘定义直接产生的矩阵乘算法的时间为Θ(n)。人们长期以来对改进矩阵乘法的效率作过不少尝试,设计了一些改进算法,但在计算时3间上都仍旧囿界于n这一数量级。直到1969年斯特拉森(V.Strassen)利用分治策略并加上一些处理技巧设计出一种矩阵乘算法以后,才在计算时间数量级上取得突破。他所设计2.81的算法的计算时间是O(n)。此结果在第一次发表时曾震动了数学界。下面介绍斯特拉森矩阵算法的基本设计思想与主要处理技巧。为简单起见,假定n是2的幂,即存在一非负整数k,使得n=2。在n不是2的幂的情况下,则可对A和B增加适当的全零行和全零列,使其变成级是2的幂的方阵。按照分治设计策略,首先可以将A和B都分成4个(n/2)×(n/2)矩阵,于是A和B就可以看成是两个以(n/2)×(n/2)矩阵为元素的2×2矩阵。对这两个2×2矩阵施以通常的矩阵乘法运算(即通过(2.6)式计算乘积矩阵元素),可得A11A12B11B12C11C12=(4.7)A21A22B21B22C21C22其中,\n98计算机算法基础C11=A11B11+A12B21,C12=A11B12+A12B22,C21=A21B11+A22B21,C22=A21B12+A22B22(4.8)使用通常的矩阵乘法和加法,计算(n/2)×(n/2)矩阵C11,C12,C21和C22的各元素的值以及C=AB各乘积元素的值,可以直接证明C11C12C=AB=C21C22如果分块子矩阵的级(这里是n/2级方阵)大于2,则可以继续将这些子矩阵分成更小的方阵,直至每个子方阵只含一个元素,以至可以直接计算其乘积为止。这样的算法显然是由分治策略设计而得的。为了用式(4.8)计算AB,需要执行以(n/2)×(n/2)矩阵为元素的82次乘法和4次加法。由于每两个n/2级方阵相加可在对于某个常数c而言的cn时间内完成,如果所得到的分治算法的时间用T(n)表示,则可以得到下面的递归关系式:bn≤2T(n)=28T(n/2)+dnn>2其中,b和d是常数。3求解这个递归关系式得到T(n)=O(n),与通常的矩阵乘算法计算时间具有相同的数32量级。由于矩阵乘法比矩阵加法的花费要大(O(n)对O(n)),斯特拉森发现了在分治设计的基础上使用一种减少乘法次数而让加减法次数相应增加的处理方法来计算式(4.8)中的Cij。其处理方法是,先用7个乘法和10个加(减)法来算出下面7个(n/2)×(n/2)矩阵:P=(A11+A22)(B11+B22)Q=(A21+A22)B11R=A11(B12-B22)S=A22(B21-B11)(4.9)T=(A11+A12)B22U=(A21-A11)(B11+B12)V=(A12-A22)(B21+B22)然后用8个加(减)法算出这些Cij:C11=P+S-T+VC12=R+T(4.10)C21=Q+SC22=P+R-Q+U以上共用7次乘法和18次加(减)法。由T(n)所得出的递归关系式是bn≤2T(n)=(4.11)27T(n/2)+ann>2其中,a和b是常数。求解这个递归关系式,得22k-1kT(n)=an(1+7/4+(7/4)+⋯+(7/4))+7T(1)\n第4章分治法992lognlogn≤cn(7/4)+7c是一个常数log4+log7-log4log7=cn+nlog7log72.81=(c+1)n=O(n)≈O(n)在斯特拉森之后,有很多人继续设法改进他的结果,值得指出的是J.E.Hopcroft和L.R.Kerr已经证明了两个2×2矩阵相乘必须要用7次乘法,因此要进一步获得改进,则需考虑3×3或4×4等更高级数的分块子矩阵或者用完全不同的设计策略。最后,要提请读者注意的是,斯特拉森矩阵乘法目前还只具有理论意义,因为只有当n相当大时它才优于通常的矩阵乘法。经验表明,当n取为120时,斯特拉森矩阵乘法与通常的矩阵乘法在计算时间上仍无显著差别。尽管如此,它还是给出了有益的启示:即使是由定义出发所直接给出的明显算法并非总是最好的。斯特拉森矩阵乘法可能为获得更有效和在计算机上切实可行的算法奠定了基础。关于矩阵乘法的更深入的讨论,读者可参看《线性代数与多项式的快速算法》(游兆永编,上海科学技术出版社1980年出版)。习题四4.1利用2.5节的规则,将过程DANDC(算法4.1)转换成迭代形式DANDC2,使得由DANDC2通过化简能够得到过程DANDC1(算法4.2)。4.2在下列情况下求解2.1节的递归关系式:g(n)n足够小T(n)=2T(n/2)+f(n)否则当①g(n)=O(1)和f(n)=O(n);②g(n)=O(1)和f(n)=O(1)时。4.3根据4.2节开始所给出的二分检索策略,写一个二分检索的递归过程。4.4作一个“二分”检索算法,它将原集合分成1/3和2/3大小的两个子集合。将这个算法与算法4.3相比较。4.5作一个“三分”检索算法,首先检查n/3处的元素是否等于某个x的值,然后检查2n/3处的元素。这样,或者找到x,或者把集合缩小到原来的1/3。分析此算法在各种情况下的计算复杂度。4.6对于含有n个内部结点的二元树,证明E=I+2n其中,E、I分别为外部和内部路径长度。4.7证明BINSRCH1的最好、平均和最坏情况的计算时间对于成功和不成功的检索都是Θ(logn)。4.8将递归过程MAXMIN翻译成在计算上等价的非递归过程。4.9按以下处理思想写一个找最大最小元素的迭代程序并分析它的比较次数。它不是以分治策略为基础的,但可能比MAXMIN更有效。先比较相邻的两个元素,然后,将较大的元素与当前最大元素相比较,较小的元素与当前最小元素相比较。4.10过程MERGESORT的最坏情况时间是O(nlogn)。它的最好情况时间是什么?能说归并分类的时间是Θ(nlogn)吗?4.11写一个“由底向上”的归并分类算法,从而取消对栈空间的需要。4.12如果一个分类算法在结束时相同元素出现的顺序与集合没分类以前一样,则称此算法是稳定的。归并分类算法是稳定的算法吗?\n100计算机算法基础4.13QUICKSORT是一种不稳定的分类算法。但是,若把A(i)中的关键字变成A(i)*n+i-1,那么,所有的关键字都不相同了。在分类之后,如何将关键字恢复成原来的值呢?4.14讨论在过程PARTITION(即算法4.12)中,将语句ifi v,那么,在PARTITION中还要作哪些改变?写出作出修改后的划分集合的算法并将它与原PARTITION相比较。4.17比较MERGESORT1和QUICKSORT2这两个分类算法。设计对这两个算法的平均和最坏情况时间进行比较的数据集。4.18如何利用插入分类算法INSERTIONSORT来改进快速分类算法QUICKSORT2?写出这一改进算法。为此,算法4.7需作哪些修改?写出INSERTIONSORT的修改模型。4.19画出对4个元素分类的比较树。4.20(1)假设只有在A中元素各不相同时才使用算法SELECT2。问r取下列哪些值能保证算法在最坏情况下用O(n)时间就可执行完毕?证明你的结论:r=3,7,9,11。(2)如果将r选得较大(但,是适当的),那么,SELECT2的计算时间是增加还是减少?为什么?4.21在题4.20中,将r=3,7,9,11改为r=7,11,13,15并且在不限制A中元素各不相同的情况下,再完成题4.20。34.22用SPARKS语言写一个计算时间为O(n)的两个n×n矩阵相乘的算法,并确定作乘法和加法的精确次数。4.23通过手算证明由(4.9)和(4.10)式确实能得到C11,C12,C21和C22的正确值。4.24在n是3的幂的情况下,考虑n×n级矩阵的乘法。使用分治策略设计的算法可以减少3×3级2.81矩阵乘法通常所要作的27次乘法。对于3×3级矩阵乘法必须作多少次乘法才使得计算时间比O(n)小呢?对于4×4级矩阵乘法作同样的讨论。4.25斯特拉森算法的另一种形式是用下面的恒等式来计算式(4.8)中的Cij(这样处理共用了7次乘法和15次加法):S1=A21+A22M1=S2S6T1=M1+M2S2=S1-A11M2=A11B11T2=T1+M4S3=A11-A21M3=A12B21S4=A12-S2M4=S3S7S5=B12-B11M5=S1S5S6=B22-S5M6=S4B22S7=B22-B12M7=A22S8S8=S6-B21Cij是C11=M2+M3C12=T1+M5+M6C21=T2-M7C22=T2+M5证明由这些恒等式确实可计算出C11,C12,C21和C22的正确值。\n第5章贪心方法5.1一般方法在现实世界中,有这样一类问题:它有n个输入,而它的解就由这n个输入的某个子集组成,只是这个子集必须满足某些事先给定的条件。把那些必须满足的条件称为约束条件;而把满足约束条件的子集称为该问题的可行解。显然,满足约束条件的子集可能不止一个,因此,可行解一般来说是不唯一的。为了衡量可行解的优劣,事先也给出了一定的标准,这些标准一般以函数形式给出,这些函数称为目标函数。那些使目标函数取极值(极大值或极小值)的可行解,称为最优解。对这一类需求取最优解的问题,又可根据描述约束条件和目标函数的数学模型的特性或求解问题方法的不同进而细分为线性规划、整数规划、非线性规划、动态规划等问题。尽管各类规划问题都有一些相应的求解方法,但其中的某些问题,还可用一种更直接的方法来求解,这种方法就是贪心方法。贪心方法是一种改进了的分级处理方法。它首先根据题意,选取一种量度标准;然后按这种量度标准对这n个输入排序,并按序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最优解加在一起不能产生一个可行解,则不把此输入加到这部分解中。这种能够得到某种量度意义下的最优解的分级处理方法称为贪心方法。要注意的是,对于一个给定的问题,往往可能有好几种量度标准。初看起来,这些量度标准似乎都是可取的。但实际上,用其中的大多数量度标准作贪心处理所得到该量度意义下的最优解并不是问题的最优解,而是次优解。尤其值得指出的是,把目标函数作为量度标准所得到的解也不一定是问题的最优解。因此,选择能产生问题最优解的最优量度标准是使用贪心法设计求解的核心问题。在一般情况下,要选出最优量度标准并不是一件容易的事,不过,一旦能选择出某个问题的最优量度标准,那么用贪心方法求解这个问题则特别有效。贪心方法可以用下面的抽象化控制来描述。算法5.1贪心方法的抽象化控制procedureGREEDY(A,n)∥A(1∶n)包含n个输入∥solution←ꯁ∥将解向量solution初始化为空∥fori←1tondox←SELECT(A)ifFEASIBLE(solution,x)thensolution←UNION(solution,x)endifrepeat\n102计算机算法基础return(solution)endGREEDY函数SELECT的功能是按某种最优量度标准从A中选择一个输入,把它的值赋给x并从A中消去它。FEASIBLE是一个布尔函数,它判定x是否可以包含在解向量中。UNION将x与解向量结合并修改目标函数。过程GREEDY描述了用贪心策略设计算法的主要工作和基本控制路线。一旦给出一个特定的问题,就可将SELECT,FEASIBLE和UNION具体化并付诸实现。5.2背包问题本节介绍使用贪心设计策略来解决更复杂的问题———背包问题。已知有n种物品和一个可容纳M重量的背包,每种物品i的重量为wi。假定将物品i的一部分xi放入背包就会得到pixi的效益,这里,0≤xi≤1,pi>0。采用怎样的装包方法才会使装入背包物品的总效益最大呢?显然,由于背包容量是M,因此,要求所有选中要装入背包的物品总重量不得超过M。如果这n件物品的总重量不超过M,则把所有物品装入背包自然获得最大效益。如果这些物品重量的和大于M,则在这种情况下该如何装包呢?这是本节所要解决的问题。由以上叙述,可将这个问题形式描述如下:极大化∑pixi(5.1)1≤i≤n约束条件∑wixi≤M(5.2)1≤i≤n0≤xi≤1,pi>0,wi>0,1≤i≤n(5.3)其中,式(5.1)是目标函数,式(5.2)和式(5.3)是约束条件。满足约束条件的任一集合(x1,⋯,xn)是一个可行解,使目标函数取最大值的可行解是最优解。例5.1考虑下列情况下的背包问题:n=3,M=20,(p1,p2,p3)=(25,24,15),(w1,w2,w3)=(18,15,10)。其中的4个可行解是(x1,x2,x3)∑wixi∑pixi①(1/2,1/3,1/4)16.524.25②(1,2/15,0)2028.2③(0,2/3,1)2031④(0,1,1/2)2031.5在这4个可行解中,第④个解的效益值最大。下面将可看到,这个解是背包问题在这一情况下的最优解。为了获取背包问题的最优解,必须把物品放满背包。由于可以只放物品i的一部分到背包中去,因此这一要求是可以达到的。如果用贪心策略来求解背包问题,则正如5.1节中所说的一样,首先要选出最优的量度标准。不妨先取目标函数作为量度标准,即每装入一件物品就使背包获得最大可能的效益值增量。在这种量度标准下的贪心方法就是按效益值的非增次序将物品一件件放到背包中去。如果正在考虑中的物品放不进去,则可只取其一部分来装满背包。但是,这最后一次的放法可能不符合使背包每次获得最大效益增量的量度标\n第5章贪心方法103准,这可以换一种能获得最大增量的物品,将它(或它的一部分)放入背包,从而使最后一次装包也符合量度标准的要求。例如,假定还剩有两个单位的空间,而在背包外还有两种物品,这两种物品有(pi=4,wi=4)和(pj=3,wj=2),则使用j就比用i要好些。下面对例5.1的数据使用这种选择策略。物品1有最大的效益值(p1=25),因此首先将物品1放入背包,这时x1=1且获得25的效益。背包容量中只剩下两个单位空着。物品2有次大的效益值(p2=24),但w2=15,背包中装不下物品2,使用x2=2/15就正好装满背包。不难看出物品2的2/15比物品3的2/10效益值高。所以,此种选择策略得到②的解,总效益值是28.2。它是一个次优解。由此例可知,按物品效益值的非增次序装包不能得到最优解。为什么上述贪心策略不能获得最优解呢?原因在于背包可用容量消耗过快。由此,很自然地启发我们用容量作为量度,让背包容量尽可能慢地被消耗。这就要求按物品重量的非降次序来把物品放入背包。例5.1的解③就是使用这种贪心策略得到的,它仍是一个次优解。这种策略也只能得到次优解,其原因在于容量虽然慢慢地被消耗,但效益值没能迅速地增加。这又启发我们采用在效益值的增长速率和容量的消耗速率之间取得平衡的量度标准。即每一次装入的物品应使它占用的每一单位容量获得当前最大的单位效益。这就需使物品的装入次序按pi/wi比值的非增次序来考虑。在这种策略下的量度是已装入物品的累计效益值与所用容量之比。其量度标准是每次装入要使累计效益值与所用容量的比值有最多的增加或最少的减小(第二次和以后的装入可能使此比值减小)。将此贪心策略应用于例5.1的数据,得到解④。如果将物体事先按pi/wi的非增次序分好类,则过程GREEDY-KNAPSACK就得出这一策略下背包问题的解。如果将物品分类的时间不算在内,则此算法所用时间为O(n)。算法5.2背包问题的贪心算法procedureGREEDY-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)是解向量∥realP(1∶n),W(1∶n),X(1∶n),M,cu;integeri,n;X←0∥将解向量初始化为零∥cu←M∥cu是背包剩余容量∥fori←1tondoifW(i)>cuthenexitendifX(i)←1cu←cu-W(i)repeatifi≤nthenX(i)←cu/W(i)endifendGREEDY-KNAPSACK值得指出的是,如果把物品事先按效益值的非增次序或重量的非降次序分好类,再使用算法5.2就可分别得到量度标准为最优(使每次效益增量最大或使容量消耗最慢)的\n104计算机算法基础解。由背包问题量度选取的研究可知,选取最优的量度标准实为用贪心方法求解问题的核心。下面证明用第三种策略的贪心算法所得的贪心解是一个最优解。基本思想是,把这贪心解与任一最优解相比较,如果这两个解不同,就去找开始不同的第一个xi,然后设法用贪心解的这个xi去代换最优解的那个xi,并证明最优解在分量代换前后的总效益无任何变化。反复进行这种代换,直到新产生的最优解与贪心解完全一样,从而证明了贪心解是最优解。这种证明最优解的方法在本书中经常使用,因此读者从现在起就应掌握它。定理5.1如果p1/w1≥p2/w2≥⋯≥pn/wn,则算法GREEDY-KNAPSACK对于给定的背包问题实例生成一个最优解。证明设X=(x1,⋯,xn)是GREEDY-KNAPSACK所生成的解。如果所有的xi等于1,显然这个解就是最优解。于是,设j是使xj≠1的最小下标。由算法可知,对于1≤i
∑pixi。不失一般性,可以假定∑wiyi=M。设k是使得yk≠xk的最小下标。显然,这样的k必定存在。由上面的假设,可以推得yk j分别得证明:(1)若k xk,显然有∑wiyi>M,与Y是可行解矛盾。若yk=xk,与假设yk≠xk矛盾,故yk j,则∑wiyi>M,这是不可能的。现在,假定把yk增加到xk,那么必须从(yk+1,⋯,yn)中减去同样多的量,使得所用的总容量仍是M。这导致一个新的解Z=(z1,⋯,zn),其中,zi=xi,1≤i≤k,并且∑wi(yi-zi)k∑piyi,则Y不可能是最优解。如果这两个和数相等,同时Z=X,则X就是最优解;若Z≠X,则需重复上面的讨论,或者证明Y不是最优解,或者把Y转换成X,从而证明了X也是最优解。证毕。5.3带有限期的作业排序在这一节将应用贪心设计策略来解决操作系统中单机、无资源约束且每个作业可在等量的时间内完成的作业调度问题。即,假定只能在一台机器上处理n个作业,每个作业均可在单位时间内完成;又假定每个作业i都有一个截止期限di>0(它是整数),当且仅当作业i在它的期限截止以前被完成时,方可获得pj>0的效益。这个问题的一个可行解是这n个作业的一个子集合J,J中的每个作业都能在各自的截止期限之前完成。可行解的效益值是J中这些作业的效益之和,即∑p。具有最大效益值的可行解就是最优解。i∈J\n第5章贪心方法105例5.2设n=4,(p1,p2,p3,p4)=表5.1例5.2的可行解与效益值(100,10,15,20)和(d1,d2,d3,d4)=(2,1,可行解处理顺序效益值①(1)11002,1),则这个问题的可行解和它们的效益②(2)210值如表5.1所示。其中,解⑦是最优的,所③(3)315允许的处理次序是:先处理作业4,再处理④(4)420⑤(1,2)2,1110作业1。于是,在时间0开始处理作业4⑥(1,3)1,3或3,1115而在时间2完成对作业1的处理。⑦(1,4)4,1120⑧(2,3)2,3255.3.1带有限期的作业排序算法⑨(3,4)4,335为了拟定出一个最优解的算法,应制定如何选择下一个作业的量度标准,利用贪心策略,使得所选择的下一个作业在这种量度下达到最优。不妨首先把目标函数∑pi作为量度。i∈J使用这一量度,下一个要计入的作业将是在满足所产生的J是一个可行解的限制条件下让∑pi得到最大增加的作业。这就要求按pi的非增次序来考虑这些作业。利用例5.2中的数i∈J据来应用这一准则,开始时J=ꯁ,∑pi=0,由于作业1有最大效益且J={1}是一个可行解,i∈J于是把作业1计入J。下一步考虑作业4,J={1,4}也是可行解。然后,考虑作业3,因为{1,3,4}不是可行解,故作业3被舍弃。最后,考虑作业2,由于{1,2,4}也不可行,作业2被舍弃。最终所留下的是效益值为120的解J={1,4}。它是这个问题的最优解。现在,对上面所叙述的贪心方法以算法5.3的形式给出其粗略的描述。算法5.3作业排序算法的概略描述procedureGREEDY-JOB(D,J,n)∥作业按p1≥p2≥⋯≥pn的次序输入,它们的期限值D(i)≥1,1≤i≤n,n≥1。J是在它们的截止期限前完成的作业的集合∥1J←{1}2fori←2tondo3ifJ∪{i}的所有作业都能在它们的截止期限前完成thenJ←J∪{i}4endif5repeat6endGREEDY-JOB定理5.2对于作业排序问题用算法5.3所描述的贪心方法总是得到一个最优解。证明设(pi,di),1≤i≤n,是作业排序问题的任一实例;J是由贪心方法所选择的作业的集合;I是一个最优解的作业集合。可证明J和I具有相同的效益值,从而J也是最优的。假定I≠J,因为若I=J,则J即为最优解。容易看出,如果IJ,则I就不可能是最优的。由于贪心法的工作方式也排斥了JI,因此至少存在着这样的两个作业a和b,使得a∈J且a|I,b∈I且b|J。设a是使得a∈J且a|I的一个具有最高效益值的作业。由于贪心方法可以得出,对于在I中又不在J中的所有作业b,都有pa≥pb。这是因为若pb>pa,则贪心方法会先于作业a考虑作业b并且把b计入到J中去。现在,设SJ和SI分别是J和I的可行调度表。设i是既属于J又属于I的一个作业;又\n106计算机算法基础设i在SJ中在t到t+1时刻被调度,而在SI中则在t′到t′+1时刻被调度。如果t a。在σ′中将ra与rb相交换,因为dra≥drb,故作业可依新产生的排列σ"=s1s2⋯sk的次序处理而不违反任何一个期限。连续使用这一方法,就可将σ′转换成σ且不违反任何一个期限。定理得证。即使这些作业有不同的处理时间ti≥0,上述定理亦真。其证明留作习题。根据定理5.3,可将带限期的作业排序问题作如下处理:首先将作业1存入解数组J中,然后处理作业2到作业n。假设已处理了i-1个作业,其中有k个作业已计入J(1),J(2),⋯,J(k)之中,且有D(J(1))≤D(J(2))≤⋯≤D(J(k)),现在处理作业i。为了判断JU{i}是否可行,只需看能否找出按期限的非降次序插入作业i的适当位置,使得作业i在此处插入后有D(J(r))≥r,1≤r≤k+1。找作业i可能的插入位置可如下进行:将D(J(k)),D(J(k-1)),⋯,D(J(1))逐个与D(i)比较,直到找到位置q,它使得D(i) 1,q<1≤k,则说明这k-q个作业均可延迟一个单位时间处理,即可将这些作业在J中均后移一个位置而不超过各自的期限值。在以上条件成立的情况下,只要D(i)>q,就可将作业i在位置q+1处插入,从而得到一个按期限的非降次序排列的含有k+1个作业的可行解。以上过程可反复进行到第n个作业处理完毕,所得到的贪心解由定理5.2可知就是一个最优解。这一处理过程可用算法5.3来描述。算法中\n第5章贪心方法107引进了一个虚构的作业0,它放在J(0),且D(J(0))=0。引入这一虚构作业是为了便于将作业插入位置1。算法5.4带有限期和效益的单位时间的作业排序贪心算法lineprocedureJS(D,J,n,k)∥D(1),⋯,D(n)是期限值,n≥1,作业已按p1≥p2≥⋯pa被排序。J(i)是最优解中的第i个作业,1≤i≤k。终止时,D(J(i))≤D(J(i+1)),1≤i D(i)andD(J(r))≠rdo7r←r-18repeat9ifD(J(r))≤D(i)andD(i)>rthen∥把i插入J∥10fori←ktor+1by-1do11J(i+1)←J(1)12repeat13J(r+1)←i;k←k+114endif15repeat16endJS对于JS,有两个赖之以测量其复杂度的参数,即作业数n和包含在解中的作业数s。第6~8行的循环至多迭代k次,每次迭代的时间为O(1)。若第9行的条件为真,则执行10~13行。这些要求O(k-r)时间去插入作业i。因此,对于4~15行的循环,其每次迭代的总时间是O(k)。该循环共迭代n-1次。如果s是k的终值,即s是最后所得解的作业数,则2算法JS所需要的总时间是O(sn)。由于s≤n,因此JS的最坏情况时间是O(n)。这种情2况在p1=d1=n-i+1,1≤i≤n时就会出现。进而可以证明最坏情况计算时间为Θ(n)。在计算空间方面,除了D所需要的空间外,为了存放解J,还需要Θ(s)的空间量。要指出的是,JS并不需要具体的效益值,只要知道pi≥pi+1,1≤i≤n即可。5.3.2一种更快的作业排序算法通过使用不相交集合的UNION与FIND算法(参见2.4.3小节)以及使用一个不同的2方法来确定部分解的可行性,可以把JS的计算时间由O(n)降低到数量级相当接近于O(n)。如果J是作业的可行子集,那么可以使用下述规则来确定这些作业中的每一个作业的处理时间:若还没给作业i分配处理时间,则分配给它时间片[α-1,α],其中α应尽量取大且时间片[α-1,α]是空的。此规则就是尽可能推迟对作业i的处理。于是,在将作业一个一个地装配到J中时,就不必为接纳新作业而去移动J中那些已分配了时间片的作业。如果正被考虑的新作业不存在像上面那样定义的α,这个作业就不能计入J。这样处理的正确性\n108计算机算法基础证明留作习题。例5.3设n=5,(p1,⋯,p5)=(20,15,10,5,1)和(d1,⋯,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个作业且每个作业花费一个单位时间,因此只需考虑这样一些时间片[i-1,i],1≤i≤b,其中b=min{n,max{dj}}。为简便起见,用i来表示时间片[i-1,i]。易于看出,这n个作业的期限值只能是{1,2,⋯,b}中的某些(或全部)元素。实现上述调度规则的一种方法是把这b个期限值分成一些集合。对于任一期限值i,设ni是使得nj≤i的最大整数且是空的时间片。为避免极端情况,引进一个虚构的期限值0和时间片[-1,0]。当且仅当nj=nj,期限值i和j在同一个集合中,即所要处理的作业的期限值如果是i或j,则当前可分配的最接近的时间片是ni。显然,若i COST(k,j)18thenNEAR(k)←j19endif20repeat21repeat22ifmincost≥∞thenprint(′nospanningtree′)endif23endPRIM2过程PRIM所需要的时间是Θ(n),其中n是图G的结点数。具体分析如下:第3行花费Θ(e)(e=|E|)时间而第4行花费Θ(1)时间;第6~9行的循环花费Θ(n)时间;第12行和第16~20行的循环要求Θ(n)时间,故第11~21行循环的每一次迭代要花费Θ(n)时间。22所以,这个循环的总时间是Θ(n)。因此,过程PRIM有Θ(n)的时间复杂度。因为最小生成树包含了与每个结点v相关的一条最小成本边,所以还可以把这算法稍许加快一点。为此,假设T是图G=(V,E)的最小成本生成树。设v是T中的任一结点。又设(v,w)是所有与v相关的边中具有最小成本的一条边。假定(v,w)|E(T)并且对于所有的边(v,x)∈E(T),COST(v,w) L,要求选取一个能放在带上的程序的最大子集合Q。最大子集指的是在其中含有最多个数的程序。构造Q的一种贪心策略是按ai的非降次序把程序计入集合。(1)假设Pi被排成使a1≤a2≤⋯≤ai。使用上面的设计策略写一个SPARKS算法。要求输出一个数组S(1∶n),若Pi在Q中,则S(i)=1,否则S(i)=0。(2)证明这一策略总能找到最大子集Q,使得∑ai≤L。P∈Qi(3)设Q是使用上述贪心策略得到的子集合,带利用率∑ai/L可以小到什么程度?P∈Qi(4)假定现在要确定一个使带的利用率最高的程序子集合,可考虑按ai的非增次序计入程序这一贪心策略。只要Pi带上还有够用的空间,就应将它计入Q。假设程序已按使a1≥a2≥⋯≥an的次序排列。用SPARKS写一个与此策略对应的算法,并分析它的时、空复杂度。(5)证明(1)所用的设计策略不一定得到使∑ai/L取最大值的子集合。P∈Qi5.2(1)求以下情况背包问题的最优解:n=7,M=15,(p1,⋯,p7)=(10,5,15,7,6,18,3)和(w1,⋯,w7)=(2,3,5,7,1,4,1)。(2)将以上数据情况的背包问题记为I。设FG(I)是物品按pi的非增次序输入时由GREEDY-KNAPSACK所生成的解,FO(I)是一个最优解。问FO(I)/FG(I)是多少?(3)当物品按wi的非降次序输入时,重复(2)的讨论。\n122计算机算法基础5.3[0/1背包问题]如果将5.3节讨论的背包问题修改成n极大化∑pixi1n约束条件∑wixi≤M1xi=0或1,1≤i≤n这种背包问题称为0/1背包问题。它要求物品或者整件装入背包或者整件不装入。求解此问题的一种贪心策略是:按pi/wi的非增次序考虑这些物品,只要正被考虑的物品能装得进就将其装入背包。证明这种策略不一定得到最优解。5.4[集合覆盖]已知集合族S由m个集合S1,⋯,Sm组成。S的子集合T={T1,T2,⋯,Tk}也是一km个集合族,而且T中每一个Tj,1≤j≤k,等于S中的某个Si,1≤i≤m。如果∪Tj=∪Si,则称T是S的一个j=1i=1覆盖。T的大小|T|是T中集合的个数。S的最小覆盖是|T|取最小值的覆盖。考虑以下的贪心策略:通过迭代构造T;在第k次迭代时,T={T1,⋯,Tk-1};现在将S中的一个集合Si加到T,这个Si中含有还不km在T中最多的元素个数;当∪Tj=∪Si时停止。j=1i=1m(1)假设∪Si={1,2,⋯,n}且m 0,要用的处理时间ti>0,限期di≥ti。5.9(1)对于5.3节的作业排序问题证明:当且仅当子集合J中的作业可以按下述规则处理时,J才表示一个可行解,即如果J中的作业i还没分配处理时间,则将它分配在时间片[α-1,α]处理,其中α是使得1≤r≤di的最大整数r,且时间片[α-1,α]是空的。(2)仿照例5.4的格式,在题5.8之(1)所提供的数据集上执行算法5.5。5.10(1)已知n-1个元素已按min-堆的结构形式存放在A(1),⋯,A(n-1)。现要将另一存放在A(n)的元素和A(1∶n-1)中元素一起构成一个具有n个元素的min-堆。对此写一个计算时间为O(logn)的算法。(2)在A(1∶n)中存放着一个min-堆,写一个从堆顶A(1)删去最小元素后将其余元素调整成min-堆的算法,要求这新的堆存放在A(1∶n-1)中,且算法时间为O(logn)。(3)利用(2)所写出的算法,写一个对n个元素按非增次序分类的堆分类算法。分析这个算法的计算复杂度。5.11(1)证明如果一棵树的所有内部结点的度都为k,则外部结点数n满足nmod(k-1)=1。(2)证明对于满足nmod(k-1)=1的正整数n,存在一棵具有n个外部结点的k元树T(在一棵k元树中,每个结点的度至多为k)。进而证明T中所有内部结点的度为k。5.12(1)证明如果nmod(k-1)=1,则在定理5.4后面所描述的贪心规则对于所有的(q1,q2,⋯,qn)生成一棵最优的k元归并树。(2)当(q1,q2,⋯,q11)=(3,7,8,9,15,16,18,20,23,25,28)时,画出使用这一规则所得到的最优三元归并树。5.13证明5.5节的Prim方法生成最小成本生成树。5.14在假定图用邻接表来表示的情况下重写Prim算法,并分析它的计算复杂度。5.15证明引理5.1。n-15.16通过考察n结点的完全图,证明在一个n结点图中可能有比2-2棵还多的生成树。5.17在图5.12的有向图中,利用算法SHORTEST-PATHS获取按长度非降次序排列的由结点1到其余各结点最短路径长度。图5.12有向图图5.13有向图5.18说明为什么将SHORTEST-PATHS应用于图5.13的有向图就不能正常地工作。结点v1和v7之间的最短路径是什么?5.19修改算法SHORTEST-PATHS,使得它在获取最短路径的同时还得到这些最短路径。请给出修改后的算法的计算时间。\n第6章动态规划6.1一般方法在实际生活中,有这么一类问题,它们的活动过程可以分为若干个阶段,而且在任一阶段后的行为都仅依赖于i阶段的过程状态,而与i阶段之前的过程如何达到这种状态的方式无关,这样的过程就构成一个多阶段决策过程。在20世纪50年代,贝尔曼(RichardBell-man)等人根据这类问题的多阶段决策的特性,提出了解决这类问题的“最优性原理”,从而创建了最优化问题的一种新的算法设计方法———动态规划。在多阶段决策过程的每一阶段,都可能有多种可供选择的决策,必须从中选取一种决策。一旦各个阶段的决策选定之后,就构成了解决这一问题的一个决策序列。决策序列不同,所导致的问题的结果也不同。动态规划的目标就是要在所有容许选择的决策序列中选取一个会获得问题最优解的决策序列,即最优决策序列。显然,用枚举的方法从所有可能的决策序列中选取最优决策序列是一种最笨拙的方法。贝尔曼认为,利用最优性原理(principleofoptimality)以及所获得的递推关系式去求取最优决策序列可以使枚举量急剧下降。这个原理指出,过程的最优决策序列具有如下性质:无论过程的初始状态和初始决策是什么,其余的决策都必须相对于初始决策所产生的状态构成一个最优决策序列。如果所求解问题的最优性原理成立,则说明用动态规划方法有可能解决该问题;而解决问题的关键在于获取各阶段间的递推关系式。例6.1[多段图问题]多段图G=(V,E)是一个有向图。它具有如下特性:图中的结点被划分成k≥2个不相交的集合Vi,1≤i≤k,其中V1和Vk分别只有一个结点s(源点)和t(汇点)。图中所有的边〈u,v〉均具有如下性质:若u∈Vi,则v∈Vi+1,1≤i ∑piyi。因此,序列y1,z2,z3,⋯,zn是一个对问题KNAP2≤i≤n2≤i≤n2≤i≤n(1,n,M)具有更大效益值的序列。最优性原理成立。能用动态规划求解的问题的最优化决策序列可表示如下。设S0是问题的初始状态。假定需要作n次决策xi,1≤i≤n。设X1={r1,1,r1,2,⋯,r1,p}是x1的可能决策值的集合,而1S1,j是在选取决策值r1,j以后所产生的状态,1≤j1≤p1。又设Γ1,j是相应于状态S1,j的最优1111决策序列。那么,相应于S0的最优决策序列就是{r1,jΓ1,j|1≤j1≤p1}中最优的序列,记为11OPT{r1,jΓ1,j}=r1Γ1。如果已作了k-1次决策,1≤k-1 k且i (n-1)*M,则表明G中由i到j没有有向路径。3过程ALL-PATHS所需的时间很容易确定。第9行迭代了n次,而且整个循环与矩阵3A中的数据无关,因此过程的计算时间是Θ(n)。至于求由i到j的最短路径所需增设的内容,则留作一道习题。6.4最优二分检索树2.4.2节给出了二分检索树的定义。根据定义,要求树中所有的结点是互异的。对于一个给定的标识符集合,可能有若干棵不同的二分检索树。图6.5给出了关于SPARKS保留字的一个子集的两棵二分检索树。图6.5两棵二分检索树为了确定标识符X是否在一棵二分检索树中出现,将X先与根比较,如果X比根中标识符小,则检索在左子树中继续;如果X等于根中标识符,则检索成功地终止;否则检索在右子树中继续下去。上述步骤可以形式化为过程SEARCH。算法6.4检索一棵二分检索树procedureSEARCH(T,X,i)∥为X检索二分检索树T,这棵树的每个结点有3个信息段:LCHILD,IDENT和RCHILD。如果X不在T中,则置i=0,否则将i置成使得IDENT(i)=X∥1i←T2whilei≠0do3case4:X IDENT(i):i←RCHILD(i)∥检索右子树∥7endcase8repeat9endSEARCH\n第6章动态规划133已知一个固定的标识符集合,希望产生一种构造二分检索树的方法。可以预料,同一个标识符集合有不同的二分检索树,而不同的二分检索树有不同的性能特征。图6.5(a)所示的树在最坏情况下找一个标识符需要进行4次比较,而图6.5(b)所示的那棵树只需要3次比较,在平均情况下,这两棵树各需要12/5和11/5次比较。这一计算结果是假定检索每一个标识符具有同等的概率并且在任何时候都不做不在T中的标识符的检索。在一般情况下,可以预计所要检索的那些不同的标识符具有不同的频率(或概率)。另外,也要做一些不成功的检索(即对不在这棵树中标识符的检索)。假定所给出的标识符集是{a1,a2,⋯,an},其中a1 an的标识符X。易于看出,在同一类Ei中的所有标识符,其检索都在同一个外部结点处终止。而在不同的Ei中的标识符则在不同的外部结点处终止。如果Ei的那个失败的结点在l级,则只要对while循环作l-1次迭代。于是,这个结点的成本分担额是Q(i)*(level(Ei)-1)。上述讨论导出二分检索树的预期成本公式如下:∑P(i)*level(ai)+∑Q(i)*(level(Ei)-1)(6.9)1≤i≤n0≤i≤n\n134计算机算法基础定义标识符集{a1,a2,⋯,an}的最优二分检索树是一棵使式(6.9)取最小值的二分检索树。例6.9标识符集(a1,a2,a3)=(doifstop)可能的二分检索树如图6.7所示。图6.73种标识符的各种二分检索树在每个内、外结点具有相同概率P(i)=Q(i)=1/7的情况下,有cost(树a)=15/7cost(树b)=13/7cost(树c)=15/7cost(树d)=15/7cost(树e)=15/7正如料想的那样,树b是最优的。在P(1)=0.5,P(2)=0.1,P(3)=0.05,Q(0)=0.15,Q(1)=0.1,Q(2)=0.05和Q(3)=0.05的情况下,则有cost(树a)=2.65cost(树b)=1.9cost(树c)=1.5cost(树d)=2.15cost(树e)=1.6在以上情况下树c是最优的。为了把动态规划应用于得到一棵最优二分检索树的问题,需要把构造这样的一棵树看成是一系列决策的结果,而且要能列出求取最优决策序列的递推式。解决上述问题的一种可能方法是,对于这些ai,1≤i≤n,要决策出将其中的哪一个作为T的根结点。如果选择ak,那么,a1,a2,⋯,ak-1这些内部结点和E0,E1,⋯,Ek-1这些类的外部结点显然都将位于这个根的左子树L中,而其余的结点则将在右子树R中。定义COST(L)=∑P(i)*level(ai)+∑Q(i)*(level(Ei)-1)1≤i 0,则有fi(x)=max{fi-1(x),fi-1(x-wi)+pi}(6.14)为了能由前向后递推而最后求解出fn(M),需从f0(x)开始。对于所有的x≥0,有f0(x)=0,当x<0时,有f0(x)=-∞。根据式(6.14),马上可求解出0≤x M的那些序偶(p,w)也清除掉,因为由它们不能导出满足约束条件的可行解。这样生成的S的所有序偶,是背包问题KNAP(1,i,x)在0≤x≤M各种取值下的最优in解(注意:S是由一些序偶构成的有序集合)。通过计算S可以找到KNAP(1,n,x),0≤x≤nM的所有解。KNAP(1,n,M)的最优解fn(M)由S的最后一对序偶的P值给出。\n140计算机算法基础n由于实际上只需要KNAP(1,n,M)的最优解,它是由S的最末序偶(p,w)给出的。而nn-1n-1S的这最末序偶或者是S的最末序偶,或者是(pj+pn,wj+wn),其中(pj,wj)∈S且wjn-1n是S中满足wj+wn≤M的最大值。因此,只需按上述方法求出S的最末序偶,无需计算n出S也一样满足求KNAP(1,n,M)最优解的要求。n如果已找出S的最末序偶(p1,w1),那么,使∑pixi=p1,∑wixi=w1的x1,⋯,xn的决in-1n-1策值可以通过检索这些S来确定。若(p1,w1)∈S,则置xn=0。若(p1,w1)|S,则n-1n-1(p1-pn,w1-wn)∈S,并且置xn=1。然后,再判断留在S中的序偶(p1,w1)或者(p1-n-2pn,w1-wn)是否属于S以确定xn-1的取值,等等。3例6.13由例6.12,在M=6的情况下,f3(6)的值由S中的序偶(6,6)给出。(6,6)|22S,因此应置x3=1。序偶(6,6)是由序偶(6-p3,6-w3)=(1,2)得到的,因此(1,2)∈S。10又,(1,2)∈S,于是应置x2=0。但(1,2)|S,从而得到x1=1。故最优解f3(6)=6的最优决策序列是(x1,x2,x3)=(1,0,1)。对于以上所述的内容可以用一种非形式的算法过程DKP来描述。为了实现DKP,需ii要给出表示序偶集S和S1的结构形式,而且要将MERGE-PURGE过程具体化,使它能按i-1in-11要求归并S和S1,且清除一些序偶。此外,还要给出一个沿着S,⋯,S回溯以确定xn,⋯,x1的0、1值的算法。算法6.6非形式化的背包算法lineprocedureDKP(p,w,n,M)01S←{(0,0)}2fori←1ton-1doii-13S1←{(p1,w1)|(p1-pi,w1-wi)∈Sandw1≤M}ii-1i4S←MERGE-PURGE(S,S1)5repeatn-16(px,wx)←S的最末序偶n-17(py,wy)←(p1+pn,w1+wn),其中,w1是S中使得w+wn≤M的所有序偶中取最大值n-11的w∥沿S,⋯,S回溯确定xn,xn-1,⋯,x1的取值∥n8ifpx>pythenxn←0∥px是S的最末序偶∥n9elsexn←1∥py是S的最末序偶∥10endif11回溯确定xn-1,⋯,x112endDKP6.5.2DKP的实现可以用两个一维数组P和W来存放所有的序偶(p,w)。p的值放在P中,w的值放在01n-1W中。序偶集S,S,⋯,S互相邻接地存放。各个集合可以用指针F(i)来指示,这里n-10≤i≤n。对于0≤i M。因此,S1的序偶都是1≤j≤u的序偶(P(j)+pi,W(j)+wi)。这些序偶在7~22行的循环中生成,相应的归并工作也在这里进行。每次迭代首先生成一对序偶i-1i(pp,ww),接着将S中所有还没有被清除和归并到S中且有W P(next-1)的情况下,把序偶(pp,ww)加入到S中,否则(pp,ww)被清除。第19~21行i-1iiii-1清除S中所有能在此时清除且没有并入S的序偶。在7~22行将S1归并到S以后,Si中可能剩下一些序偶需并入S,此工作在第23~26行完成。要指出的是,由于第19~21行的处理,这些剩下的序偶没有一个能被清除。过程PARTS(29行)实现过程DKP(算法6.6)的第6~11行,具体实现留作一道习题。算法6.70/1背包问题的算法lineprocedureDKNAP(p,w,n,M,m)realp(n),w(n),P(m),W(m),pp,ww,MintegerF(0;n),l,h,u,i,j,p,next01F(0)←1;P(1)←W(1)←0∥S∥02l←h←1∥S的首端与末端∥3F(1)←next←2∥P和W中第一个空位∥i4fori←1ton-1do∥生成S∥5k←l6u←在l≤r≤h中使得W(r)+wi≤M的最大的ri7forj←ltoudo∥生成S1及归并∥i8(pp,ww)←(P(j)+pi,W(j)+wi)∥S1中的下一个元素∥i-19whilek≤handW(k) P(next-1)then(P(next),W(next))←(pp,ww)17next←next+118endif19whilek≤handP(k)≤P(next-1)do∥清除∥20k←k+121repeat22repeati-1i∥将S中剩余的元素并入S∥23whilek≤hdo24(P(next),W(next))←(P(k),W(k))25next←next+1;k←k+126repeati+1∥对S置初值∥27l←h+1;h←next-1;F(i+1)←next28repeat29callPARTS30endDKNAP6.5.3过程DKNAP的分析iiii-1ii假设S的序偶数是|S|。在i>0的情况下,每个S由S和S1归并而成,并且|S1|i-1ii-1≤|S|,因此|S|≤2|S|。在最坏情况下没有序偶会被清除,所以iin∑|S|=∑2=2-10≤i≤n-10≤i≤n-1n即,DKNAP的空间复杂度为O(2)。i-1ii-101n-1由S生成S需要Θ(|S|)这么多时间,因此计算S,S,⋯,S总的时间是Θ(∑i-1iiin|S|)。由于|S|≤2,所以计算这些S总的时间是O(2)。i如果每件物品的重量wj和所产生的效益值pj都是整数,那么S中每个序偶(p,w)的iP和W也是整数,且有P≤∑pj,W≤M。又由于在任一S中,它的序偶有互异的P值,也1≤j≤i有互异的W值,因此有ii|S|≤1+∑pj和|S|≤1+min{∑wj,M}1≤j≤i1≤j≤i于是,在所有wj和pj为整数的情况下,DKNAP的时间和空间复杂度(除去PARTS的时n间)是O(min{2,n∑pi,nM})。1≤i≤n以上分析似乎表明,当n取值大时DKNAP的有效性是令人失望的,但这类问题在很多情况下都能在“适当的”时间内解出。这是因为在很多情况下P和W都是整数,而且M比ni2小得多。另外,支配规则在清除不应并入S的序偶上是很有效的。使用探试方法(heuristic)可以加速DKNAP的计算。设L是最优解的估计值,它使得\n第6章动态规划143ifn(M)≥L,又设PLEFT(i)=∑pj。如果序偶(p,w)∈S,且使得P+PLEFT(i) M。f6(165)可由S求出,它等于160+p6=163。i此例中L值一直没有改变。在一般情况下,如果计算某S会产生更好的估计值,L值将会改变。如果不使用启发性方法,由DKNAP就会产生如下结果:0S={0}1S={0,100}2S={0,50,100,150}3S={0,20,50,70,100,120,150}4S={0,10,20,30,50,60,70,80,100,110,120,130,150,160}5S={0,7,10,17,20,27,30,37,50,57,60,67,70,77,80,87,100,107,110,117,120,127,130,137,150,157,160}5f6(165)可用已知条件(p6,w6)=(3,3)从S求出。6.6可靠性设计乘积函数最优化问题,也可使用动态规划法来求解。本节讨论这类问题中的一个实例。假定要设计一个系统,这个系统由若干个以串联方式连接在一起的不同设备所组成(见图\n144计算机算法基础6.11)。设ri是设备Di的可靠性(即ri是设备Di正常运转的概率),则整个系统的可靠性就是∏ri。即便这些单个设备是非常可靠的(每个ri都非常接近于1),该系统的可靠性也不一定很高。例如,若n=10,ri=0.99,1≤i≤10,则∏ri=0.904。为了提高系统可靠性,最好是增加一些重复设备,并通过开关线路把数个同类设备并联在一起(见图6.12)。由开关线路来判明其中的设备的运行情况,并将能正常运行的某台投入使用。图6.11以串联方式连接的n台设备图6.12每级中以并联方式连接多台设备m如果第i级的设备Dii的台数为mi,那么这mi台设备同时出现故障的概率为(1-ri)。m从而第i级的可靠性就变成1-(1-ri)i。例如,假定ri=0.99,mi=2,于是这一级的可靠m性就是0.9999。不过在任何实际系统中,每一级的可靠性要比1-(1-rii)小一些,这是由于这些开关线路本身并不是完全可靠的,而且同一类设备的失误率也不可能是完全独立的(例如,由于设计不当所造成的失误)。基于以上分析,不妨假设第i级的可靠性由函数φ(mi)给定,1≤i≤n。并且可以看出,开始时φ(mi)随mi值的增大而增大,在到达mi的某个取值以后φ(mi)的值则可能下降。这个多级系统的可靠性是∏φ(mi)。诚然,增加重复的设备可提高系统的可靠性,但在实际工作中,设计一个系统都是在有成本约束的条件下实现的。因此,所谓可靠性设计最优化问题是在可容许最大成本c的约束下,如何使系统的可靠性达到最优的问题。假设cj是一台设备j的成本,由于系统中每种设备至少有一台,故设备j允许配置的台数至多为nuj=|(c+cj-∑ck)/cj|k=1如果用RELI(l,i,X)表示在可容许成本X约束下,对第l种到第i种设备的可靠性设计问题,它就可形式描述成极大化∏φ(mi)1≤j≤i约束条件∑cjmj≤X(6.16)1≤j≤ii其中,cj>0,1≤mj≤uj=|(X+cj-∑ck)/cj|且mj为整数,l≤j≤i。k=l于是,整个系统的可靠性设计问题由RELI(1,n,c)表示。它的一个最优解是对m1,⋯,mn的一系列决策的结果。每得到一个mi都要对其取值进行一次决策。设fi(x)是在容许成本值X约束下对前i种设备组成的子系统可靠性设计的最优值,即fi(x)=max∏j(mj),那么最优解的可靠性是fn(c)。所作的最后决策要求从{1,2,⋯,un}中选择一个元素作为mn。一旦选出了mn的值,则下阶段的决策就应使剩余资金c-cnmn按最优的方\n第6章动态规划145式使用。于是,有fn(c)=max{φ(mn)fn-1(c-cnmn)}(6.17)将(6.17)式推广到一般,对任一fi(x),i≥1,有fi(x)=max{φ(mi)fi-1(x-cimi)}(6.18)显然,当0≤x≤c时,对于所有的x,有f0(x)=1。使用类似于解0/1背包问题的方法可i以解出(6.18)式。设S由(f,x)形式的序偶所组成,其中f=fi(x)。由m1,⋯,mi的决策序列所得出的每一个不同的x都至多只有一个序偶。支配规则对这个问题也适用,即当且仅i当f1≥f2而x1≤x2时,(f1,x1)支配(f2,x2)。那些受支配的序偶可从S中舍去。例6.15设计一个由设备D1,D2,D3组成的三级系统。每台设备的成本分别为30元,15元和20元,可靠性分别是0.9,0.8和0.5,计划建立该系统的投资不得超过105元。假m定,若i级有mi台设备Di并联,则该级的可靠性φ(mi)=1-(1-ri)i。上述条件可以表示为:c=105;c1=30,c2=15,c3=20;r1=0.9,r2=0.8,r3=0.5。由此立即可得:u1=2,u2=3,u3=3。i用S表示m1,⋯,mi的各种决策序列产生的所有不受支配的序偶(f,x)之集合。这里0i-1f=fi(x)。整个工作从S={(1,0)}开始。假设已求出S。S的求取可按以下步骤进行,对i-1i于mi的所有可能值,依次求出当mi=j,1≤j≤ui时,由S可能得到的所有序偶的集合Sj,ii然后将这ui个Sj按支配规则归并即得S。于是,由11S1={(0.9,30)}S2={(0.99,60)}1得S={(0.9,30),(0.99,60)}222由S1={(0.72,45),(0.792,75)}S2={(0.864,60)}S3={(0.8928,75)}2得S={(0.72,45),(0.864,60),(0.8928,75)}2注意:S2中已删去了由(0.99,60)所得到的序偶(0.9504,90)。因为这只剩下15元,不足以让m3=1。说明:归并时由于(0.792,75)受(0.864,60)支配,故舍去。3由S1={(0.36,65),(0.432,80),(0.4464,95)}3S2={(0.54,85),(0.648,100)}3S3={(0.63,105)}3得S={(0.36,65),(0,432,80),(0.54,85),(0.648,100)}i最优设计有0.648的可靠性,需用资金100元。通过对这些S的回溯,求出m1=1,m2=2,m3=2。i对于可靠性设计问题,值得注意的是,正如上例中已作的处理那样,在S中不需要保留任何比c-∑cj还大的x值的序偶,这是因为任何序偶都不能超出完成这个系统所能负担1≤j≤ni的资金。此外,为了减少这些S的规模,可以像求解背包问题一样,将启发性方法引进求解可靠性问题的动态规划算法,即提供某种判定下界,如果(f,x)小于它,则将(f,x)从S中删去。\n146计算机算法基础6.7货郎担问题货郎担问题属于易于描述但难于解决的著名难题之一,至今世界上还有不少人在研究它。该问题的基本描述是:某售货员要到若干个村庄售货,各村庄之间的路程是已知的,为了提高效率,售货员决定从所在商店出发,到每个村庄售一次货然后返回商店,问他应选择一条什么路线才能使所走的总路程最短?此问题可描述如下:设G=(V,E)是一个具有边成本cij的有向图,cij的定义如下:对于所有的i和j,cij>0,若〈i,〉j|E,则cij=∞。令|V|=n,并假定n>1。G的一条周游路线是包含V中每个结点的一个有向环。周游路线的成本是此路线上所有边的成本和。货郎担问题(travelingsalespersonproblem)是求取具有最小成本的周游路线问题。有很多实际问题可归结为货郎担问题。例如,邮路问题就是一个货郎担问题。假定有一辆邮车要到n个不同的地点收集邮件,这种情况可以用n+1个结点的图来表示。一个结点表示此邮车出发并要返回的那个邮局,其余的n个结点表示要收集邮件的n个地点。由地点i到地点j的距离则由边〈i,〉j上所赋予的成本来表示。邮车所行经的路线是一条周游路线,希望求出具有最小长度的周游路线。第二个例子是在一条装配线上用一个机械手去紧固待装配部件上的螺帽问题。机械手由其初始位置(该位置在第一个要紧固的螺帽的上方)开始,依次移动到其余的每一个螺帽,最后返回到初始位置。机械手移动的路线就是以螺帽为结点的一个图中的一条周游路线。一条最小成本周游路线将使这机械手完成其工作所用的时间取最小值。注意:只有机械手移动的时间总量是可变化的。第三个例子是产品的生产安排问题。假设要在同一组机器上制造n种不同的产品,生产是周期性进行的,即在每一个生产周期这n种产品都要被制造。要生产这些产品有两种开销,一种是制造第i种产品时所耗费的资金(1≤i≤n),称为生产成本,另一种是这些机器由制造第i种产品变到制造第j种产品时所耗费的开支cij,称为转换成本。显然,生产成本与生产顺序无关。于是,我们希望找到一种制造这些产品的顺序,使得制造这n种产品的转换成本和为最小。由于生产是周期进行的,因此在开始下一周期生产时也要开支转换成本,它等于由最后一种产品变到制造第一种产品的转换成本。于是,可以把这个问题看成是一个具有n个结点,边成本为cij的图的货郎担问题。货郎担问题要从图G的所有周游路线中求取具有最小成本的周游路线,而由始点出发的周游路线一共有(n-1)!条,即等于除始结点外的n-1个结点的排列数,因此货郎担问题是一个排列问题。排列问题比子集合的选择问题(例如,0/1背包问题就是这类问题)通nn常要难于求解得多,这是因为n个物体有n!种排列,只有2个子集合(n!>O(2))。通过枚举(n-1)!条周游路线,从中找出一条具有最小成本的周游路线的算法,其计算时间显然为O(n!)。为了改善计算时间必须考虑新的设计策略来拟制更好的算法。动态规划就是待选择的设计策略之一。但货郎担问题是否能使用动态规划设计求解呢?下面就来讨论这个问题。不失一般性,假设周游路线是开始于结点1并终止于结点1的一条简单路径。每一条\n第6章动态规划147周游路线都由一条边〈1,k〉和一条由结点k到结点1的路径所组成,其中k∈V-{1};而这条由结点k到结点1的路径通过V-{1,k}的每个结点各一次。容易看出,如果这条周游路线是最优的,那么这条由k到1的路径必定是通过V-{1,k}中所有结点的由k到1的最短路径,因此最优性原理成立。设g(i,S)是由结点i开始,通过S中的所有结点,在结点1终止的一条最短路径长度。g(1,V-{1})是一条最优的周游路线长度。于是,可以得出g(1,V-{1})=min{c1k+g(k,V-{1,k})}(6.19)2≤k≤n将式(6.19)一般化可得g(i,S)=min{cij+g(j,S-{j})}(6.20)j∈S如果对于k的所有选择,知道了g(k,V-{1,k})的值,由式(6.19)就可求解出g(1,V-{1})。而这些g值则可通过式(6.20)得到。显然,g(i,ꯁ)=ci1,12时,得到OFT和POFT调度的一般问题是难于计算的问题,得到OMFT调度的一般问题也是难于计算的问题(见第10章)。本节只讨论当m=2时获取OFT调度这种特殊情况的算法。为方便起见,用aj表示t1i,bj表示t2i。在两台设备情况下容易证明:在两台设备上处理的任务若不按作业的排列次序处理,则在调度完成时间上不比按次序处理弱(注意:若m>2则不然)。因此,调度方案的好坏完全取决于这些作业在每台设备上被处理的排列次序。当然,每个任务都应在最早的可能时间开始执行。图6.15所示的调度就完全由作业的排列次序5,1,3,2,4图6.15一种调度所确定。为讨论简单起见,假定ai≠0,1≤i≤n。事实上,如果允许有ai=0的作业,那么最优调度可通过下法构造出来:首先对于所有ai≠0的作业求出一种最优调度的排列,然后把所有ai=0的作业以任意次序加到这一排列的前面。容易看出,最优调度的排列具有下述性质:在给出了这个排列的第一个作业后,剩下的排列相对于这两台设备在完成第一个作业时所处的状态而言是最优的。假设对作业1,2,⋯,k的一种调度排列为σ1,σ2,⋯,σk。对于这种调度,设h1和h2分别是在设备P1和P2上完成任务(T11,T12,⋯,T1k)和(T21,T22,⋯,T2k)的时间;又设t=h2-h1,那么,在对作业1,2,⋯,k作了一系列决策之后,这两台设备所处的状态可以完全由t来表征。它表示如果要在设备P1和P2上处理一些别的作业,则必须在这两台设备同时处理前k个作业的不同的任务后,设备P2还要用大小为t的时间段处理前k个作业中没处理完的任务,即在t这段时间及其以前,设备P2不能用来处理别的作业的任务。记这些别的作业组成的集合为S,假设g(S,t)是上述t下S的最优调度长度,于是作业集合{1,2,⋯,n}的最优长度是g({1,2,⋯,n},0)。由最优调度排列所具有的性质可得g({1,2,⋯,n},0)=min{ai+g({1,2,⋯,n}-{i},bi)}(6.23)1≤i≤n将式(6.23)推广到一般情况,对于任意的S和i可得式(6.24)。式中,要求g(ꯁ,t)=max{t,0}且ai≠0,1≤i≤n。g(S,t)=min{ai+g(S-{i},bi+max{t-ai,0})}(6.24)i∈S式(6.24)中max{t-ai,0}这一项由以下推导得出。由于任务T2在max{aj,t}这段时间及其以前不能用设备P2处理,因此,h2-h1=bj+max{ai,t}-ai=bi+max{t-ai,0}。本来,可以使用一种与求解式(6.20)类似的方法求解g(S,t),但它有这么多不同的S,\n150计算机算法基础而对这些S都要计算g(S,t),因此这样计算最优调度长度g({1,2,⋯,n},0)至少要花费nO(2)的时间。下面介绍另一种代数方法来求解式(6.24),可得到一组非常简单的规则,使用这组规则可以在O(nlogn)的时间内产生最优调度。考虑这些作业的一子集S的任意一种调度R。假设直到t这段时间P2都不能用来处理S中的作业。如果i和j是这调度中排在前面的两个作业,则由式(6.24)就得到g(S,t)=ai+g(S-{i},bi+max{t-ai,0})=ai+aj+g(S-{i,j},b+max{bi+max{t-ai,0}-aj,0})(6.25)令tij=bj+max{bi+max{t-ai,0}-aj,0}=bj+bi-aj+max{max{t-ai,0},aj-bi}=bj+bi-aj+max{t-ai,aj-bi,0}=bj+bi-aj-ai+max{t,ai+aj-bi,ai}(6.26)如果作业i和j在R中互易其位,那么完成时间g′(S,t)=ai+aj+g(S-{i,j},tji)其中,tji=bj+bi-aj-ai+max{t,ai+aj-bj,aj}。将g(S,t)与g′(S,t)相比较可以看出,如果下面的式(6.27)成立,则g(S,t)≤g′(S,t)。max{t,ai+aj-bi,ai}≤max{t,ai+aj-bj,ai}(6.27)为了使式(6.27)对于t的所有取值都成立,则需要max{ai+aj-bi,ai}≤max{ai+aj-bj,aj}即,ai+aj+max{-bi,-aj}≤ai+aj+max{-bj,-ai},或min{bi,aj}≥min{bj,ai}(6.28)由式(6.28)可以断定存在一种最优调度,在这调度中,对于每一邻近的作业对(i,j),有min{bi,aj}≥min(bj,ai)。不难证明,具有这一性质的所有调度都有相同的长度。由此足以产生对于每个邻近作业对都满足式(6.28)的任何调度。根据式(6.28)作以下的观察就能得到具有这一性质的调度。如果min{a1,a2,⋯,an,b1,b2,⋯,bn}是ai,那么作业i就应是最优调度中的第一个作业。如果min{a1,a2,⋯,an,b1,b2,⋯,bn}是bj,那么作业j就应是最优调度中的最后一个作业。这使我们能作出一个决策,以便决定n个作业中的一个作业应放的位置。然后又将式(6.28)用于其余的n-1个作业,再正确地决定另一作业的位置,等等。因此,由式(6.28)导出的调度规则如下。(1)把全部ai和bi分类成非降序列。(2)按照这一分类次序考察此序列:如果序列中下一个数是aj且作业j还没调度,那么在还没使用的最左位置调度作业j;如果下个数是bj且作业j还没调度,那么在还没使用的最右位置调度作业j;如果已经调度了作业j,则转到该序列的下一个数。要指出的是,上述规则也正确地把ai=0的那些作业放在适当位置上,因此对这样的作业不用分开考虑。例6.19设n=4,(a1,a2,a3,a4)=(3,4,8,10)和(b1,b2,b3,b4)=(6,2,9,15),对这些a和b分类后的序列是(b2,a1,a2,b1,a3,b3,a4,b4)=(2,3,4,6,8,9,10,15),设σ1,σ2,σ3,σ4是最优调度。由于最小数是b2,置σ4=2。下一个数是a1,置σ1=1。接着的最小数是a2,由于作业2已被调度,转向再下一个数b1。作业1已被调度,再转向下一个数a3,置σ2=3。最\n第6章动态规划151后剩σ3是空的,而作业4还没调度,从而σ3=4。习题六6.1(1)递推关系式(6.8)对图6.16成立吗?为什么?(2)递推关系式(6.8)为什么对于含有负长度环的图不能成立?6.2修改过程ALL-PATHS,使其输出每对结点(i,j)间的最短路径,这个新算法的时间和空间复杂度是多少?6.3对于标识符集(a1,a2,a3,a4)=(end,goto,print,stop),已知成功检索概率为P(1)=1/20,P(2)=1/5,P(3)=1/10,P(4)=1/20,不成功检索概率为Q(0)=1/5,Q(1)=1/10,Q(2)=1/5,Q(3)图6.167个结点的有向图=1/20,Q(4)=1/20。用算法OBST对其计算W(i,j),R(i,j)和C(i,j)(0≤i,j≤4)。26.4(1)证明算法OBST的计算时间是O(n)。(2)在已知根R(i,j)(0≤i mthenprint(′stackoverflow′)8stop9endif10STACK(i)←P;P←LCHILD(P)11repeat12loop13callVISIT(P)∥P的左子树周游完∥14P←RCHILD(P)15ifP≠0thenexitendif∥周游右子树∥16ifi=0thenreturnendif17P←STACK(i);i←i-118repeat∥访问父结点∥\n第7章基本检索与周游方法15719repeatendINORDER1通过二元树T中的结点数n来分析INORDER1的计算时间。在第5~11行的while循环的每一次迭代中,把一个结点存入栈(第10行),存入栈中的每一个结点都要被访问(第13行)。由于没有结点被访问一次以上,因此,在此算法的整个执行过程中第5~11行的循环就不可能迭代n次以上。事实上,至多有n-1个结点可能存入栈,这是因为叶子结点都不存入栈(第5行)并且n≥1的每一棵树至少有一个叶子结点。所以第5~11行的总的时间是O(n)。在第12~18行的循环的每次迭代中,有一个结点被访问。由于T中的每一个结点实际上被访问一次,而且在这算法中任何别的地方都不对结点进行访问,因此该算法中的这一循环迭代n次。故这循环所需要的总的时间是Θ(n)。所以,INORDER1的时间复杂度是Θ(n)。就栈空间而论,只有那些具有非空左子树的结点可能存入栈。当T是一棵左斜二元树时,其最坏情况就会发生(见图7.3(b))。在一棵左斜二元树中,除了叶子结点外的每一个结点都有一棵非空左子树和一棵空右子树。在这种情况下,需要大小为n-1的栈,如果每一个结点都有一棵空左子树并且除了叶子结点之外的所有结点都有一棵非空的右子树就会发生最好的情况。这样的一棵二元树是右斜二元树(见图7.3图7.3斜二元树(a))。在这种情况下,没有结点进入栈。用T的深度(a)右斜树;(b)左斜树来表示所需的栈空间则更为有效。可以证明,若T的深度为d,则所需的栈空间为O(d)。由以上分析可知,所有的周游算法都必须访问每一个结点,因此计算时间应该至少是Θ(n)。对所使用的附加空间(即栈空间)作一些改进是唯一可探讨之处。下面介绍在Θ(n)时间和Θ(1)空间内周游二元树的一种方法。如果每个结点有一个PARENT信息段与它的父结点相连接,则周游可以在Θ(n)时间和Θ(1)空间内完成,对此的算法设计与分析留作习题。在这里,将在没有PARENT信息段的情况下去获取一个具有类似特性的算法。PARENT信息段的存在使我们能从任何一个结点P到达根结点。为了得到一个空间为Θ(1)的算法,可通过颠倒由根结点到当前正被检查的结点的链的方向来完成这一效能。譬如,若假定P指向树T中当前正在检查的那个结点,Q指向它的父亲,那么将保留一条由Q到根T的路径,这条路径叫做Q-T路径,它由T到Q路径上的所有结点链接到一起而组成。如果U、V和W是这条路径上的这么3个结点———U是V的父亲而V是W的父亲,那么在W是V的右儿子的情况下就将V通过它的RCHILD信息段链接到U。否则就通过它的LCHILD信息段把V链接到U。但是,使用颠倒结点链接方向的方法,在访问了P所指结点以后,要将Q所指结点与P所指结点再链接在一起就会出现如下问题:设P所指结点为X,Q所指结点为Y,如果已周游了以X为根的子树的所有结点,此时要是Y只有这一个儿子结点,就可以通过LCHILD(Y)或RCHILD(Y)是否为零来判断X是Y的右儿子还是左儿子。因此,在这种\n158计算机算法基础情况下,将X与Y重新链接起来没有什么问题。但是,如果Y有两个儿子,则此时的LCHILD(Y)和RCHILD(Y)一个是Q-T路径上的一个倒挂链,一个指向Y的另一个儿子,于是,判断X是左儿子还是右儿子就出现了困难。为了解决这一问题,根据中根次序周游的定义,可以在访问Y的时候,将Y存入一临时工作单元LR,这样在周游了以X为根的子树后,如果LR中的内容是Y,则说明X是Y的右儿子,反之,则是左儿子。但是,Q-T路径上有两个儿子的结点可能有多个,而在访问它们时又都应将它们保存起来,因此不可避免地要引进栈。为了节省空间,可利用已访问过的叶子结点的LCHILD和RCHILD信息段来建立一个链接表结构的栈,用LCHILD信息段存放一个有两个儿子的结点,用RCHILD信息段作为链接指针。算法7.5用以上方法对中根次序周游进行了具体描述。除上面引进的临时工作单元P、Q和LR外,还引进了临时工作单元AV、TOP和R。其中,AV用来指示当前可作栈使用的叶子结点,TOP和R的作用在算法中一眼就可看出。算法7.5在Θ(n)的时间和Θ(1)的空间内周游二元树的算法lineprocedureINORDER2(T)∥使用固定量的附加空间,二元树T作中根次序周游∥1ifT=0thenreturnendif∥二元树为空∥2TOP←LR←0;Q←P←T∥置初值∥3loop4loop∥尽可能地往下移∥5case6:LCHILD(P)=0andRCHILD(P)=0:∥不能往下移了∥7callVISIT(P);exit8:LCHILD(P)=0:∥移到RCHILD(P)∥9callVISIT(P)10R←RCHILD(P);RCHILD(P)←QQ←P;P←R11:else:∥移到LCHILD(P)∥12R←LCHILD(P);LCHILD(P)←Q;Q←P;P←R13endcase14repeat∥P是叶结点,向上移到其右子树还没检查的结点∥15AV←P∥作为栈使用的叶结点∥16loop∥由P向上移∥17case18:P=T:return∥回退到了根,返回∥19:LCHILD(Q)=0:∥Q由RCHILD链接∥20R←RCHILD(Q);RCHILD(Q)←P;P←Q;Q←R21:RCHILD(Q)=0:∥Q由LCHILD链接∥22R←LCHILD(Q);LCHILD(Q)←P;P←Q;Q←R;\n第7章基本检索与周游方法159callVISIT(P)23:else:∥检查P是否为Q的右儿子∥24ifQ=LRthen∥P是Q的右儿子∥25R←TOP;LR←LCHILD(R)∥修改LR∥26TOP←RCHILD(R)∥退栈∥27LCHILD(R)←RCHILD(R)←0∥释放作栈用的叶结点∥28R←RCHILD(Q);RCHILD(Q)←P;P←QQ←R29else∥P是Q的左儿子∥30callVISIT(Q)31LCHILD(AV)←LR;RCHILD(AV)←TOP32TOP←AV;LR←Q33R←LCHILD(Q);LCHILD(Q)←P∥恢复到P的链接∥34P←RCHILD(Q);RCHILD(Q)←R;exit∥移到右边∥35endif36endcase37repeat38repeat39endINORDER2为便于理解INORDER2,图7.4描述了用INORDER2周游图7.2所示树的基本过程。2行:置初值,Q-T路径为空。11~13行:P移到B,Q-T路径只含根结点A,LCHILD(A)=A表示Q-T路径末端。11~13行:P移到D,结点B由LCHILD链接到Q-T8~10行:访问D,P移到B的右子树(结点G),D由RCHILD链接到Q-T图7.4用Θ(1)的附加空间周游图7.2的二元树\n160计算机算法基础6~7行:再不能下移,访问G。21~22行:继续回退,Q指向A,P指向B并恢复15行:叶结点G将作栈使用,存入AV。LCHILD(B)指向D。19~20行:沿Q-T路径回退,Q指向B,P指向D并恢复由于从B的左子树返回(注意:这是根据RCHILD(B)=RCHILD(D)指向G。0),故访问B且回退到A。图示同(c)图示同(d)(e)(f)23,24,29~35行:由于Q(即A)的左、右儿子均不为0,且LR≠Q,说明P是Q的左儿子,故访问A。然后将LR中的值进栈(即存入LCHILD(G)),原栈顶地址置于RCHILD(G),现栈顶地址变为结点G的地址。再将A存入LR。由A的RCHILD链接到Q-T,并恢复LCHILD(A)指向B,P移到C。11~13行:P移到E,结点C由LCHILD链接到Q-T。6~7行:再不能下移,访问E。15行:叶结点E将作栈使用,存入AV。23,24,29~35行:说明与(g)类似。访问C。6~7行:访问F23~28行:继续回退,说明与(j)类似。15行:F存入AV23~28行:沿Q-T路径回退,由于Q=LR,说明F是C的18行:由于P=T,表示已回退到根,返回。右儿子。修改LR。退栈,释放作栈用的叶结点E。P指向C,Q指向A,恢复RCHILD(C)=F。图示同(g)图示同(a)(j)(k)续图7.4用Θ(1)的附加空间周游图7.2的二元树现在,分析算法INORDER2需要的计算时间和辅助空间。设二元树的结点数为n,又设度为0,1和2的结点数分别为n0,n1和n2,于是n=n0+n1+n2。显然,P指向一个0度结点的次数为1,这种情况出现在第4~14行的循环中P下移到达这个0度结点之时。而P将两次指向只有一个儿子的结点,一次是在下移的时候,另一次则在从它的儿子向上移动的\n第7章基本检索与周游方法161时候(第16~37行)。对于有两个儿子的结点,P也将两次指向该结点,一次在下移时(第4~14行),另一次是在从它的右儿子处上移时(第16~37行),而当从它的左儿子处上移时(第29行),虽然此时访问该结点,但P被修改成指向该结点的右儿子。由此P值改变的总次数是n0+2n1+2n2。在第4~14行的循环的每次迭代中,如果P不是叶子,则P的值就改变;如果P是叶子,则转出这个循环且在第16~37行的循环中改变P的值,即P是叶子时既进入第4~14行的循环,又进入第16~37行的循环。另外,第16~37行的循环的每一次迭代都改变P的值。所以,第4~14行和第16~37行这两个循环的迭代总数为2n0+2n1+2n2。而这两个循环中的任何一次迭代所需的时间是Θ(1),故第3~38行循环的总时间是Θ(2n0+2n1+2n2)=Θ(n)。第1行和第2行都只花Θ(1)的时间,所以总共需要的时间是Θ(n)。由于算法中只有一些像P、Q、AV、LR、TOP和R之类的简单变量,因此所需的辅助空间为Θ(1)。值得指出的是,在所用辅助空间上,INORDER2虽比INORDER1节省,但这种节省是以增加计算时间为代价的,因此,只有在INORDER1所要求的辅助空间O(d)无法满足的情况下才考虑使用INORDER2。7.1.2树周游可以用类似于定义二元树的周游方法对树的周游进行定义。由于树的子树一般无顺序可言,而对二元树的各种周游算法又是建立在它的子树有严格顺序(左、右子树)这一基础之上的,因此,为便于对树周游作出定义,不妨假定树的子树也有某种顺序。这样一来,说一个结点的第一、第二、第三棵子树等就有了意义。由树和森林之间的关系知道,一棵树恰好是具有一棵树的森林,而一个森林可由去掉一棵树的根而得到,因此,利用森林的周游来递归定义树周游是很方便的。树周游一般也有3种方法,分别叫做树先根次序周游,树中根次序周游和树后根次序周游。设F是一个森林,这3种周游方法可描述如下。1.树先根次序周游(F)(1)若F为空,则返回;(2)访问F的第一棵树的根;(3)按树先根次序周游F的第一棵树的子树;(4)按树先根次序周游F其余的树。2.树中根次序周游(F)(1)若F为空,则返回;(2)按树中根次序周游F的第一棵树的子树;(3)访问F的第一棵树的根;(4)按树中根次序周游F其余的树。3.树后根次序周游(F)(1)若F为空,则返回;(2)按树后根次序周游F的第一棵树的子树;(3)按树后根次序周游F其余的树;(4)访问F的第一棵树的根。由于树在一般情况下都是用其相应的二元树来表示,因此,这里就不再给出树周游的详\n162计算机算法基础细算法。在后几节中,可以看到树后根次序周游的一些例子。假定T是由森林F转换成的二元树,由转换方法可知,对T的先根次序和中根次序周游与F上的这些周游有一个自然的对应,即T的先根次序周游相当于按树先根次序周游访问F的结点,T的中根次序周游相当于按树中根次序周游访问F的结点。但对T的后根次序周游则没有类似的自然对应。7.1.3图的检索和周游与图有关的一个基本问题是路径问题。就其最简单的形式而论,它要求确定在一个给定的图G=(V,E)中是否存在一条起自结点v而终于结点u的路径。一种更一般的形式则是确定与某已知起始结点v∈V有路相通的所有结点。后面这个问题可以从结点v开始,通过有次序地检索这个图G上由v可以到达的结点而得到解决,对此问题介绍下面两种检索方法。1.宽度优先检索和周游在宽度优先检索中,从结点v开始,并给v标上已到达(即访问)的标记(此时,称结点v还没检测。当一个算法访问了邻接于某结点的所有结点时,就说该结点已由此算法检测了)。下一步,访问邻接于v的所有未被访问的结点,这些结点是新的没检测的结点。而结点v现在已检测过了。由于这些新近已访问的结点还没有检测,于是将它们放置到未检测结点表的末端。这表上的第一个结点是下一个要检测的结点。这个未检测结点表起一个队列的作用并且可以用任何一种标准的队列表示形式来表示它。过程BFS描述了这一检索的细节。这个过程还用了两个算法DELETEQ(v,Q)和ADDQ(v,Q)。算法DELETEQ(v,Q)从队列Q删除一个结点并带着这个被删除的结点返回。算法ADDQ(v,Q)将结点v加到队列Q的尾部。在图7.5(a)所示的无向图上检验这个算法。假定这个图用邻接表(图7.5(b))来表示,这些结点就按1,2,3,4,5,6,7,8这样的次序被访问。而对图7.5(c)所示的有向图,其起始结点为1的宽度优先检索只访问结点1,2,3。由结点1不能到达结点4。图7.5例图和邻接表(a)无向图G;(b)G的邻接表;(c)有向图算法7.6宽度优先检索算法lineprocedureBFS(v)∥宽度优先检索G,它在结点v开始执行。所有已访问结点都标上VISITED(i)=1。图G\n第7章基本检索与周游方法163和数组VISITED是全程量。VISITED开始时都已置成0∥1VISITED(v)←1;u←v2将Q初始化为空∥Q是未检测结点的队列∥3loop4for邻接于u的所有结点wdo5ifVISITED(w)=0thencallADDQ(w,Q)∥w未检测∥6VISITED(w)←17endif8repeat9ifQ为空thenreturnendif∥不再有还未检测的结点∥10callDELETEQ(u,Q)∥从队中取一个未检测结点∥11repeat12endBFS定理7.2算法BFS访问由v可到达的所有结点。证明设G=(V,E)是一个(有向或无向)图,且v∈V。通过施归纳法于由v到所有可达结点w∈V的最短路径的长度来对这个定理进行证明。用d(v,w)来表示由v到某一可达结点w的最短路径的长度(即边数)。显然,d(v,w)≤1的所有结点w都要被访问。现在假定所有d(v,w)≤r的结点都要被访问,进而证明d(v,w)=r+1的所有结点都要被访问。设w是V中一个d(v,w)=r+1的结点,又设u是一个在v到w的最短路径上紧挨w的前一结点,于是d(v,u)=r,因此u通过BFS被访问。假定u≠v且r≥1。于是,u在临访问之前被放到未检测结点队列Q上,而这个算法直到Q变成空时才会终止,因此,u必定在某个时刻从Q中移出,而且所有邻接于u的未访问结点在第4~8行的循环中被访问,所以w也被访问。证毕。定理7.3设t(n,e)和S(n,e)是算法BFS在任一具有n个结点和e条边的图G上所花的最大时间和最大附加空间。若G由邻接表表示,则t(n,e)=Θ(n+e)和S(n,e)=2Θ(n)。若G由邻接矩阵表示,则t(n,e)=Θ(n)和S(n,e)=Θ(n)。证明只有在第5行的结点才被加到队列。仅当结点w有VISITED(w)=0时,它才可以加到队列上。在将w加到队列之后,紧接着就把VISITED(w)置成1(第6行)。因此,每个结点都至多只有一次被放在队列上。结点v不会放在队列上,所以至多做n-1次加入队列的动作,需要的队列空间至多是n-1。其余的变量所用的空间为O(1)。所以S(n,e)=O(n)。如果G是一个具有v与其余的n-1个结点相连接的图,那么邻接于v的全部n-1个结点都将在同一时刻被放在队列上。此外,数组VISITED需要Θ(n)的空间。因此,S(n,e)=Θ(n)。这结果与使用的是邻接矩阵还是邻接表无关。如果使用邻接表,那么对邻接于u的所有结点可以在时间d(u)内作出判断。若G是无向图,则d(u)是u的度数;若G是有向图,则d(u)是u的出度。因此,当结点u被检测时,第4~8行循环的时间是Θ(d(u))。因为G中的每一个结点至多可以被检测一次,所以第3~11行的循环的总时间是O(Σd(u))=O(e)。VISITED(i)应初始化为0,1≤i≤n。这要花费O(n)的时间。从而总的时间是O(n+e)。如果使用邻接矩阵,那么判断所有邻接于u2的结点要花Θ(n)的时间,因此总的时间就变成为O(n)。如果G是一个由v可到达所有结\n164计算机算法基础2点的图,那么就要检测所有的结点,因此总的时间至少分别是O(n+e)和O(n)。故使用邻2接表时t(n,e)=Θ(n+e),使用邻接矩阵时t(n,e)=Θ(n)。证毕。如果在一个连通无向图G上使用BFS,则G中的所有结点都要被访问,因此该图被周游。但是,若G是非连通的,则G中至少有一个结点没被访问。通过每一次用一个新的未访问的起始结点来反复调用BFS,就可以作出对这个图的一次完全周游。所导出的这个周游算法叫做宽度优先周游算法(BFT),定理7.3的证明也可以用来证明在一个n个结点e条边的图上,若使用邻接表,BFT所要求的时间和附加空间分别是Θ(n+e)和Θ(n)。若使2用邻接矩阵,则分别囿界于Θ(n)和Θ(n)。算法7.7宽度优先图周游算法procedureBFT(G,n)∥G的宽度优先周游∥declareVISITED(n)fori←1tondo∥将所有结点标记为未访问∥VISITED(i)←0repeatfori←1tondo∥反复调用BFS∥ifVISITED(i)=0thencallBFS(i)endifrepeatendBFT如果G是一个连通无向图,则在第一次调用BFS时就会访问G的所有结点。如果G是非连通的,则至少需要调用BFS两次,因此,BFS可以用来判断G是否连通。而且在BFT对BFS的一次调用时,新近访问的所有结点表明这些结点在G的一个连通分图之中,所以一个图的连通分图可以用BFT获得。为此,BFS可以作如下的修改:将新近访问的所有结点都放在一个表中,于是由这个表中的结点所构成的子图和这些结点的邻接表合在一起就构成一个连通分图。因此,若使用邻接表,宽度优先周游算法将在Θ(n+e)的时间内得到这*些连通分图。BFT也可用来得到一个无向图G的自反传递闭包矩阵。设A是这个矩阵,*当且仅当i=j或者i≠j而i和j在同一个连通分图中时,A(i,j)=1。为了判断在i≠j的情*况下A(i,j)是1还是0,可以构造一个数组CONNEC(1∶n),数组元素CONNEC(i)表示*结点i所在的那个连通分图的标记数,当CONNEC(i)=CONNEC(j)时,A(i,j)=1,反之为0。这个CONNEC数组可以在O(n)时间内构造出来。因此,有n个结点e条边的无向图2G的自反传递闭包矩阵,不管是使用邻接表还是使用邻接矩阵,都可以在Θ(n)的时间和*Θ(n)空间内算出(此空间计算不包括A本身所需要的空间)。作为宽度优先检索的最后一个应用,考虑获取无向图G的生成树问题。当且仅当G连通时,它有一棵生成树。从而BFS易于判断生成树的存在。此外,考虑在算法BFS的第4~8行中为到达那些未访问结点w所使用的边(u,w)的集合。这些边称为前向边(forwardedges)。设T表示前向边的集合。我们断言,如果G是连通的,则T是G的一棵生成树。对于图7.5(a)所示的图,边集T将是G中除了(5,8),(6,8),(7,8)以外的所有的边的集合(图7.6(a))。使用宽度优先检索所得到的生成树称为宽度优先生成树(breadthfirstspan-ningtree)。\n第7章基本检索与周游方法165图7.6图7.5(a)中的图的BFS和DFS生成树定理7.4修改算法BFS,在第1行和第6行分别增加语句T←ꯁ和T←T∪{(u,w)}。**修改后的算法叫做算法BFS。如果在v是连通无向图中任一结点的情况下调用BFS,那么在算法终止时,T中的边组成G的一棵生成树。证明若G是n个结点的连通图,则这n个结点都要被访问。而且除了起始结点v外,所有其它的结点都要放到队列上一次(第5行),从而T将正好包含n-1条边。而且这些边都是各不相同的。因此,T中的这n-1条边将定义一个关于这n个结点的无向图。由于这个图包含由起始结点v到所有其它结点的路径(因此在每个结点对之间存在一条路径),所以这个图是连通的。用归纳法容易证明对于有n个结点且正好有n-1条边的连通图是一棵树。因此T是G的一棵生成树。证毕。宽度优先检索有广泛的应用,其中解决最优化问题的一种重要方法:分枝-限界法(第七章)就是在宽度优先检索基础上建立起来的。2.深度优先检索和周游一个图的深度优先检索与宽度优先检索的差别在于一有新的结点到达就中止对原来结点v的检测,且同时开始对新结点u的检测。在此新结点被检测后,再恢复对v的检测。当所有可达结点全部被检测完毕时,就结束这一检索过程。这一检索过程最好用递归描述成算法5.8那样的形式。算法7.8图的深度优先检索lineprocedureDFS(v)∥已知一个n结点的无向(或有向)图G=(V,E)以及初值已置为零的数组VISITED(1∶n)。这个算法访问由v可以到达的所有结点。G和VISITED是全程量∥1VISITED(v)←12for邻接于v的每个结点wdo3ifVISITED(w)=0thencallDFS(w)endif4repeat5endDFS图7.6(a)起始于结点1并且使用图7.5(b)的邻接表,对于这个图的深度优先检索导致按1,2,4,8,5,6,3,7的次序去访问这些结点。对于DFS的非递归算法要使用栈来保留已部分检测的所有结点。容易证明DFS访问由v可达的所有结点。如果t(n,e)和S(n,e)表示DFS对一n个结点e条边的图所花的最大时间和最大附加空间,那么S(n,e)=Θ(n),在2使用邻接表的情况下,t(n,e)=Θ(n+e),而在使用邻接矩阵的情况下,t(n,e)=Θ(n)。一个图的深度优先周游是通过每次用一个新的未访问的起始结点来反复调用DFS实\n166计算机算法基础现的。这个DFT算法与BFT的区别仅在于用对DFS(i)的调用去代替对BFS(i)的调用。和在BFT的情况下一样,使用DFT可以得到一个图的那些连通分图。同样,也可以用DFT求出一个无向图的自反传递闭包矩阵。只要对DFS作如下修改:把T←ꯁ和T←T∪{(v,w)}分别加到第1行和第3行then的子句,那么,DFS终止时,在无向图G是连通的条件下,T中的这些边就定义了G的一棵生成树。用这种方法所得到的生成树叫深度优先生成树(depthfirstspanningtree)。图7.5(a)所得到的生成树包含除(2,5),(8,7)和(1,3)以外所有的边(见图7.6(b))。因此,DFS和BFS对迄今所讨论的检索问题是等效的。由以上讨论可知,BFS和DFS是两种根本不同的检索方法。在BFS中,一个结点在任何其它结点开始检测之前要完全被检测,这下一个要检测的结点是剩下未检测的第一个结点。习题中还讨论了一种检索方法(D-search),它与BFS的区别仅在于下一个要检测的结点是最新到达的未检测结点。在DFS中,一旦到达一个新的未检测结点,则中止原来那个结点的检测,并立即开始这个新结点的检测。显然DFS和D-search的实现都需要一个栈,但这两种检索方法是不同的。本节中所介绍的检索方法可用于各种各样的问题。7.2代码最优化编译程序的作用是,把按某种源语言写成的程序翻译成一个等效的汇编语言程序或机器语言程序。考察这么一个问题,把按某种语言(例如PASCAL)写成的算术表达式翻译成汇编语言代码。这种翻译显然依赖于正使用的那种特定的汇编语言(因此,依赖于所用的机器)。开始,假定有一台非常简单的机器模型,把这模型叫做模型机A。这台机器只有一个称为累加器的寄存器。所有的算术运算都必须在这寄存器中进行。如果○·代表像+、-、*、/这样的一个双目运算符,则○·的左运算量必须在这累加器中。为简单起见,将讨论只局限于这4个运算符。这一讨论易于推广到其它的运算符。相应的汇编语言指令是LOADX⋯将内存单元X的内容装入累加器;STOREX⋯将累加器的内容存入内存X单元;OPX⋯OP可以是ADD,SUB,MPY或DIV。指令OPX用累加器的内容作为左运算量,X存储单元的内容作为右运算量进行操作符OP的计算。作为一个例子,考虑算术表达式:(a+b)/(c+d)。这表达式的两个可能的汇编语言模式在表7.2中给出。T1和T2是内存中的临时存储单元。在这两种情况下,结果都留在累加器中。代码段(a)比代码段(b)长两条指令。如果每条指令用的时间量相同,则代码段(b)将比代码段(a)所用的时间少25%。对于表达式(a+b)/(c+d)和给定的机器A,代码段(b)显然是最优的。定义7.1表达式E翻译成某给定机器的机器语言或汇编语言是最优的,当且仅当这一翻译有最少的指令条数。下面再来看3个例子。考虑表达式a+b*c。表7.3显示了两种可能的翻译。代码段(b)虽不符合+的左运算量应放在累加器、右运算量应放在内存储器中的要求,但由于x+y=y+x,因此代码段(b)与(a)是等效的。\n第7章基本检索与周游方法167表7.2(a+b)/(c+d)两种可能的代码段表7.3a+b*c的代码段LOADaLOADcLOADbLOADbADDbADDdMPYcMPYcSTORET1STORET1LOADcLOADaSTORET1ADDaADDdADDbLOADaSTORET2DIVT1LOADT1ADDT1DIVT2(a)(b)(a)(b)定义7.2双目运算符○·在定义域D中是可交换的,当且仅当对于D中所有的a和b,有a○·b=b○·a。+和*运算符对于整数和实数是可交换的,而-和/运算则不是。利用某些运算符的可交换性可能得到较短的代码段。下面考虑表达式a*b+c*d。表7.4显示了两种可能的代码段。代码段(b)实际上计算(a+c)*b,它和a*b+c*b相等。表7.4a*b+c*b的代码段表7.5a*(b*c)+d*cLOADbLOADaLOADcLOADaMPYcMPYbMPYbADDcSTORET1ADDdLOADaSTORET1MPYbMPYT1STORET1LOADaLOADdMPYbMPYcSTORET2ADDT1LOADT1ADDT2(a)(b)(a)(b)定义7.3双目运算符○*相对于双目运算符ꯝ在定义域D中是左分配的,当且仅当对于D中的每一个a,b,c,有a○*(bꯝc)=(a○*b)ꯝ(a○*c)。○*相对于ꯝ是右分配的,当且仅当对于D中的每一个a,b,c,有(aꯝb)○*c=(a○*c)ꯝ(b○*c)。在实数范围内,*相对于+和-是左、右分配的,这是因为a*(b+c)=(a*b)+(a*c),a*(b-c)=(a*b)-(a*c),(a+b)*c=(a*c)+(b*c)和(a-b)*c=(a*c)-(b*c)。/相对于+和-不是左分配的,因为a/(b+c)≠(a/b)+(a/c)。但是在实数范围内,/是右分配的。不过,在整数范围内/相对于+和-不是右分配的。例如,(2+3)/5=1,而在整数算术中,由于2/5=0,3/5=0,得(2/5)+(3/5)=0。最后一个例子,考虑表达式a*(b*c)+d*c。表7.5描述了两种可能的代码段。表7.5(b)的代码段使用了(a*b)*c=a*(b*c)。定义7.4双目运算符○·在定义域D中是可结合的,当且仅当对于D中的所有的a,b,c有a○·(b○·c)=(a○·b)○·c。\n168计算机算法基础对于整数范围和实数范围,*运算是可结合的,/运算是不可结合的。利用运算符的可结合、分配和交换的性质,可能作出较短的代码段。注意,虽然在实数范围内,有(a+c)*b=(a*b)+(c*b),但是表7.4(a)和(b)的这两个代码段可能算出不同的答案。这种情况的出现是由于计算机有限长的算术运算在计算中会产生一些误差的缘故。在下面的讨论中将略去这一因素,并且假定在可应用结合律、交换律和分配律的时候都可以对它们自由地使用。对于一个给定的表达式可能给出不同的代码段,下面讨论怎样获取最优代码段的问题。开始,将讨论局限于这台简单的机器A。然后,再考察更一般的机器模型。运算符出现在它们的运算量之间的表达式的形式称为中缀形式。这是通常写算术表达式的方法。为了产生最优代码段,用二元树来表示算术表达式是方便的。二元树的每一个非叶子结点表示一个运算符。非叶子结点称为内部结点。某内部结点P的左子树表示由P所代表的运算符的左运算量的二元树形式。而右子树则表示其右运算量的二元树形式。一个叶子结点或者表示一个变量或者表示一个常数。图7.7给出了一些表达式的二元树形式。这些表示算术表达式的二元树称为表达式树。图7.7某些中缀表达式的二元树形式为了从表达式树得到产生最优代码段的算法,开始,假定所有的运算符既不可交换,也不可分配或结合,也不允许使用代数变换去化简表达式。例如,尽管a+b-a-b有值为0,但在上述假定下,最优代码段将是LOADa;ADDb;SUBa;SUBb。我们也不涉及对公共子表达式的处理,所有的子表达式都假定为互不相关的。因此,a*b*(a*b-d)的最优代码段与a*b*(c*e-d)的最优代码段相同。在这些假定下,易于看出若一个表达式有n个运算符,则它的代码段恰好有n条ADD,SUB,MPY,DIV这种类型的指令。将这类指令称为运算符指令。只有累加器的装入和存放指令条数会变化。例如,表7.2(a)和(b)的代码段都有3条运算符指令。代码段(a)有3条装入和两条存放指令,而代码段(b)只有两条装入和一条存放指令。易于证明在任何没有冗余指令的代码段中,除了第一条以外的每条装入指令都必须紧接在一条存放指令之后。因此,装入指令数总比存放指令数多1。所以只要产\n第7章基本检索与周游方法169生使装入指令数或存放指令数为最小值的代码段就是最优的代码。设P是任一表达式树的一个内结点;又设L和R分别是P的左、右子树;再设○·是在结点P的运算符。根据对运算符所作出的假定,计算L○·R的唯一方法是先独立地计算L和R,然后计算L○·R,L和R的代码段也应是最优的。假定已给出L和R的最优代码段CL和CP,那么计算L○·R就有表7.6所列出的那几种可能性。表中“,条件”这一列揭示了L和R的各种可能性以及在L和R不是叶子结点的情况下计算L和R的次序。在写出的代码中,○·a表示一条运算指令。如果○·是+,则指的是ADDa。表7.6揭示出,在产生L○·R的代码段时,只有L和R都是内部结点才有选择的机会。当L和R中的任何一个为叶子结点时,则(在①,②和③种条件下的)代码段是唯一的(禁止引进无用的代码)。当L和R都是内部结点时,条件⑤的代码段比条件④的要少些,所以应被采用。由此产生以下的观察结果,如果R是内部结点,在最优化码段中CR在CL的前面,否则,CL就在CR的前面。表7.6计算L○·R的各种可能性条件相应的代码段①L和R都是叶子,变量分别是a和bLOADa;○·b②L是具有变量a的叶子,R不是叶子CR;STORET1;LOADa;○·T1③R是具有变量a的叶子,L不是叶子CL;○·a④L和R都不是叶子,L在R之前计算CL;STORET1;CR;STORET2;LOADT1;○·T2⑤L和R都不是叶子,R在L之前计算CR;STORET1;CL;○·T1以上论述的与用分治方法或动态规划法是类似的。利用分治方法,可以先获得L和R的最优代码段,然后以某种方法将这些最优代码段结合起来而得出L○·R的最优代码段。用动态规划法可以将代码段看成是一系列决策的结果。其每一步都作出对某一个子表达式接着编码的决策。只有在已经产生了L和R的代码段后才可以对表达式L○·R编码。表7.6导致代码段的递归生成过程为CODE1(算法7.9)。这个算法使用过程TEMP(i)和RETEMP(i)。TEMP(i)得到临时存储器的一个存储单元,而RETEMP(i)则释放临时存储单元i。假定表达式树有一个由T所指示的根结点,并且每一个结点都有3个信息段LCHILD,RCHILD和DATA。内部结点的DATA信息段是运算符。叶子结点的此信息段是运算量地址。另外,该算法还假定T≠0。注意,这算法实质上执行对二元树T的周游,但是这一周游方法与7.1.1节中所讨论的3种方法都不相同的是,只有内部结点被访问。当访问一个结点时,就生成该结点的代码段。对一个结点的访问只有在它的两棵子树的代码段生成以后才能进行。这类似于后根次序周游。但是,在算法CODE1中,一棵非平凡的右子树在其相应的左子树之前被周游(一棵平凡的子树是只有一个根结点的子树)。如果把临时存储器当成一个栈来处理,而TEMP和RETEMP分别对应于从这个栈删去和插入的操作,则表7.7可显示由CODE1对图7.7中的某些例子所生成的代码段。根据前面的讨论,可以得出由CODE1所生成的代码段相对于机器A是最优的。在研究机器A的推广形式时,将给出较严密的证明。定理7.5用CODE1所产生的代码段能正确计算由表达式树T所表示的算术表达式的值。该定理的证明(对T的深度施行简单的归纳),留作习题。\n170计算机算法基础算法7.9生成代码段的算法procedureCODE1(T)∥假设T≠0,T是一棵表达式树,生成T的代码段∥ifT是叶子thenprint(″LOAD″,DATA(T))returnendifF←0∥如果RCHILD(T)不是叶子,则将F置成1∥ifRCHILD(T)不是叶子thencallCODE1(RCHILD(T))∥生成CR∥callTEMP(i)print(″STORE″,i)F←1endifcallCODE1(LCHILD(T))∥生成CL∥ifF=1thenprint(DATA(T),i)callRETEMP(i)elseprint(DATA(T),DATA(RCHILD(T)))endifendCODE1如果允许使用运算符的可交换性,CODE1在机器A上就不能产生出最优代码段。为了看清这一点,考察图7.7和表7.7的(b):当+可交换时,最优代码段是LOADb,MPYc,表7.7由CODE1对图7.7的一些例子所生成的代码段LOADaLOADbLOADaADDbMPYcADDbSTORET1MPYcLOADaADDT1(a)(b)(c)LOADcLOADeADDdADDfSTORET1STORET1LOADbLOADdADDT1MPYT1STORET1STORET1LOADaLOADbADDT1ADDcSTORET2LOADaDIVT2ADDT1(g)(h)\n第7章基本检索与周游方法171ADDa。如果○·是可交换的,表7.6中的可能性就要增加。为了在可交换运算符的情况下能产生最优代码段,需要修改CODE1。所需要的修改留下作为习题。现在,将机器A推广到另一种机器B。B有N≥1个可以执行算术运算的寄存器。对于B,有4种类型的机器指令:指令功能(1)LOADM,R将内存单元M的内容装入寄存器R,1≤R≤N。(2)STORER,M将寄存器R的内容存到内存单元M,1≤R≤N。(3)OPR1,M,R2执行(R1)OP(M)的计算,结果放在R2寄存器中。OP是任何一种双目运算符(例如,+,-,*,/),R1和R2是寄存器,M是一个内存单元。R1可等于R2。(4)OPR1,R2,R3与(3)类似。R1,R2和R3是寄存器。这些寄存器的某两个或全体可以相同。比较A和B这两种机器模型,可以看出,当N=1时模型B的(1),(2),(3)型指令与模型A相应的指令是相同的;(4)型指令只允许在没有附加存储存取装置的情况下执行像a+a,a-a,a*a和a/a这样一些普通的运算。这不会改变在A和B上产生最优代码段指令的条数。因此,当N=1时模型A在某种意义上与模型B是一样的。就模型B而言,一个给定的表达式E的最优代码段可以因N值的不同而不同。表7.8列出了图7.7所示的表达式(f)在N=1和N=2两种情况下的最优代码段。要指出的是,当N=1时,必须生成一条存放指令,而当N=2时,则不需要生成存放指令。寄存器标记成R1和R2。T1是存储器中的临时存储单元。并且注意,LOAD的条数不再需要正好比STORE的条数多1。因此,为了最优化而只考虑LOAD指令的条数或者只考虑STORE指令的条数就不够了。而是要使它们的和取最小值。为了简化这一讨论,首先假定没有运算符是可结合、可交换或可分配的。并且假定一个运算符的左、右两个运算量即使是相同的子表达式也必须分别进行计算,这一限制被扩大到表达式之类情况,例如a○·a,也要求对左、右运算量都作一次内存访问。表7.8N=1和N=2的最优代码段LOADc,R1LOADc,R1MPYR1,d,R1MPYR1,d,R1STORER1,T1LOADa,R2LOADa,R1ADDR2,b,R2ADDR1,b,R1DIVR2,R1,R1DIVR1,T1,R1N=1N=2图7.8计算最小的寄存器数给定一个表达式E,可能要问的第一个问题是:不用任何STORE指令可以算出E的值吗?第二个问题是:不用任何STORE指令而计算E的值所需要寄存器的最小数量是多少?在作了上面的那些假定的条件下来回答这些问题,并且还假定E的值将保留在这N个寄存器的一个之中。设E用一棵表达式树T来表示,若T只有一个结点,则这个结点必定是一个叶子,显然,所需要的全部工作就是把相应变量的值或常数装入到一个寄存器中。这种情况只需要一个寄存器,如图7.8(a)所示。若表达式E只有一个运算符,则表达式就是a○·b\n172计算机算法基础这种形式。这种情况也只需要一个寄存器R1来装a,然后可以使用○·R1,b,R1指令(见图7.8(b))。当出现一个以上运算符时,则有图7.8(c)所示的情况。设l1和l2分别是对根运算符的左运算量(L)和右运算量(R)进行单独计算所需要寄存器的最小数。设l是计算L○·R所需要寄存器的最小数。在作了上述这些假定的情况下就应单独计算L和R的值,因此,l≥max{l1,l2}。如果l1>l2,可以先用l1个寄存器计算L,并将L的值保存在一个指定的寄存器中,然后可用其余的l1-1≥l2个寄存器来计算R。最后,用一条(4)型指令就可计算出L○·R。因此,当l1>l2时,l=l1。类似地,当l1 N时,代码段必须包含某些STORE型的指令,最优代码段将使得(1)和(2)型指令数之和最小。定理7.6的证明提供了一个代码段生成算法(算法7.10)。后面将证明CODE2在规定的这些假设条件下生成最优代码段。现在先来理解这个算法。图7.9结点的MR值(即结点上方的数)算法假定表达式树T中的每个结点有4个信息段:LCHILD,RCHILD,DATA和MR。MR的值已像定理7.6定义的那样被算出。CODE2用了两个与CODE1中相同的子程序TEMP和RETEMP。为了生成表达式树T的代码段,CODE2以callCODE2(T,1)的形式\n第7章基本检索与周游方法173被调用。寄存器总数N是一个全程变量。假定T≠0,即表达式不是空的。对于CODE2(T,i)的调用,只使用寄存器Ri,⋯,RN来生成表达式T的代码段。结果保留在Ri中。在对CODE2初次调用的情况下,若T是一个叶子,则只产生一条装入指令。若T是一个内结点,则进入case语句(6~24行)。L和R分别指向T的左、右儿子。设T的DATA信息段中的运算符为○·,若R是一个叶子,则MR(R)=0。因此,L○·R的最优代码段是L的最优代码段加上这条○·运算指令。这在7~9行生成。而对L是一个叶子的情况,只有在第8行和第18行递归调用CODE2时,才可能出现。当在这两处L是叶子时,只要在此处再产生一条装入指令。在初次调用CODE2时,如果MR(T)>N,则在此表达式树中必定至少有一个内结点,它的左、右儿子所需的最小寄存器数MR(L)≥N且MR(R)≥N。由定理7.6可知,在这种情况下至少要产生一条存放指令。L○·R的最优代码段是R的最优代码段后跟随一条存放R的结果的指令再加上L的最优代码段,这在第10~15行生成。要指出的是,在MR(L)≥N且MR(R)≥N的情况下,第10行和第13行的两次递归调用都允许CODE2使用寄存器Ri,⋯,RN。对递归深度施以简单的归纳可知,总有i=1。不管对CODE2是初次调用还是递归调用,如果其实在参数T的MR(L)和MR(R)至少有一个小于N且MR(R)≠0,则在第16~23行处理。若MR(L) DFN(u)≥L(u)。在这两种情况下L(u)都能得到正确的修正。对于ART的初次调用是callART(1,0)。在调用ART之前DFN初始化为0。算法7.11计算DFN和L的算法lineprocedureART(u,v)∥u是深度优先检索的开始结点。在深度优先生成树中,u若有父亲,那么v就是它的父亲。假设数组DFN是全程量,并将其初始化为0。num是全程变量,被初始化为1。n是G的结点数∥globalDFN(n),L(n),num,n1DFN(u)←num;L(u)←num;num←num+12for每个邻接于u的结点wdo3ifDFN(w)=0thencallART(w,u)∥还没访问w∥4L(u)←min(L(u),L(w))5elseifw≠vthenL(u)←min(L(u),DFN(w))6endif7endif8repeat9endART如果连通图G有n个结点e条边,且G由邻接表表示,那么ART的计算时间为O(n+e)。因此,L(1∶n)可在时间O(n+e)内算出。一旦算出L(1∶n),G的关节点就能在O(n)时间内识别出来。因此,识别关节点的总时间不超过O(n+e)。如何判断G的双连通分图呢?要是在第3行调用ART之后有L(w)≥L(u),就可断定u或者是根,或者是关节点。不管u是否为根,也不管u有一个或是多个儿子,将边(u,w)和对ART的这次调用期间遇到的所有树边和逆边加在一起(除了包含在子树w中其它双连通分图的边以外),构成一个双连通分图(它的形式证明将在定理7.10的证明中给出)。因此,为了得到双连通分图,对ART需作以下修改。(1)引进一个用来存放边的全程栈S。(2)在2到3行间增加一行:2.1ifv≠wandDFN(w)DFN(u)时,(u,w)早已存在栈中。(3)在3到4行间增加下列行:3.1ifL(w)≥DFN(u)thenprint(′newbiconnectedcomponent′)3.2loop3.3从栈S的顶部删去一条边3.4设这条边是(x,y)3.5print(′(′,x,′,′,y,′)′)3.6until((x,y)=(u,w)or(x,y)=(w,u))repeat3.7endif可以证明算法ART在增加了这些内容之后,其计算时间仍然是O(n+e)。下面来证明这算法的正确性。定理7.10当连通图G至少有两个结点时,增加了2.1和3.1~3.7行的算法,ART能正确地生成G的双连通分图。证明当G只有一个结点时,它没有边,因此这过程不产生输出。G的双连通分图就是这单个结点。对此情况可作单独处理。当G的结点数n≥2时,算法能正确运行,这可通过施归纳于G的双连通分图数来证明。如果G只有一个双连通分图,即G是双连通图,显然,它的深度优先生成树的根u只有一个儿子w,而且w是3.1行中使得L(w)≥DFN(u)的唯一结点。到w被检测完时,G中所有的边已作为一个双连通分图输出。现假定该算法对至多有m个双连通分图的所有连通图G都能正确执行。下面证明对于有m+1个双连通分图的所有连通图这个算法也能正确执行。考虑3.1行中第一次出现L(w)≥DFN(u)的情况。此时还没有任何边被输出,因此G中与w子孙相关联的所有边都在栈中,且在边(u,w)的上面。由于u的子孙都不是关节点而u是一个关节点,因此S栈中(u,w)上面的边集和边(u,w)一起构成一个双连通分图。一旦将这些边从栈S中删除并输出,此算法基本上相当于在一个从G中删去这个双连通分图后所剩下的图G′上运行。算法在G和G′上运行的差别仅在于以下一点,即,在完成对结点u的检测期间,可能要考虑刚输出的分图中的那些边(u,r)。然而,对于这些边都有DFN(r)≠0和DFN(r)>DFN(u)≥L(u),因此,这些边只会使第2~8行的循环作些无意义的迭代而并不会影响这个算法。容易证明G′至少有两个结点。又由于G′正好有m个双连通分图,因此,由归纳假设可以得出这m个双连通分图能正确地生成。证毕。要特别指出的是,上面描述的算法要在生成树满足以下条件的环境中工作,相对于这棵生成树,所给定的图没有交叉边。而相对于宽度优先生成树,一些图可能有交叉边,因此算法ART对BFS不适用。7.4与/或图很多复杂问题很难或没法直接求解,但可以分解成一系列(类型不同)的子问题,而这些子问题又可反复细分成一些更小的子问题,一直到分成一些可普通求解的、相当基本的问题\n第7章基本检索与周游方法181为止。然后,由这些分解成的子问题的全部或部分解再导出原问题的解。这种将一个问题分解成若干个子问题,又由子问题的解导出原问题解的方法称为问题化简。问题化简已在工业调度分析、定理证明等方面得到应用。把复杂问题分解成一系列子问题的过程可以用如下结构的有向图来表示:在有向图中,结点代表问题,一个结点的子孙代表与其相联系的子问题。为了暗示父结点的解可由哪些子问题联合导出,则用一条弧将那些能联合导出其解的子结点连接在一起。例如,图7.16(a)表示问题A可以通过求解子问题B和C来解出,或者可由单个求解子问题D或E来解出。边〈A,B〉和〈A,C〉则用一条弧连在一起。为了使图中的每个结点含义单一化,即它的解或者需要求解它所有的子孙得到,或者求解它的一个子孙就可得到,通过引进像图7.16(b)那样的虚结点可达到此目的。这前一类结点称为与结点,后一类结点称为或结点。由与结点出发的所有边用一条穿过它们的弧相连结。图7.16(b)的A和A″是或结点,A′是与结点。没有子孙的结点是终结点,它代表基本问题并标记上可解或不可解。可解的终结点用方框表示。图7.16表示问题的图下面来看一个例子,某人一星期洗一次衣服,所要做的事有:收集脏衣服、洗衣服、把衣服弄干、熨平、叠好并归堆。其中,某些事可采用不同的方法,如洗衣服一项可以是手洗也可以是机器洗。对于这个问题可以构造出图7.17所示的那样的一棵与/或树。图中,手洗的那个结点没有子孙也不是方形结点,它表示此人不采用手洗的方法。当然,洗衣服问题是个非常简单的问题,而实际应用中很多问题远非如此简单,因此使用问题化简就有了现实的意义。图7.17洗衣服问题对应的与/或图图7.18两个不是树的与/或图在对问题化简时,如果两个子问题在分解成的更小子问题中有一个公共的更小子问题,而这个更小的子问题只需求解一次,则在该问题的与/或图上可用一个结点来表示这个更小的公共子问题。图7.18显示了两个出现这种情况的与/或图。这样的与/或图就不再是树了,而且可能出现像图7.18(b)所示的有向环。要指出的是,有向环的出现并不意味着该问\n182计算机算法基础题不可解。事实上,图7.18(b)所示的问题A可通过求解基本问题G,H和I来导出其解。即通过求解G,H和I导出D和E的解,因此也就能导出B和C的解。下面再引进一个概念:解图是由与/或图中一些可解结点组成的子图,它表示对问题求解。图7.18所示的两个图的解图由粗线条示出。下面,只考虑问题的分解过程可以用与/或树来表示的情况,在这种情况下,如何根据问题的与/或树来判断该问题是否可解呢?这只需对这棵与/或树作后根次序周游就可得出答案。算法7.12对这一判断过程作了具体的描述。在算法执行过程中,一旦发现某与结点的一个儿子结点不可解(第6行),或者发现某或结点的一个儿子结点可解(第11行),就立即终止该算法,这可减少算法的工作量且对结果无任何影响。算法7.12判断与/或树是否可解算法lineprocedureSOLVE(T)∥T是一棵其根为T的与/或树,T≠0。如果问题可解则返回1,否则返回0∥1case2:T是终结点:ifT可解thenreturn(1)3elsereturn(0)4endif5:T是与结点:forT的每个儿子Sdo6ifSOLVE(S)=0thenreturn(0)7endif8repeat9return(1)10:else:forT的每个儿子Sdo∥或结点∥11ifSOLVE(S)=1thenreturn(1)endif12repeat13return(0)14endcase15endSOLVE对于一个给定的复杂问题,不仅需要知道此问题是否可解,而且希望知道如果问题可解,那么此问题的解是由哪些基本问题、沿着什么样的途径所导出的,即希望求出问题的解树。由于解树是与/或树的全体或一部分,因此求解树的算法可在生成与/或树算法的基础上加上一些对结点可解性的判断和删除措施而获得。因为与/或树结点的生成取决于问题的分解方法,假定问题的分解方法可用函数F来表示,所以对于一个已经生成的结点,可用函数F去生成它的所有儿子。而生成结点的次序既可按宽度优先也可按深度优先的次序来生成。因此,解树的结点也需用函数F并可按宽度优先或深度优先的次序来生成。不过要指出的是,一棵与/或树可能有无穷的深度,在使用解树的深度优先生成算法的情况下,即使已知解树存在,算法也可能导致所有生成的结点都在一条由根出发的无穷深度的路径上,从而根本就不能确定出一棵解树,这一点可通过对生成深度作出某种限制获得解决。譬如生成的深度只准达到某个d,凡在深度d处的非终止结点都标记为不可解。这样只要有一处的深度不大于d,就可保证生成一棵解树。宽度生成算法没有这样的缺点。因为每个结点都只有有限个儿子,所以与解树相对应的与/或树中任何一级都不可能有无穷多个儿子。于是,\n第7章基本检索与周游方法183只要存在解树,宽度优先生成算法就一定可以将其找出,而且找出的还是一棵具有最小深度的解树。不过,如果与/或树中由根出发的所有路径都有无穷的深度,宽度优先生成算法也会出现不终止的情况。这可通过限制所希望得到的解树的深度获得解决。过程BFGEN是一个解树的宽度优先生成算法。如果解树存在,则算法生成与/或树的一棵宽度优先解树,而与/或树则是在结点T开始,应用儿子生成函数所得到的。BFGEN使用了一个与SOLVE类似的子算法ASOLVE(T)。该子算法对部分生成的与/或树T作一次后根次序周游,并且将结点标上可解、不可解或可能可解的标记。由于T不是一棵完整的与/或树,因此它有3类叶子结点:第一类结点是非终止叶子结点。由于非终止叶子结点还没检测,因此对其可解性暂时没法判定,故将其标记为可能可解。其它两类叶子结点是完整与/或树的叶子,故根据叶子结点所代表问题的可解性标上可解或不可解。如果一个非叶子结点是与结点,则只要它有一个儿子不可解它就不可解。而对于一个非叶子结点的或结点,若它至少有一个儿子可解,则该结点就是可解的。所求得的任何不可解的结点都可从T中删去(第7行)。对于任何不可解结点P的子孙,也没有必要检测,因为,即使某些子孙可解,P也不能解出,所以第9行从队列中删去P的所有还没检测的子孙。如果已求出某结点可解,则没有必要进一步去检测那些还没检测的子孙,这一工作也在第9行完成。容易证明,如果存在一棵对应于(T,F)的解树,那么BFGEN就一定会找到这棵树。注意:如果找到了这样的一棵树,那么T就指向它的根并且这棵树可能有某些为了求解整个问题并不需要求出的可能结点,对T另外作一次扫描可以消去这些多余的结点。算法7.13宽度优先生成解树lineprocedureBFGEN(T,F)∥F生成T中的儿子结点;T是根结点。终止时,如果存在解树,则T是这解树的根∥1将队列Q初始化为空;V←T2loop3用F生成V的那些儿子∥检测V∥4ifV没有儿子then标记V为不可解else①将V的所有不是叶子结点的儿子放入队列Q,将那些叶子结点分别标上可解或不可解②把V的所有儿子加入树T5endif6callASOLVE(T)7从树T删去所有标记为不可解的结点8if根结点T标记为可解thenreturn(T)endif9从队列Q中删去以下的所有结点:它们在T中曾有一个祖先被标记为不可解或者在T中有一个标记为可解的祖先10ifQ为空thenprint(′nosolution′);stopendif11删去队列Q的第一个元素;设此结点是V12repeat13endBFGEN\n184计算机算法基础7.5对策树本节讨论树在博弈游戏中的应用。在一盘棋赛中,对弈各方都要根据当前的局势,分析和预见以后可能出现的局面,决定自己要采取的各种对策,以争取获得最好的结果。博弈是一种竞争,而竞争现象广泛存在于社会活动的许多方面,因此本节的内容可以很自然地引申并应用于含有竞争现象的政治、经济、军事、外交等各个领域。首先,来看一个非常简单的拾火柴棍游戏。假定盘上放有n支火柴,由弈者A和B两个人参加比赛。比赛规则是:两名弈者轮流从盘中取走火柴,每次从盘中取走1,2或3支火柴均为合法着。否则,为非法着;拿走盘中最后一支火柴的弈者则负了这一局,当然另一名弈者则胜这一局。任何时刻盘中剩下的火柴数都表示此时刻的棋局。拾火柴棍游戏在任一时刻的状态则由此时的棋局和轮到走下一着的弈者一起所决定。终局是表示胜局、负局或和局情况的棋局。其它棋局都是非终止棋局。在拾火柴棍游戏中只有一种终局形式,即盘中没火柴棍了。因此不是A胜就是B胜,不可能出现和局。在以下条件成立时,棋局序列C1,C2,⋯,Cm称为有效(棋局)序列。(1)C1是开始棋局。(2)Ci(0B,否则它就不能影响V′(X)。故B是GC(X)值应有的下界。把这个下界加入到算法VEB就得到算法AB。所添加的参数LB是X值应有的下界。算法7.16纵深α-β截断算法procedureAB(X,l,LB,D)∥LB是V′(X)的一个下界。其余注释与VEB同∥ifX是终结点orl=0thenreturn(e(X))endifans←LB∥V′(X)的当前下界∥fori←1toddoifans≥Dthenreturn(ans)endifans←max(ans,-AB(Ci,l-1,-D,-ans))repeatreturn(ans)endAB不难证明初次调用AB(Y,l,-∞,∞)与调用VB(Y,l)得到的结果是相同的。图7.21(b)显示了一棵假想的对策树,在这棵树中,使用算法AB比使用算法VEB产生更大的截断。先在图7.21说明下界的对策树图7.21(b)所示的这棵树上执行VEB。假定最初的调用为VEB(P1,l,∞),其中l是这棵树的深度。在检查了P1的左子树之后,P1的B值被置成10,并且生成P3,P4,P5和P6这些结点。此后,V′(P6)确定为9,进而P5的B值变成-9。使用这一算法继续算出结点P7的值。然而,在使用AB的情况下,由于P1的B值是10,P4的下界也就是10,因此P4实际的B值变为10。因为结点P7的值无论是什么都无关紧要,所以结点P7不生成,于是V′(P5)≥-9且不可能使V′(P4)达到它的下界。在分析过程AB时,确定一棵树中会生成哪一部分结点是极其困难的。而对于VEB的分析,至今也只对某些类的对策树作出了证明。有兴趣的读者可以参阅D.Knuth于1975年发表在《ArtificialIntelligence》第6期的论文“:Ananalysisofalpha-betacutoffs”。\n190计算机算法基础习题七本章习题中的二元树,除非特别声明,其结点都由3个信息段表示,即有LCHILD,DATA和RCHILD。7.1写一个统计二元树T的叶结点数的算法并分析它的计算时间。7.2使用7.1.1节所讨论的3种周游方法之一,写一个求二元树的镜像树的算法SWAPTREE(T)。图7.22给出了一棵二元树T和它的镜像树的例子。7.3(1)证明一棵二元树可由它的中根顺序和后根顺序所唯一定义。(2)证明一棵二元树可由它的中根顺序和先根顺序所唯一定义。(3)证明一棵二元树不能由它的先根顺序和后根顺序所唯一定义。图7.22二元树T和它的镜像树7.4已知一棵二元树的中根序列为I,后根序列为P,写一个构造该二元树的算法。可直接使用子过程GETNODE去获取一个新结点。其算法的计算复杂度是什么?7.5给出一个例子,使用例中的数据运行你在习题7.4作出的算法。7.6如果二元树T有n个结点,证明定理7.1对算法INORDER1成立。7.7写一个对二元树T作先根次序周游的非递归算法(可以使用栈),并分析其时、空复杂度。7.8写一个对二元树T作后根次序周游的非递归算法(可以使用栈),并分析其时、空复杂度。7.9如果n结点二元树T的每个结点有4个信息段:LCHILD,DATA,PARENT,RCHILD。要求以下写的算法所用附加空间都不超过O(1),时间都不超过O(n)。并证明其确实达到了这些要求。(1)写对T的中根次序周游算法。(2)写对T的先根次序周游算法。(3)写对T的后根次序周游算法。7.10写一个时间为Θ(n),附加空间为Θ(1)的二元树中根次序周游算法。树中的每个结点除了信息段:LCHILD,DATA和RCHILD外还有一个位信息段TAG。(提示:使用INORDER2链倒挂的思想,但不用LR的方法。用TAG位区别向左还是向右子树移动)7.11证明按树先根次序周游一棵树与按先根次序周游此树对应的二元树所给出的结果相同(即按相同的次序访问这些结点)。7.12证明按树中根次序周游一棵树与按中根次序周游此树对应的二元树所给出的结果相同(即按相同的次序访问这些结点)。7.13证明如果按树后根次序周游一棵树,那么访问这树中结点的次序与按后根次序周游对应二元树的访问这些结点的次序可能不同。7.14设树T的度为k,而且结点P有k个儿子信息段CHILD(P,i),1≤i≤k。写出下列算法并分析它们的时、空复杂度。(1)写一个树中根次序周游的非递归算法TI(T,k)。(2)写一个树先根次序周游的非递归算法TPRE(T,k)。(3)写一个树后根次序周游的非递归算法TPOST(T,k)。7.15证明对于任一无向图G=(V,E),v∈V。对BFS(v)的一次调用就会访问含结点v的连通分图的全部结点。\n第7章基本检索与周游方法1917.16重写BFS和BFT,使它能打印出无向图G的所有连通分图。假定G是按邻接表方式输入的,每个结点i的邻接表有头结点HEAD(i)。7.17利用BFS的思想写一个找包含已知结点v的最短(有向)环算法。证明你的算法能找出最短环,分析算法的时、空复杂度。7.18证明DFS访问G中由v可到达的所有结点。7.19证明定理7.3中给出的时、空限界对DFS也成立。7.20对图的另一种检索方法是D-search。此方法与BFS的不同之处在于,下一个要检测的结点是最新加到未检测结点表的那个结点。因此这个表应作成一个栈而不是一个队。(1)写一个D-search算法。(2)证明由结点v开始的D-search访问v可到达的所有结点。(3)你的算法的时、空复杂度是什么?(4)修改你的算法,使它能对无向连通图产生一棵生成树。7.21判断n结点的无向图G是否有环?若有,就尽可能多地写出对它的算法,分析这些算法的时、空复杂度;从而对这些算法的有效性作出评价。7.22写一个计算以二元树T表示的算术表达式的算法。假定表达式只使用双目运算符+、-、*、/。此二元树中的每个结点有3个信息段:LCHILD,DATA和RCHILD。如果P是叶结点,则DATA(P)是P所代表的变量或常数在存储器中的地址。VAL(DATA(P))是此变量或常数的当前值。你的算法需要多少计算时间?7.23证明定理7.5。7.24在表达式包含某些可交换运算符的情况下完善表7.6。7.25修改算法CODE1,使其在表达式树T含有可交换运算符的情况下也能生成最优代码。证明你的算法确能达到这一要求。7.26在T含有可结合运算符情况下,做与题7.25相同的工作。7.27获取下述表达式的表达式树,并标出每个结点的MR值。然后在N=1和N=2的情况下,由CODE2生成它们的最优代码。假定所有的运算符都是不可交换和不可结合的。(1)(a+b)*(c+d*(e+f)/(g+h))(2)a*b*c/(e-f+g*(h-k)*(l+m))(3)a*(b-c)*(d+f)/(g*(h+j)-k*l)7.28参看定理7.6对MR(P)的定义,写一个对二元表达式树T的每个结点计算其MR(P)值的算法。假设每个结点P有4个信息段LCHILD、DATA、MR和RCHILD。7.29证明定理7.7。7.30证明定理7.8。7.31证明CODE2的时间复杂度是Θ(n),其中n是T的结点数。7.32如果MR(T)≤n,在不允许使用装入指令的情况下,证明CODE2用最少的寄存器生成代码。7.33证明引理7.1。7.34写一个算法FLIP(T),用它来交换表达式树T中那些表示可交换运算符结点的左、右子树,使生成的树对每个给定的寄存器数N,大、小结点数之和最小。FLIP的计算复杂度是多少?7.35将CODE2扩展到具有可结合运算符表达式的计算。7.36识别图7.23的关节点并画出它们的双连通分图。7.37如果G是一个无向连通图,证明G中任何一条边都不可能在两个不同的双连通分图中。7.38假设无向连通图G的双连通分图是Gi=(Vi,Ei),1≤i≤k。证明:\n192计算机算法基础图7.23两个连通图(1)如果i≠j,那么Vi∩Vj至多包含一个结点。(2)结点v是G的关节点,当且仅当对于某i≠j,{v}=Vi∩Vj。7.39设G是一个无向连通图,写一个算法以求出将G变成双连通图所需要增加的最小边数并要求输出这些边。分析你的算法需要的时间和空间。7.40证明如果T是无向连通图G的宽度优先生成树,那么,相对于T,G可能有交叉边。7.41假设u不是根,证明u是一个关节点,当且仅当对于u的某个儿子w,L(w)≥DFN(u)。7.42证明在算法ART加了2.1和3.1~3.7行后,如果v=w或者DFN(w)>DFN(u),那么边(u,w)或者已在栈S的顶部,或者已作为双连通图的一部分输出。7.43修改算法SOLVE,使它能识别T的一棵解子树。7.44写出算法BFGEN中使用的子算法ASOLVE。7.45写一个算法PRUNE,用它去消除由算法BFGEN生成的解树T中所有不需要求解的结点,即,使输出的树是一棵为了求解整个问题必须求解它的每个结点的解子树。7.46考虑下面这棵假想对策树(见图7.24):图7.24假想对策树(1)使用最大最小方法式(7.1)去获取根结点的值。(2)弈者A应采用什么棋着?(3)列出用算法VE计算这棵对策树结点的值时结点的计算顺序。(4)对树中的每个结点X,用式(7.2)计算V′(X)。(5)在取X=根,l=∞,LB=-∞,D=∞的情况下,用算法AB计算此树的根的值期间,这树的哪些结点没有计算?7.47证明对于那些由A走子的级上的每个结点用式(7.2)计算的V′(X)与用式(7.1)计算的V(X)有相同的值,而对于其它级上的结点,用式(7.1)计算的V(X)为用式(7.2)计算的V′(X)取负。7.48证明初次调用AB(X,l,-∞,∞)与初次调用VB(Y,l)所得的结果相同。\n第8章回溯法8.1一般方法在算法设计的基本方法中,回溯法是最一般的方法之一。在那些涉及寻找一组解的问题或者求满足某些约束条件的最优解的问题中,有许多可以用回溯法来求解。8.1.1回溯的一般方法为了应用回溯法,所要求的解必须能表示成一个n-元组(x1,⋯,xn),其中xi是取自某个有穷集Si。通常,所求解的问题需要求取一个使某一规范函数P(x1,⋯,xn)取极大值(或取极小值或满足该规范函数条件)的向量。有时还要找出满足规范函数P的所有向量。例如,将A(1∶n)中的整数分类就是可用一个n-元组表示其解的问题,其中xi是A中第i小元素的下标。规范函数P是不等式A(xi)≤A(xi+1),其中1≤i 0doif还剩有没检验过的X(k)使得X(k)∈T(X(1),⋯,X(k-1))andB(X(1),⋯,X(k))=truethenif(X(1),⋯,X(k))是一条已抵达一答案结点的路径thenprint(X(1),⋯,X(k))endifk←k+1∥考虑下一个集合∥elsek←k-1∥回溯到先前的集合∥endifrepeatendBACKTRACK需要注意的是,集合T()将提供作为解向量的第一个分量X(1)的所有可能值,解向量则取使限界函数B1(X(1))为真的那些X(1)的值。还要注意这些元素是如何按深度优先方式生成的。随着k值的增加,解向量的各分量不断生成,直到找到一个解或者不再剩有没经检验的X(k)为止。当k值减少时,算法必须重新开始生成那些可能在以前剩下而没经检验的元素。因此,还需拟定一个子算法,使它按某种次序来生成这些元素X(k)。如果只想要一个解,则可在print后面设置一条return语句。算法8.2提供了回溯算法的一种递归表示。由于它基本上是一棵树的后根次序周游,因此按照这种方法描述回溯法是自然的。这个递归模型最初由callRBACKTRACK(1)所调用。算法8.2递归回溯算法procedureRBACKTRACK(k)∥此算法是对回溯法抽象地递归描述。进入算法时,解向量X(1∶n)的前k-1个分量X(1),⋯,X(k-1)已赋值∥globaln,X(1∶n)for满足下式的每个X(k)X(k)∈T(X(1),⋯,X(k-1))andB(X(1),⋯,X(k))=truedoif(X(1),⋯,X(k))是一条已抵达一答案结点的路径thenprint(X(1),⋯,X(k))endifcallRBACKTRACK(k+1)repeatendRBACKTRACK将解向量(x1,⋯,xn)作为一个全程数组X(1∶n)来处理。这个元组第k个位置上满足B的所有元素逐个被生成,并被连接到当前的向量(X(1),⋯,X(k-1)),每次X(k)都要附之以这样的一种检查,即判断一个解是否已被找到。因此,这个算法被递归调用。当退出for循环时,不再剩有X(k)的值,从而结束此层递归并继续上次没解决的调用。要指出的是,当k大于n时,T(X(1),⋯,X(k-1))返回一个空集,因此根本不进入for\n200计算机算法基础循环。还要指出的是,这个程序印出所有的解,而且组成解的元组的大小是可变的。如果只想要一个解,则可加上一个标志作为一个参数,以指明首次成功的情况。8.1.2效率估计上面所给出的两个回溯程序的效率主要取决于以下4种因素:①生成下一个X(k)的时间;②满足显式约束条件的X(k)的数目;③限界函数Bi的计算时间;④对于所有的i,满足Bi的X(k)的数目。如果这些限界函数大量地减少了所生成的结点数,则认为它们是好的。不过,一些好的限界函数往往需要较多的计算时间,而所希望的不只是减少所生成的结点而是要减少总的计算时间。因此,在选择限界函数时,通常在好坏与时间消费上采取折中的方案。有许多问题,其状态空间树的规模太大,要想生成其全部结点实际上是不允许的,因此应该使用限界函数并且希望在一段适当的时间内至少会找出一个解。不过对于许多问题(例如,n-皇后问题)至今还没听说有完善的限界方法。对于许多问题,可以按任意次序使用包含各个解分量xi可能取值的那些有穷集Si。为了提高有效检索的效率,一般可采用一种称之为重新排列的方法。其基本思想是,在其它因素相同的情况下,从具有最少元素的集合中作下一次选择。这种策略虽已证明对n-皇后问题无效,而且还可构造出一些证明用此方法无效的例子,但从信息论的观点看,从最小集合中作下一次选择,平均来说更为有效。在图8.7中,对同一个问题用两棵回溯检索树显示了这一方法的潜在能力。如果能去掉图8.7(a)中那棵树的第一级的一个结点,那么实际上就从所考虑的情况中去掉了12个元组。如果从图8.7(b)中那棵树第一级上去掉一个结点,则只删去8个元组。更完善的重新排列策略将在后面和动态状态空间树一起研究。图8.7重新排列如上所述,有4种因素决定回溯算法所需要的时间。一旦选定了一种状态空间树结构,这4种因素的前3种因素相对来说就与所要解决问题的实例无关,只有第4种因素,即所生成的结点数因问题的实例不同而异。对于某一实例,回溯算法可能只生成O(n)个结点,而对于另一不同的实例,由于它与原实例密切相关,故也可能生成这棵状态空间树的几乎全部n结点。如果解空间的结点数是2或n!,则回溯算法的最坏情况时间一般将分别是nO(p(n)2)或O(q(n)n!)。p(n)和q(n)都是n的多项式。尽管回溯法对同一问题的不同实例在计算时间上可能出现极大差异,但当n很大时,对于某些实例而言,回溯算法确实可在很短时间内求出其解,因此回溯法仍不失为一种有效的算法设计策略,只是在决定采用回溯算法正式计算某实例之前,应预先估算出回溯算法在此实例情况下的工作效能。\n第8章回溯法201用回溯算法去处理一棵树所要生成的结点数,可以用蒙特卡罗(MonteCarlo)方法估算出来。这种估计方法的一般思想是,在状态空间树中生成一条随机路径。设X是这条随机路径上的一个结点,而且X在状态空间树的第i级。在结点X处用限界函数确定没受限界的儿子结点的数目mi,在这mi个没受限儿子结点中随机地选择一个结点作为这条路径上的下一个结点。这条路径的生成在以下结点处结束,或者它是一个叶子结点,或者该结点的所有儿子结点都已被限界。用这些mi可以估算出这棵状态空间树中不受限界结点的总数m。此数在准备检索出所有答案结点时非常有用。在这种情况下,需要生成所有的不受限结点。当只想求一个解时,由于只需生成m个结点的很少一部分,回溯算法就可以得到一个解,因此m不是一个理想的估计值。由mi来估算m,需要假定这些限界函数是固定的,即在算法执行期间当其信息逐渐增加时限界函数不变,而且同一个函数正好用于这棵状态空间树同一级的所有结点。这一假定对于大多数回溯算法并不适用。在大多数情况下,随着检索的进行限界函数会变得更强一些,在这些情况中,对m的估计值将大于考虑了限界函数的变化后所能得到的值。沿用限界函数固定的假定,可以看到第2级没受限的结点数为m1。如果这棵检索树是同一级上结点有相同的度的树,那么就可预计到每一个2级结点平均有m2个没限界的儿子,从而得出在第3级上有m1m2个结点。第4级预计没受限的结点数是m1m2m3。一般,在i+1级上预计的结点数是m1m2⋯mi。于是,在求解给定问题的实例8.1中所要生成的不受限界结点的估计数m=1+m1+m1m2+m1m2m3+⋯。过程ESTIMATE是一个确定m值的算法。它从状态空间树的根出发选择一条随机路径。函数SIZE返回集合Tk的大小。函数CHOOSE从Tk中随机地挑选一个元素。m和r是产生不受限结点估计数所用的临时工作单元。算法8.3估计回溯法的效率procedureESTIMATE∥程序沿着状态空间树中一条随机路径产生这棵树中不受限界结点的估计数∥m←1;r←1;k←1loopTk←{X(k):X(k)∈T(X(1),⋯,X(k-1))andBk(X(1),⋯,X(k))}ifSIZE(Tk)=0thenexitendifr←r*SIZE(Tk)m←m+rX(k)←CHOOSE(Tk)k←k+1repeatreturn(m)endESTIMATE对算法ESTIMATE稍加修改就可得到更准确的结点估计数。这只需增加一个for循环语句,选取数条不同的随机路径(一般可取20条),在求出沿每条路径的估计值后取平均值即得。\n202计算机算法基础8.28-皇后问题8-皇后问题实际上很容易一般化为n-皇后问题,即要求找出在一个n×n棋盘上放置nn个皇后并使其不能互相攻击的所有方案。令(x1,⋯,xn)表示一个解,其中x是把第i个皇后放在第i行的列数。由于没有两个皇后可以放入同一列,因此这所有的xi将是截然不同的。那么,应如何去测试两个皇后是否在同一条斜角线上呢?如果设想棋盘的方格像二维数组A(1∶n,1∶n)的下标那样标记,那么可以看到,对于在同一条斜角线上的由左上方到右下方的每一个元素有相同的“行-列”值,同样,在同一条斜角线上的由右上方到左下方的每一个元素则有相同的“行+列”值。假设有两个皇后被放置在(i,j)和(k,l)位置上,那么根据以上所述,仅当i-j=k-l或i+j=k+l时,它们才在同一条斜角线上。将这两个等式分别变换成j-l=i-k与j-l=k-i因此,当且仅当|j-1|=|i-k|时,两个皇后在同一条斜角线上。过程PLACE(k)返回一个布尔值,当第k个皇后能放置于X(k)的当前值处时,这个返回值为真。这个过程测试两种情况,即X(k)是否不同于前面X(1),⋯,X(k-1)的值以及在同一条斜角线上是否根本就没有别的皇后。该过程的计算时间是O(k-1)。算法8.4可以放置一个新皇后吗procedurePLACE(k)∥如果一个皇后能放在第k行和X(k)列,则返回true;否则返回false。X是一个全程数组,进入此过程时已置了k个值。ABS(r)过程返回r的绝对值∥globalX(1∶k);integeri,ki←1whilei 0do∥对所有的行执行以下语句∥X(k)←X(k)+1∥移到下一列∥whileX(k)≤nandnotPLACE(k)do∥此处能放这个皇后吗∥X(k)←X(k)+1repeatifX(k)≤n∥找到一个位置∥thenifk=n∥是一个完整的解吗∥thenprint(X)∥是,打印这个数组∥elsek←k+1;X(k)←0∥转向下一行∥endifelsek←k-1∥回溯∥endifrepeatendNQUEENS此时,读者可能对于过程NQUEENS怎么会优于硬性处理感奇怪。原因是这样的,如64果硬性要求一个8×8的棋盘安排出8块位置,就有种可能的方式,即要检查将近4.489×10个8-元组。然而,过程NQUEENS只允许把皇后放置在不同的行和列上,因此至多需要作8!次检查,即至多只检查40320个8-元组。可以用过程ESTIMATE来估算NQUEENS所生成的结点数。要指出的是,过程ESTIMATE所需要的假定也适用于NQUEENS,即,使用固定的限界函数且在检索进行时函数不改变。另外,在状态空间树的同一级的所有结点都有相同的度。图8.8显示了由过程ESTIMATE求结点估计数所用的5个8×8棋盘。如同所要求的那样,棋盘上每一个皇后的位置是随机选取的。对于每种选择方案,都记下了可以将一个皇后合法地放在各行中列的数目(即状态树的每一级没受限的结点数)。它们都列在每个棋盘下方的向量中。向量后面的数字表示过程ESTIMATE由这些量值所产生的值。这5次试验的平均值是1625。8-皇后状态空间树的结点总数是7j1+∑(∏(8-i))=69281j=0i=0因此,不受限结点的估计数大约只是8-皇后状态空间树的结点总数的2.34%。1111122222333334444455555666778(8,5,4,3,2)=1649(8,5,3,1,2,1)=769(8,6,4,3,2)=1977(8,6,4,2,1,1,1)=1401(8,5,3,2,2,1,1,1)=2329图8.88-皇后问题的5种方案及不受限结点的估计值\n204计算机算法基础8.3子集和数问题子集和数问题是假定有n个不同的正数(通常称为权),要求找出这些数中所有使得某和数为M的组合。例8.2和例8.4说明了如何用大小固定或变化的元组来表示这个问题。本节将利用大小固定的元组来研究一种回溯解法,在此情况下,解向量的元素X(i)取1或0值,它表示是否包含了权数W(i)。生成图8.4中任一结点的儿子是很容易的。对于i级上的一个结点,其左儿子对应于X(i)=1,右儿子对应于X(i)=0。对于限界函数的一种简单选择是,当且仅当kn∑W(i)X(i)+∑W(i)≥Mi=1i=k+1时,B(X(1),⋯,X(k))=true。显然,如果这个条件不满足,X(1),⋯,X(k)就不能导致一个答案结点。如果假定这些W(i)一开始就是按非降次序排列的,那么这些限界函数可以被强化。在这种情况下,如果k∑W(i)X(i)+W(k+1)>Mi=1则X(1),⋯,X(k)就不能导致一个答案结点。因此,将要使用的限界函数是Bk(X(1),⋯,X(k))=true当且仅当knk∑W(i)X(i)+∑W(i)≥M且∑W(i)X(i)+W(k+1)≤M(8.1)i=1i=k+1i=1由于在即将拟制的算法中不会使用Bn,因此不必担心这个函数中会出现W(n+1)。至此已说明了直接使用8.1节介绍的两种回溯方案中任何一种方案所需要的一切。为简单起见,将算法8.2修改成适应求子集和数的需要便得到递归算法SUMOFSUB。算法8.6子集和数问题的递归回溯算法procedureSUMOFSUB(s,k,r)∥找W(1∶n)中和数为M的所有子集。进入此过程时X(1),⋯,X(k-1)的值已确定。s=k-1nn∑W(j)X(j)且r=∑W(j)。这些W(j)按非降次序排列。假定W(1)≤M,∑W(i)≥M∥j=1j=ki=11globalintegerM,n;globalrealW(1∶n);globalbooleanX(1∶n)2realr,s;integerk,j∥生成左儿子。注意,由于Bk-1=true,因此s+W(k)≤M∥3X(k)←14ifs+W(k)=Mthen∥子集找到∥5print(X(j),j←1tok)∥此处由于W(j)>0,1≤j≤n,因此不存在递归调用∥6else7ifs+W(k)+W(k+1)≤Mthen∥Bk=true∥8callSUMOFSUB(s+W(k),k+1,r-W(k))9endif10endif∥生成右儿子和计算Bk的值∥\n第8章回溯法20511ifs+r-W(k)≥Mands+W(k+1)≤M∥Bk=true∥12thenX(k)←013callSUMOFSUB(s,k+1,r-W(k))14endif15endSUMOFSUBkn过程SUMOFSUB将∑W(i)X(i)和∑W(i)分别保存在变量s和r中以避免每次都要i=1i=k+1n计算这些值。此算法假定W(1)≤M和∑W(i)≥M。初次调用是callSUMOFSUB(0,1,i=1n∑W(i))。着重要指出的是,算法没有明显地使用测试条件k>n去终止递归。之所以不需i=1要这一测试条件,是因为在算法入口处s≠M且s+r≥M,因此r≠0,从而k也不可能大于n。在第7行由于s+W(k) 0种颜色,在只准使用这m种颜色对G的结点着色的情况下,是否能使图中任何相邻的两个结点都具有不同的颜色呢?这个问题称为m-着色判定问题。在m着色最优化问题则是求可对图G着色的最小整数m。这个整数称为图G的色数。对图着色的研究是从m-可着色性问题的著名特例———4色问题开始的。这个问题要求证明平面或球面上的任何地图的所有区域都至多可用4种颜色来着色,并使任何两个有一段公共边界的相邻区域没有相同的颜色。这个问题可转换成对一平面图的4-着色判定问题(平面图是一个能画于平面上而边无任何交叉的图)。将地图的每个区域变成一个结点,若两个区域相邻,则相应的结点用一条边连接起来。图8.10显示了一幅有5个区域的地图以及与该地图对应的平面图。多年来,虽然已证明用5图8.10一幅地图和它的平面图表示种颜色足以对任一幅地图着色,但是一直找不到一定要求多于4种颜色的地图。直到1976年这个问题才由爱普尔(K.I.Apple),黑肯(W.Haken)和考西(J.Koch)利用电子计算机的帮助得以解决。他们证明了4种颜色足以对任何地图着色。在这一节,不是只考虑那些由地图产生出来的图,而是所有的图。讨论在至多使用m种颜色的情况下,可对一给定的图着色的所有不同方法。假定用图的邻接矩阵GRAPH(1∶n,1∶n)来表示一个图G,其中若(i,j)是G的一条边,则GRAPH(i,j)=true,否则GRAPH(i,j)=false。因为要拟制的算法只关心一条边是否存在,所以使用布尔值。颜色用整数1,2,⋯,m表示,解则用n-元组(X(1),⋯,X(n))来给出,其中X(i)是结点i的颜色。使用和算法8.2相同的递归回溯表示,得到算法MCOLORING。此算法使用的基本状态空间树是一棵度数为m,高为n+1的树。在i级上的每一个结点有m个儿子,它们与X(i)的m种可能的赋值相对应,1≤i≤n。在n+1级上的结点都是叶结点。图8.11给出了n=3且m=3时的状态空间树。图8.11当n=3,m=3时MCOLORING的状态空间树\n第8章回溯法207算法8.7找一个图的所有m-着色方案procedureMCOLORING(k)∥这是图着色的一个递归回溯算法。图G用它的布尔邻接矩阵GRAPH(1∶n,1∶n)表示∥∥它计算并打印出符合以下要求的全部解,把整数1,2,⋯,m分配给图中∥∥各个结点且使相邻近的结点的有不同的整数。k是下一个要着色结点的下标∥globalintegerm,n,X(1∶n)booleanGRAPH(1∶n,1∶n)integerkloop∥产生对X(k)所有的合法赋值∥callNEXTVALUE(k)∥将一种合法的颜色分配给X(k)∥ifX(k)=0thenexitendif∥没有可用的颜色了∥ifk=nthenprint(X)∥至多用了m种颜色分配给n个结点∥elsecallMCOLORING(k+1)∥所有m-着色方案均在此反复递归调用中产生∥endifrepeatendMCOLORING在最初调用callMCOLORING(1)之前,应对图的邻接矩阵置初值并对数组X置0值。在确定了X(1)到X(k-1)的颜色之后,过程NEXTVALUE从这m种颜色中挑选一种符合要求的颜色,并把它分配给X(k),若无可用的颜色,则返回X(k)=0。算法8.8生成下一种颜色procedureNEXTVALUE(k)∥进入此过程前X(1),⋯,X(k-1)已分得了区域[1,m]中的整数且相邻近的结点有不同的整数。本过程在区域[0,m]中给X(k)确定一个值:如果还剩下一些颜色,它们与结点k邻接的结点分配的颜色不同,就将其中最高标值的颜色分配给结点k;如果没剩下可用的颜色,则置X(k)为0∥globalintegerm,n,X(1∶n)booleanGRAPH(1∶n,1∶n)integerj,kloopX(k)←(X(k)+1)mod(m+1)∥试验下一个最高标值的颜色∥ifX(k)=0thenreturnendif∥全部颜色用完∥forj←1tondo∥检查此颜色是否与邻近结点的那些颜色不同∥ifGRAPH(k,j)and∥如果(k,j)是一条边∥X(k)=X(j)∥并且邻近的结点有相同的颜色∥thenexitendifrepeatifj=n+1thenreturnendif∥找到一种新颜色∥repeat∥否则试着找另一种颜色∥endNEXTVALUEn-1i算法8.7的计算时间上界可以由状态空间树的内部结点数∑m得到。在每个内部结i=0点处,为了确定它的儿子们所对应的合法着色,由NEXTVALUE所花费的时间是O(mn)。nin+1n因此,总的时间由∑mn=n(m-m)/(m-1)=O(nm)所限界。i=1\n208计算机算法基础图8.12显示了一个包含4个结点的简单图。下面是一棵由过程MCOLORING生成的树。到叶子结点的每一条路径表示一种至多使用3种颜色的着色法。图8.12一个4结点图和所有可能的3着色注意:正好用3种颜色的解只有12种。8.5哈密顿环设G=(V,E)是一个n结点的连通图。一个哈密顿环是一条沿着图G的n条边环行的路径,它访问每个结点一次并且返回到它的开始位置。换言之,如果一个哈密顿环在某个结点v1∈V处开始,且G中结点按照v1,v2,⋯,vn+1的次序被访问,则边(vi,vi+1),1≤i≤n,均在G中,且除了v1和vn+1是同一个结点外,其余的结点均各不相同。在图8.13中,图G1含有一个哈密顿环1,2,8,7,6,5,4,3,1。图G2不包含哈密顿环。似乎没有一种容易的方法能确定一个已知图是否包含哈密顿环。本节考察找一个图中所有哈密图8.13两个图,第一个包含一个哈密顿环顿环的回溯算法。这个图可以是有向图也可以是无向图。只有不同的环才会被输出。用向量(x1,⋯,xn)表示用回溯法求得的解,其中,xi是找到的环中第i个被访问的结点。如果已选定x1,⋯,xk-1,那么下一步要做的工作是如何找出可能作xk的结点集合。若k=1,则X(1)可以是这n个结点中的任一结点,但为了避免将同一个环重复打印n次,可事先指定X(1)=1。若1 nthenfp←cp;fw←cw;k←n;X←Y∥修改解∥8elseY(k)←0∥超出M,物品K不适合∥9endif10whileBOUND(cp、cw,k,M)≤fpdo∥上面置了fp后,BOUND=fp∥11whilek≠0andY(k)≠1do12k←k-1∥找最后放入背包的物品∥13repeat14ifk=0thenreturnendif∥算法在此处结束∥15Y(k)←0;cw←cw-W(k);cp←cp-P(k)∥移掉第k项∥16repeat17k←k+118repeat19endBKNAP1n当fp≠-1时,X(i),1≤i≤n,是这样的一些元素,它们使得∑P(i)X(i)=fp。在4~6i=1行的while循环中,回溯算法作一连串到可行左儿子的移动。Y(i),1≤i≤k,是到当前结点k-1k-1的路径。cw=∑W(i)Y(i)且cp=∑P(i)Y(i)。在第7行,如果k>n,则必有cp>fp,因为i=1i=1若cp≤fp,则在上一次使用限界函数时(第10~16行)就会终止到此叶结点的路径。如果k≤n,则W(k)不适合,从而必须作一次到右儿子的移动。所以在第8行,Y(k)被置成0。如果在第10行中BOUND≤fp,由于现今的这条路径不能导出比迄今所找到的最好解还要好的解,因此该路径可终止。在第11~13行,沿着到最近结点的路径回溯,而由这最近结果可以作迄今尚未试验过的移动。如果不存在这样的结点,则算法在第14行终止。反之,Y(k),cw和cp则对应于一次右儿子移动作出相应的修改。计算这个新结点的界。继续倒转去处理第10~16行,一直到作出有可能得到一个大于fp值的解的右儿子结点为止,否则fp就是背包问题的最优效益值。注意,第10行的限界函数并不是固定的,这是因为当检索这棵树的更多结点时,fp就改变。因此限界函数动态地被强化。\n212计算机算法基础例8.7考虑以下情况的背包问题:P=(11,21,31,33,43,53,55,65),W=(1,11,21,23,33,43,45,55),M=110,n=8。图8.14显示了对向量Y作出各种选择的情况下所生成的树,这棵树的第i级对应于将1或0赋值给Y(i),表示或者含有重量W(i)或者拒绝接纳重量W(i)。一个结点内所载的两个数是重量(cw)和效益(cp),给出了该结点的下一级的两个赋值。注意,若结点不含有重量和效益值则表示此两类值与它们父亲的相同。每个右儿子以及根结点外面的数是对应于那个结点的上界。左儿子的上界与它父亲的相同。算法8.12的变量fp在结点A、B、C和D的每一处被修改。每次修改fp时也修改X。终止时,fp=159和X=(1,1,1,0,1,1,0,0)。9在状态空间树的2-1=511个结点中只生成了其中的35个结点。注意到由于所有的P(i)都是整数而且所有可行解的值也是整数,因此BOUND(p,w,k,M)是一个更好的限界函数,使用此限界函数结点E和F就不必再扩展,从而生成的结点数可减少到28。图8.14算法8.12所生成的树每次在第10行调用BOUND时,在过程BOUND中基本上重复了第4~6行的循环,因此可对算法BKNAP1作进一步的改进。为了取消BKNAP1第4~6行所做的工作,需要把BOUND改变成一个具有边界效应的函数。这两个新算法BOUND1和BKNAP2以算法8.13和8.14的形式写出。这两个算法与算法8.11和8.12中同名的变量含意完全一样。\n第8章回溯法213算法8.13有边界效应的限界函数procedureBOUND1(P,W,k,M,pp,ww,i)∥新近移到的左儿子所对应的效益为pp,重量为ww。i是上一个不适合的物品。如果所有物品都试验过了,则i的值是n+1∥globaln,P(1∶n),W(1∶n),Y(1∶n)integerk,i;realp,w,pp,ww,M,bpp←p;ww←wfori←k+1tondoifww+W(i)≤Mthenww←ww+W(i);pp←pp+P(i);Y(i)←1elsereturn(pp+(M-ww)*P(i)/W(i))endifrepeatreturn(pp)endBOUND1算法8.14改进的背包算法procedureBKNAP2(M,n,W,P,fw,fp,X)∥与BKNAP1同∥integern,k,Y(1∶n),i,j,X(1∶n)realW(1∶n),P(1∶n),M,fw,fp,pp,ww,cw,cpcw←cp←k←0;fp←-1loopwhileBOUND1(cp,cw,k,M,pp,ww,j)≤fpdowhilek≠0andY(k)≠1dok←k-1repeatifk=0thenreturnendifY(k)←0;cw←cw-W(k);cp←cp-P(k)repeatcp←pp;cw←ww;k←j∥等价于BKNAP1中4~6行的循环∥ifk>nthenfp←cp;fw←cw;k←n;X←YelseY(k)←0endifrepeatendBKNAP2到目前为止,所讨论的都是在静态状态空间树环境下工作的回溯算法,现在讨论如何利用动态状态空间树来设计背包问题的回溯算法。下面介绍的一种回溯算法的核心思想是以5.3节贪心算法所得的贪心解为基础来动态地划分解空间,并且力图去得到0/1背包问题的最优解。首先用约束条件0≤xi≤1来代换xi=0或1的整数约束条件,于是得到一个放宽了条件的背包问题:max∑pixi1≤i≤n约束条件∑wixi≤M,0≤xi≤1,1≤i≤n(8.3)1≤i≤n\n214计算机算法基础用贪心方法解式(8.3)这个背包问题,如果所得贪心解的所有xi都等于0或1,显然这个解也是相应0/1背包问题的最优解。如若不然,则必有某xi使得0 POSITION(i)的数目。例如,对于图9.2(a)所示的状态,有LESS(1)=0,LESS(4)=1和LESS(12)=6。在初始状态下,如果空格在图9.2(c)的阴影位置中的某一格处,则令X=1;否则令X=0。于是有定理9.1。16定理9.1当且仅当∑LESS(i)+X是偶数时,图9.2(b)所示的目标状态可由此初始状i=1态到达。证明留作习题。定理9.1用来判定目标状态是否在这个初始状态的状态空间之中。若在,就可着手确定导致目标状态的一系列移动。为了实现这一检索,可以将此状态空间构造成一棵树。在这棵树中,每个结点的儿子表示由状态X通过一次合法的移动可到达的状态。不难看出,移动牌与移动空格实质上是等效的,而且在作实际移动时更为直观,因此以后都将父状态到子状态的一次转换看成是空格的一次合法移动。图9.3(a)中树的根结点表示15-谜问题一个实例的初始状态,该图给出了此实例所构造状态空间树的前三级和第四级的一部分。图中已对这棵树作了一些修剪,即如果结点P的儿子中有和P的父亲状态重复的,则将这一枝剪去。对此实例作FIFO检索表现为依图9.3(a)中结点编号的顺序来生成这些结点。可以看出第四级有一个答案结点,由于是宽度优先检索,因此它还是离根最近的答案结点。图9.3(b)给出了对此实例按深度优先生成其状态空间树结点的一部分,从图中一连串的棋盘格局可以看出,这种检索方法不管开始格局如何(即不管问题的具体实例),总是采取由根开始的那条最左路径,而在这条最左路径上的每一次移动不是离目标更近了而是更远了。这种检索是呆板而盲目的。尽管上面使用的宽度优先检索可以找到离根最近的答案结点,但从处理方式看也是不管开始格局如何总是按千篇一律的顺序移动,因此在这种意义下它也是呆板和盲目的。所希望的是,一种能按不同具体实例作不同处理的有一定“智能”的检索方法。这种检索方法需给状态空间树的每个结点X赋予一定的成本c(X)。如果具体实例有解,则将由根出发到最近目标结点路径上的每个结点赋以这条路径的长度作为它们的成本。于是,在图9.3(a)所示实例中,c(1)=c(4)=c(10)=c(23)=3,其余结点均赋以∞的成本。如果能做到这一点,在使用宽度优先检索的情况下必然会实现非常有效的检索。把根作为E-结点开始,在生成它的儿子结点时,可以将成本为∞的结点统统杀掉,只有与根具有相同成本值的儿子结点4成为活结点,而且它立即成为下一个E-结点。按这种检索策略继续处理很快就可到达目标结点23。但这是一种很不实际的策略,因为要想简单地给出能得到像上面那样成本值的函数c(·)是不可能的。∧∧切合实际的做法是给出一个便于计算成本估计值的函数c(X)=f(X)+g(X),其中f(X)∧是由根到结点X路径的长度,g(X)是以X为根的子树中由X到目标状态的一条最短路径∧长度的估计值。为此,这个g(X)至少应是能把状态X转换成目标状态所需的最小移动数。对它的一种可能的选择是∧g(X)=不在其目标位置的非空白牌数目\n第9章分枝-限界法221图9.315-谜问题的实例及深度优先检索(a)15-谜问题的一部分状态空间树;(b)一种深度优先检索的前十步∧这样定义的g(X)是符合以上要求的。不难看出,为达到目标状态所需要的移动数可能大于∧∧∧g(X)。例如对图9.4的问题状态,由于只有7号牌不在其目标位置上,因此g(X)=1(g(X)∧的计数排除了空白牌)。然而,为了达到目标状态所需要的移动数比g(X)多得多。由此可∧以看出,c(X)是c(X)的下界。∧使用c(X)图9.3(a)的LC-检索将结点1作为E-结点的开始。结点1在生成它的所有儿子结点2,3,4和5之后死去。变成E-结点的下一个结点是具有最1234∧∧∧∧∧小c(X)的活结点,c(2)=1+4,c(3)=1+4,c(4)=1+2和c(5)=1568+4,结点4成为E-结点。生成它的儿子结点,此时的活结点是2,3,9101112∧∧∧5,10,11和12。c(10)=2+1,c(11)=2+3,c(12)=2+3,具有最小1314157∧c值的活结点10成为下一个E-结点。接着生成结点22和23,结点图9.4问题状态23被判定是目标结点,此次检索结束。在这种情况下,LC-检索几乎\n222计算机算法基础和使用精确函数c(·)一样有效。由此可以看出,通过对c(·)的适当选择,LC-检索的选择性将远比已讨论过的其它检索方法强得多。9.1.3LC-检索的抽象化控制设T是一棵状态空间树,c(·)是T中结点的成本函数。如果X是T中的一个结点,则c(X)是其根为X的子树中任一答案结点的最小成本。从而,c(T)是T中最小成本答案结点的成本。如前所述,要找到一个如上定义且易于计算的函数c(·)通常是不可能的,为此使∧用一个对c(·)估值的启发性函数c(·)来代替。这个启发函数应是易于计算的并且一般有∧如下性质,如果X是一个答案结点或者是一个叶结点,则c(X)=c(X)。过程LC(算法9.1)∧用c去找寻一个答案结点。这个算法用了两个子算法LEAST(X)和ADD(X),它们分别将∧一个活结点从活结点表中删去或加入。LEAST(X)找一个具有最小的c值的活结点,从活结点表中删除这个结点,并将此结点放在变量X中返回。ADD(X)将新的活结点X加到活结点表。通常把这个活结点表作成一个min-堆来使用。过程LC输出找到的答案结点到根结点T的那条路径。如果使用PARENT信息段将活结点X与它的父亲相链接,这条路径就很容易输出了。算法9.1LC-检索∧lineprocedureLC(T,c)∥为找一个答案结点检索T∥0ifT是答案结点then输出T;returnendif1E←T∥E-结点∥2将活结点表初始化为空3loop4forE的每个儿子Xdo5ifX是答案结点then输出从X到T的那条路径6return7endif8callADD(X)∥X是新的活结点∥9PARENT(X)←E∥指示到根的路径∥10repeat11if不再有活结点thenprint(′noanswernode′)12stop13endif14callLEAST(E)15repeat16endLC下面证明算法LC的正确性。变量E总是指着当前的E-结点。由LC-检索的定义,根结点是第一个E-结点(第1行)。第2行将活结点表置初值。在执行LC的任何时刻,这个表含有除了E-结点以外的所有活结点,因此这个表最初为空(第2行)。第4~10行的for循环检查E-结点的所有儿子。如果有一个儿子是答案结点,则算法输出由X到T的那条路\n第9章分枝-限界法223径并且终止。如果E的某个儿子不是答案结点,则成为一个活结点,将它加到活结点表(第8行)中且将其PARENT信息段置E。当生成了E的全部儿子时,E变成死结点,控制到达第11行。这种情况只有在E的所有儿子都不是答案结点时才会发生,于是检索应更深入地继续进行。在没有活结点剩下的情况下,这整棵状态空间树就被检索完毕,且没有找到答案结点,算法在第12行结束。反之,则通过LEAST(X)按规定去正确地选择下一个E-结点,并从这里继续进行检索。根据以上讨论,显然LC只有在找到一个答案结点或者在生成并检索了这整棵状态空间树时才会终止。因此,只有在有限状态空间树下才能保证LC终止。对于无限状态空间∧树,在其至少有一个答案结点并假定对成本估计函数c(·)能作出“适当”的选择时也能保证算法LC终止。例如,对于如下的每一对结点X和Y,在X的级数“足够地”大于Y的级数∧∧时,有c(X)>c(Y),这样的成本估计函数就能对结点作适当的选择。对于没有答案结点的无限状态空间树,LC不会终止。因此,将检索局限在寻找估计成本不大于某个给定的限界C的答案结点则是可取的。实际上LC算法与状态空间树的宽度优先检索算法和D-检索算法基本相同。如果活结点表作为一个队列来实现,用LEASL(X)和ADD(X)算法从队列中删去或加入元素,则LC就转换成FIFO检索。如果活结点表作为一个栈来实现,用LEAST(X)和ADD(X)算法从栈中删去或加入元素,则LC就转换成LIFO检索。唯一的不同之处在于活结点表的构造上,即仅在于得到下一个E-结点所使用的选择规则不同。9.1.4LC-检索的特性在许多应用中,希望在所有的答案结点中找到一个最小成本的答案结点。LC是否一定找得到具有最小成本c(G)的答案结点G呢?回答是否定的。考虑图9.5所示的状态空间树,方形叶子结点是答案结点。每个结点内有两个数,上面的数是c的值,下∧∧面的数是估计值c。于是c(根结点)=10而c(根结点)=∧0。LC首先生成根的两个儿子,然后c()=2的那个结点成为E-结点。扩展这一结点就得到答案结点G,它有图9.5LC-检索∧c(G)=c(G)=20,算法终止。但是,具有最小成本的答案结点是c(G)=10的结点。LC没能达到这个最小成本答案结点的原因在于有两个这样的∧∧结点X和Y,当其c(X)>c(Y)时,c(X) c(G′)的答案结点G处终止,而G′是一个最小成本答案结点。令R是G的这样一个最近的祖先,它使得子树R含有一个最小成本答案结点(见图9.6)。假设R,\n224计算机算法基础α1,α2,⋯,αk,G′是由R到G′的路径,R,β1,β2,⋯,βj,G是由R到G的路径。由R的定义,α1≠β1,且子树β1不具有成本为c(G′)的答案结点。为使检索到达结点G,R必须在某一时刻变成E-结点。此时,它的儿子(包括α1和β1)成为活结点。由c(·)的定义,可以得出c(R)=c(α1)=c(α2)=⋯=c(G′)和c(β1),c(β2),⋯,c(G)>c(R)。∧∧∧∧于是,由c(·)的条件,可以得出c(α1),c(α2),⋯,c(αk)<∧c(β1),因此在αi,1≤i≤k,变成E-结点并到达G′以前β1不能变成E-结点。证毕。这个定理易于推广到其每个结点的度都是有限的无限状图9.6状态空间树态空间树的情况。但是要得到满足定理9.2的要求又易于计∧∧算的c(·)通常是不可能的。一般只可能找到一个易于计算且具有如下特性的c(·),对于∧每一个结点X,c(X)≤c(X)。在这种情况下,算法LC不一定能找到最小成本答案结点(见∧∧图9.5)。如果对于每一个结点X有c(X)≤c(X)且对于答案结点X有c(X)=c(X),只要对LC稍作修改就可得到一个在达到某个最小成本答案结点时终止的检索算法。在这个改进型的算法中,检索一直继续到一个答案结点变成E-结点为止。这个新算法是LC1(算法9.2)。算法9.2找最小成本答案结点的LC-检索∧lineprocedureLC1(T,c)∥为找出最小成本答案结点检索T∥1E←T∥第一个E-结点∥2置活结点表为空3loop4ifE是答案结点then输出从E到T的路径5return6endif7forE的每个儿子Xdo8callADD(X);PARENT(X)←E9repeat10if不再有活结点thenprint(′noanswernode′)11stop12endif13callLEAST(E)14repeat15endLC1∧定理9.3令c(·)是满足如下条件的函数,在状态空间树T中,对于每一个结点X,有∧∧c(X)≤c(X),而对于T中的每一个答案结点X,有c(X)=c(X)。如果算法在第5行终止,则所找到的答案结点是具有最小成本的答案结点。∧∧证明此时,E-结点E是答案结点,对于活结点表中的每一个结点L,c(E)≤c(L)。由∧∧假设c(E)=c(E)且对于每一个活结点L,c(L)≤c(L)。因此c(E)≤c(L),从而E是一个最小成本答案结点。证毕。\n第9章分枝-限界法2259.1.5分枝-限界算法检索状态空间树的各种分枝-限界方法都是在生成当前E-结点的所有儿子之后再将另一结点变成E-结点。假定每个答案结点X有一个与其相联系的c(X),并且假定会找到最小∧∧成本的答案结点。使用一个使得c(X)≤c(X)的成本估计函数c(·)来给出可由任一结点X得出的解的下界。采用下界函数使算法具有一定的智能,减少了盲目性,另外还可通过设置∧最小成本的上界使算法进一步加速。如果U是最小成本解的成本上界,则具有c(X)>U的∧所有活结点X可以被杀死,这是因为由X可以到达的所有答案结点有c(X)≥c(X)>U。在∧已经到达一个具有成本U的答案结点的情况下,那些有c(X)≥U的所有活结点都可以被杀死。U的初始值可以用某种启发性方法得到,也可置成∞。显然,只要U的初始值不小于最小成本答案结点的成本,上述杀死活结点的规则不会去杀死可以到达最小成本答案结点的活结点。每当找到一个新的答案结点就可以修改U的值。现在讨论如何根据上述思想来得到解最优化问题的分枝-限界算法。以下只考虑极小化问题,极大化问题可通过改变目标函数的符号很容易地转换成极小化问题。为了找到最优解需要将最优解的检索表示成对状态空间树答案结点的检索,这就要求定义的成本函数满足代表最优解的答案结点的c(X)是所有结点成本的最小值。最简单的方法是直接把目标函数作为成本函数c(·)。在这种定义下代表可行解的结点c(X)就是那个可行解的目标函数值;代表不可行解的结点有c(X)=∞;而代表部分解的结点c(X)是根为X的子树中最小成本结点的成本。由此可知在状态空间树中每个答案结点(当然它必定是解结点)都对应一个可行解,只有成本最小的答案结点才与最优解对应,因而,在这类问题的状态空间树中答案结点与解结点是不相区别的。另外,由于计算c(X)一般和求解最优化问题一样困难,∧∧因此,分枝-限界算法采用一个对于所有的X有c(X)≤c(X)的估值函数c(·)。要特别指出∧的是,在解最优化问题时所使用的c(·)不是用来估计到达一个答案结点在计算方面的难易程度,而是对目标函数进行估计。作为最优化问题的一个例子,考虑5.4节所引入的带限期的作业排序问题。将此问题一般化,允许作业有不同的处理时间。假定有n个作业和一台处理机,每个作业i与一个三元组(pi,di,ti)相联系,它要求ti个单位处理时间,如果在期限dj内没处理完则要招致pi的罚款。问题的目标是从这n个作业中选取一个子集合J,要求在J中的作业都能在相应的期限内完成且使不在J中的作业招致的罚款总额最小。这样的J就是最优解。考虑下面的实例:n=4;(p1,d1,t1)=(5,1,1);(p2,d2,t2)=(10,3,2);(p3,d3,t3)=(6,2,1);(p4,d4,t4)=(3,1,1)。此实例的解空间由作业指标集{1,2,3,4}的所有可能的子集合组成。使用子集和数问题(例8.2)两种表示的任何一种都可以将这解空间构造成一棵树。图9.7所示为元组大小可变的状态空间树,而图9.8所示为元组大小固定的状态空间树。在这两棵树中,方形结点代表不可行的子集合。图9.7中所有的圆形结点都是答案结点。结点9代表最优解并且是仅有的最小成本答案结点。对于这个结点,J={2,3}罚款值(即成本)为8。在图9.8中,只有圆形叶结点才是答案结点。结点25代表最优解,它也是仅有的最小成本答案结点。这个结点对应于J={2,3}和8这么大的罚款。图9.8中各答案结点的罚款数标在这些结点的下面。\n226计算机算法基础图9.7大小可变的元组表示的状态空间树对图9.7和图9.8这两种状态空间表示可将成本函数c(·)定义为,对于圆形结点X,c(X)是根为X的子树中结点的最小罚款;对于方形结点,c(X)=∞。在图9.7的树中,c(3)=8,c(2)=9,c(1)=8。在图9.8的树中,c(1)=8,c(2)=9,c(5)=13,c(6)=8。显然,c(1)是最优解J对应的罚款。图9.8大小固定的元组表示的状态空间树∧∧对于所有的X容易得出这样一个下界函数c(·),它使得c(X)≤c(X)。设SX是在结点∧∧X对J所选择的作业的子集,如果m=max{i|i∈SX},则c(X)=∑pi是使c(X)有c(X)≤i U,所以7被杀死;结点8是不可行结点,也被杀死。接着,结点3成为E-结点,∧生成其儿子结点9和10。u(9)=8,因此U变成8;c(10)=11>U,所以10被杀死。下一个E-结点是6,它的两个儿子均不可行。结点9只有一个儿子且不可行,因此9是最小成本答案结点,它的成本值为8。∧在实现FIFO分枝-限界算法时,每修改一次U,在活结点队中那些有c(X)>U或者在∧U是已找到的一个成本值情况下有c(X)≥U的结点应被杀死,但由于活结点按其生成次序放在活结点队中,因此,队中符合可杀条件的结点是随机分布的,所以每修改一次U就从活结点表中找出并杀死这些结点是很不经济的。一种比较经济的方法是直到可杀结点要变成E-结点时才将其杀掉。但不管采用哪种方法,都必须识别出这个修改了的U是一个已找到的解的成本还是一个不是解成本的单纯的上界(这个单纯上界U由以下任意一种情况得到,一是还没找到一个答案结点,U由处理一可行结点时修改而得;一是虽然找到一答案结点,但它的成本值大于它的上界值,说明这答案结点的子孙中还有成本更小的答案结点,U∧取这上界值)。这样就可以决定在c(X)=U的情况下是否杀死结点X,即若U为前者则杀死X,若为后者,那么X是有希望导致成本值等于U的解的结点,于是应将X变成E-结点。在算法实现时,可引进一个很小的正常数ε来进行这一识别。此ε要取得足够小,使得对于任意两个可行结点X和Y,如果u(X)U的结点是否能减少所生成的结点数?∧∧(3)假定有两个成本估计函数c1(·)和c2(·),对于状态空间树的每一个结点X,若有∧∧∧∧∧c1(X)≤c2(X)≤c(X),则称c2(·)比c1(·)好。是否用较好的成本估计函数c2(·)比用∧c1(·)生成的结点数要少呢?对于以上问题,读者可凭直觉立即得出一些“是”或“非”的答案,但由下述定理可以看出有的结论可能刚好与你的直觉相反。假定下面出现的分枝-限界算法均用来求最小成本答案结点,c(X)是X子树中最小成本答案结点的成本。定理9.4设U1和U2是状态空间树T中最小成本答案结点的两个初始上界且U1 U的结点X而减少。∧证明因为c(X)>U,所以扩展X不可能使U值减小,故扩展X不会影响算法在这棵树上其余部分的运算。证毕。∧定理9.6在FIFO和LIFO分枝-限界算法中使用一个更好的成本估计函数c(·)不会增加其生成的结点数。证明留作习题。定理9.7在LC分枝-限界算法中使用一个更好的∧成本估计函数c(·)可能增加所生成的结点的个数。证明考虑图9.9所示的状态空间树,所有叶结点都是答案结点,叶结点下面的数是其成本值,从这些值中可以得出c(1)=c(3)=3,c(2)=4。结点1,2和3外∧c1∧∧面的数是对应的∧值,显然c2是比c1更好的成本估图9.9定理9.7的一个例子c2\n230计算机算法基础∧∧∧计函数。如果使用c2,由于c2(2)=c2(3),因此结点2会在结点3之前变成E-结点,于是所∧有9个结点都将被生成,而使用c1将不会生成结点4,5和6。证毕。9.20/1背包问题为了用上一节所讨论的分枝-限界方法来求解0/1背包问题,可用函数-∑pixi来代替目标函数∑pixi,从而将背包问题由一个极大化问题转换成一个极小化问题。显然,当且仅当-∑pixi取极小值时∑pixi取极大值。这个修改后的背包问题可描述如下:n极小化-∑pixii=1n约束条件∑wixi≤M(9.1)i=1xi=0或xi=1,1≤i≤n在8.6节曾介绍过0/1背包问题的两种状态空间树结构,这里只讨论在大小固定的元组表示下如何求解0/1背包问题,至于在元组大小可变时,如何求解背包问题,在此基础上是不难解决的。状态空间树中那些表示∑wixi≤M的装包方案的每一个叶结点是答案结1≤i≤n点,其它的叶结点均不可行。为了使最小成本答案结点与最优解相对应,需要对每一个答案结点X定义c(X)=-∑pixi;对不可行的叶结点则定义c(X)=∞;对于非叶结点则将c(X)1≤i≤n递归定义成min{c(LCHILD(X)),c(RCHILD(X))}。∧∧还需要两个函数c(·)和u(·),使它们对于每个结点X,有c(X)≤c(X)≤u(X)。这样的两个函数可由下法得到:设X是j级上的一个结点,1≤j≤n+1。在结点X处已对前j-1种物品装包,这j-1种物品的装入情况为xi,1≤i U=38,因此,它立即被∧杀死。现在活结点表中具有最小c值的结点是6和8,无图9.10例9.2的LC分枝-限界树∧论哪个结点变成下一个E-结点都有c(E)≥U,因此,在找到答案结点8的情况下终止检索,这时打印出值-38和路径8,7,4,2,1,算法结束。要指出的是,由路径8,7,4,2,1并不能弄清是由哪些物品装入背包才得到-∑pixi=U,即看不出这些xi的取值情况,因此在实现过程LCBB时应保留一些能反映xi取值情况的附加信息。一种解决办法是每一个结点增设一个位信息段TAG,由答案结点到根结点的这一系列TAG位给出这些xi的值。于是,对此问题将有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,因此x4=1,x3=0,x2=1,x1=1。为了用过程LCBB(算法9.4)求解背包问题,需要确定:①被检索的状态空间树中结点的结构;②如何生成一给定结点的儿子;③如何识别答案结点;④如何表示活结点表。所需的结点结构取决于开始时采用哪一种状态空间树表示,这里仍采用大小固定的元组表示。要生成并放在活结点表上的每一个结点应有6个信息段:PARENT、LEVEL、TAG、CU、PE和UB信息段。其中,PARENT信息段是结点X的父结点链接指针;LEVEL信息段标志出结点X在状态空间树中结点X的级数,在生成结点X的儿子时使用,通过置XLEVEL(X)=1表示生成X的左儿子,XLEVEL(X)=0表示生成X的右儿子;位信息段TAG正如例9.2所描述的那样,用来输出最优解的xi值;CU信息段用来保存在结点X处背包的剩余空间,该信息段在确定X左儿子的可行性时使用;PE信息段用来保存在结点X处已装入物∧品相应的效益值的和,即∑pixi,它在计算c(X)和u(X)时使用;UB信息段用来存放1≤i L(第24行)或者L=UBB-ε LthenL←prof;ANS←E11endif12:else:∥E有两个儿子∥13ifcap≥W(i)then∥左儿子可行∥14callNEWNODE(E,i+1,1,cap-W(i),prof+P(1)UB(E))15endif∥看右儿子是否会活∥16callLUBOUND(P,W,cap,prof,N,i+1,LBB,UBB)17ifUBB>Lthen∥右儿子会活∥18callNEWNODE(E,i+1,0,cap,prof,UBB)19L←max(L,LBB-ε)20endif21endcase22if不再有活结点thenexitendif23callLARGEST(E)∥下一个E-结点是UB值最大的结点∥24untilUB(E)≤Lrepeat25callFINISH(L,ANS,N)26endLCKNAP9.2.2FIFO分枝-限界求解例9.3[FIFOBB]背包问题采用式(9.1)表示,考虑用FIFOBB(算法9.3)来求解例9.2的背包实例,其工作情况如下:最开始根结点(即图9.11中结点1)是E-结点,活结点队为空。由于结点1不是答案结点,因此U置初值为u(1)+ε=-32+ε。扩展结点1,生成它的两个儿子2和3并将它们依次加入活结点队,结点2成为下一个E-结点,生成它的儿子4和5并将它们加入队。结点了成为下一个E-结点,生成它的儿子6和7,结点6加入队,由于结∧点7的c(7)=-30>U,因此立即被杀死。下次扩展结点4,生成它的儿子8和9并将它们∧加入队,修改U=u(9)+ε=-38+ε。接着要成为E-结点的结点是5和6,由于它们的c值均大于U,因此都被杀死。结点8是下一个E-结点,生成结点10和11,结点10不可行,于是\n第9章分枝-限界法235∧被杀死;结点11的c(11)=-32>U,因此也被杀死。接着扩展结点9,当生成结点12时将∧U和ans的内容分别修改成-38和12,结点12加入活结点队,生成结点13,但c(13)>U,因此13立即被杀死。此时,队中只剩下一个活结点12,结点12是叶结点,不可能有儿子,所以终止检索,输出U值和由结点12到根的路径。为了能知道在这条路径上x的取值情况,和例9.2一样,各个结点还需附上能反映xi取值的信息。图9.11例9.3的FIFO分枝-限界树在用FIFO分枝-限界算法处理背包问题时,由于结点的生成和确定其是否变为E-结点是逐级进行的,因此无需对每个结点专设一个LEVEL信息段,而只要用标志“#”来标出活结点队中哪些结点属于同一级即可。于是,状态空间树中每个结点可用CU、PE、TAG、UB和PARENT这5个信息段构成。过程NNODE(算法9.9)取一个可用结点并给此结点各信息段置值,然后将其加入活结点队。和LCKNAP一样,通过适当修改该问题的FIFO分枝-限界算法可将其变换成一个处理极大化问题的算法,过程FIFOKNAP描述了经过修改后的算法。算法9.9生成一个新结点procedureNNODE(par,tcap,prof,ub)∥生成一个新结点I并将它加入活结点队∥callGETNODE(I)PARENT(I)←par;TAG(I)←tCU(I)←cap;PE(I)←prof;UB(E)←ubcallADDQ(X)endNNODE在算法FIFOKNAP中,L表示最优解值的下界。由于只有生成了N+1级的结点才可能到达解结点,因此可以不用LCKNAP中的ε。第3~6行对可用结点表、根结点E、下界L和活结点队置初值。活结点队最初有根结点E和级结束标志′#′。i是级计数器,它的初始值是1。在算法执行期间i的值总是对应于当前E-结点的级数。在第7~26行while循环的每一次迭代中,取出i级上所有的活结点,它们是由第8~23行的循环从队中逐个被取出的。一旦取出级结束标志,则从第11行跳出循环;否则仅在UB(E)≥L时扩展E,在第13~21行生成E-结点的左、右儿子,这部分的代码与过程LCKNAP相应部分的代码类似。当控制从while循环转出时,活结点队上所剩下的结点都是N+1级上的结点。其中,具有最大PE\n236计算机算法基础值的结点是最优解对应的答案结点,它可通过逐个检查这些剩下的活结点来找到。过程FINISH(算法9.7)打印最优解的值和为了得到这个值应装入背包的那些物品。算法9.10背包问题的FIFO分枝-限界算法procedureFIFOKNAP(P,W,M,N)∥功能和假设均与LCKNAP相同∥1realP(N),W(N),M,L,LBB,UBB,E,prof,cap2integerANS,X,N3callINIT;i←14callLUBOUND(P,W,M,0,N,1,L,UBB)5callNNODE(0,0,M,0,UBB)∥根结点∥6callADDQ(′#′)∥级标志∥7whilei≤Ndo∥对于i级上的所有活结点∥8loop9callDELETEQ(E)10case11:E=′#′:exit∥i级结束,转到24行∥12:UB(E)≥L:∥E是活结点∥13cap←CU(E);prof←PE(E)14ifcap≥W(i)then∥可行左儿子∥15callNNODE(E,1,cap-W(i),prof+P(i),UF(E))16endif17callLUBOUND(P,W,cap,prof,N,i+1,LBB,UBB)18ifUBB≥Lthen∥右儿子是活结点∥19callNNODE(E,0,cap,prof,UBB)20L←max(L,LBB)21endif22endcase23repeat24callADDQ(′#′)∥级的末端∥25i←i+126repeat27ANS←PE(X)=L的活结点X28callFINISH(L,ANS,N)29endFIFOKNAP9.3货郎担问题2n6.7节介绍了货郎担问题的一个动态规划算法,它的计算复杂度为O(n2)。本节讨论2n货郎担问题的分枝-限界算法,它的最坏情况时间虽然也为O(n2),但对于许多具体实例而言,却比动态规划算法所用的时间要少得多。设G=(V,E)是代表货郎担问题的某个实例的有向图,|V|=n,cij表示边〈i,j〉的成本。\n第9章分枝-限界法237若〈i,j〉|E,则有cij=∞。为不失一般性,假定每一次周游均从结点1开始并在结点1结束,于是解空间S可表示成:S={1,π,1|π是{2,3,⋯,n}的一种排列},|S|=(n-1)!。为减小S的大小,可将S限制为:只有在0≤j≤n-1,〈ij,ij+1〉∈E且i0=in=1的情况下,(1,i1,i2,⋯,in-1)∈S。可以将这样的S构造成一棵类似于n-皇后问题的状态空间树(见图8.2)。图9.12给出了|V|=4的一图9.12n=4,i0=i4=1的货郎担问题的个完全图的一种状态空间树。树中每个叶状态空间树结点L是一个解结点,它代表由根到L路径所确定的一次周游。结点14表示i0=1,i1=3,i2=4,i3=2和i4=1的一次周游。为了用LC分枝-限界法检索货郎担问题的状态空间树,需要定义成本函数c(·),成本∧∧估计函数c(·)和上界函数u(·),使它们对于每个结点X,有c(X)≤c(X)≤u(X)。c(·)可以定义为由根到X的路径确定的周游路线成本X是叶结点c(X)=子树X中最小成本叶结点的成本X不是叶结点∧∧在c(·)作了以上定义的情况下,对于每个结点X均满足c(X)≤c(X)的函数c(·)可∧简单地定义成:c(X)是由根到结点X那条路径确定的(部分)周游路线的成本。例如,在图9.12的结点6处所确定的部分周游路线为i0=1,i1=2,i2=4。它包含边〈1,2〉和〈2,4〉。不∧∧∧过一般并不采用以上定义的c(·),而是采用一个更好的c(·)。在这个c(·)的定义中使用了图G的归约成本矩阵。下面先给出归约矩阵的定义。如果矩阵的一行(列)至少包含一个零且其余元素均非负,则此行(列)称为已归约行(列)。所有行和列均为已归约行和列的矩阵称为归约矩阵。可以通过对一行(列)中每个元素都减去同一个常数t(称为约数)将该行(列)变成已归约行(列)。逐行逐列施行归约就可得到原矩阵的归约矩阵。假设第i行的约nn数为ti,第j列的约数为rj,1≤i,j≤n,那么各行、列的约数之和L=∑ti+∑rj称为矩阵约i=1j=1数。作为一个例子,考虑一个有5个结点的图G,它的成本矩阵C为∞2030101115∞1642C=35∞24(9.2)19618∞3164716∞因为对G的每次周游只含有由i(1≤i≤5)出发的5条边〈i,1〉,〈i,2〉,〈i,3〉,〈i,4〉,〈i,5〉中的一条边〈i,〉j,同样也只含有进入j(1≤j≤5)的5条边〈1,j〉,〈2,〉j,〈3,j〉,〈4,〉j,〈5,j〉中的一条边〈i,〉j,所以在此成本矩阵中,若对i行(或j列)施行归约,即将此行(列)的每个元素减去该行(列)的最小元素t,则此次周游成本减少t。这表明原矩阵中各条周游路线的成本分别\n238计算机算法基础是与其归约成本矩阵相应的周游路线成本与矩阵约数之和,因此矩阵约数L显然是此问题∧的最小周游成本的一个下界值,于是可以将它取作状态空间树根结点的c值。对矩阵C的1,2,3,4,5行和1,3列施行归约得C的归约成本矩阵C′,矩阵约数L=25。因此,图G的周游路线成本最少是25。∞10170112∞1120C′=03∞02(9.3)15312∞0110012∞∧为了定义函数c(·),在货郎担问题的状态空间树中对每个结点都附以一个归约成本矩阵。设A是结点R的归约成本矩阵,S是R的儿子且树边〈R,S〉对应这条周游路线中的边〈i,〉j。在S是非叶结点的情况下,S的归约成本矩阵可按以下步骤求得:①为保证这条周游路线采用边〈j,〉j而不采用其它由i出发或者进入j的边,将A中i行和j列的元素置为∞;②为防止采用边〈i,1〉(因为在已选定的路线上加入边〈i,j〉之后若再采用边〈j,1〉就会构成一个环从而得不到这条周游路线),将A(j,1)置为∞;③对于那些不全为∞的行和列施行归约则得到S的归约成本矩阵,令其为B,矩阵约数为r。非叶结点S∧的c值可定义为∧∧c(S)=c(R)+A(i,j)+r(9.4)如果S是叶结点,由于一个叶结点确定一条唯一的∧周游路线,因此可用这条周游路线的成本作为S的c∧值,即c(S)=c(S)。至于上界函数u(·)可将其定义为,对于树中每个结点R,u(R)=∞。现在用LC分枝-限界算法LCBB求解式(9.2)的货郎担问题实例的最小成本周游路线。LCBB使∧用了上面定义的c(·)和u(·)。图9.13给出了图9.13算法LCBB生成的状态空间树LCBB所产生的那一部分状态空间树,结点外的数∧是该结点的c值。根结点1是第一个E-结点,它的归纳成本矩阵为式(9.3)的矩阵C′,此时U=∞。扩展结点1,依次生成结点2,3,4和5。它们对应的归约成本矩阵为∞∞∞∞∞∞∞∞∞∞∞∞11201∞∞200∞∞02∞3∞0215∞12∞043∞∞011∞012∞00∞12∞(a)结点2(b)结点3\n第9章分枝-限界法239∞∞∞∞∞∞∞∞∞∞12∞11∞010∞90∞03∞∞203∞0∞∞312∞01209∞∞1100∞∞∞0012∞(c)结点4(d)结点5以结点3为例,它的归约成本矩阵由以下运算得到:先将矩阵C′的1行和3列所有元素置成∞;再将C′(3,1)置成∞;然后归约第1列,将该列的每个元素减去11即得。由式(9.4)得∧c(3)=25+17(即C′(1,3)的值)+11=53∧结点2,4和5的归约成本矩阵和c值也可类似得到。U的值不变。结点4变成下一个E-结点,它的儿子结点6,7和8被依次生成,与它们对应的归约成本矩阵为∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞11∞01∞∞∞01∞0∞∞0∞∞∞2∞1∞∞003∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞11∞0∞∞00∞∞∞∞00∞∞(e)结点6(f)结点7(g)结点8∧此时的活结点有2,3,5,6,7和8,其中c(6)最小,所以结点6成为下一个E-结点。扩展结点6,生成结点9和10,它们的归约成本矩阵为∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞00∞∞∞∞∞∞∞∞∞∞∞∞∞∞0∞∞∞∞∞∞0∞∞(h)结点9(i)结点10结点10是下一个E-结点,由它生成结点11。结点11是一个答案结点,它对应的周游路线∧∧的成本是c(11)=28,U被修改成28。下一个E-结点应是结点5,由于c(5)=31>28,故算法LCBB结束并得出最小成本周游路线是1,4,2,5,3,1。如果把一条周游路线看成是n条边的集合,则可以得到解的另一种表示形式。设图G=(V,E)有e条边,于是,一条周游路线均含有这e条边中的n条不同的边。由此可以将货郎担问题的状态空间树构造成一棵二元树,树中结点的左分枝表示周游路线中包含一条指定的边,右分枝表示不包含这条边。例如,图9.14(b)和(c)就表示图9.14(a)所示的那个三结点图中的两棵可能的状态空间树的前三级。就一般情况而言,一个给定的问题可能有多棵不同的状态空间树,它们的差别在于对图中边的取舍的决策次序不同。图9.14(b)所示的首先确定边〈1,3〉的取舍,图9.14(c)所示的则首先确定边〈1,2〉的取舍。应采用哪种状态空间树进行检索随货郎担问题的具体实例而定,因此所考虑的是动态状态空间树。为了根据问题的具体实例构造出便于检索的二元状态空间树,应确定图中边的取舍次\n240计算机算法基础图9.14一个例子(a)圆;(b)部分状态空间树;(c)部分状态空间树序。如果选取边〈i,〉j,则这条边将解空间划分成两个子集合,即在将要构造出的状态空间树中,根的左子树表示含有边〈i,j〉的所有周游,根的右子树表示不含边〈i,j〉的所有周游。此时,如果左子树中包含一条最小成本周游路线,则只需再选取n-1条边;如果所有的最小成本周游路线都在右子树中,则还需对边作n次选择。由此可知在左子树中找最优解比在右子树中找最优解容易,所以我们希望选取一条最有可能在最小成本周游路线中的边〈i,j〉作∧为这条“分割”边。一般所采用的选择规则是:选取一条使其右子树具有最大c值的边。使用∧这种选择规则可以尽快得到那些c值大于最小周游成本的右子树。还有一些其它的选择规∧则,例如选取使左、右子树c值相差最大的边等等。本节只使用前一种选择方法。∧在二元状态空间树中,根结点的归约成本矩阵由该实例的成本矩阵归约而得,根的c值是矩阵约数。如果非叶结点S是结点R的左儿子则S的归约成本矩阵的求取与前面所述步∧骤相同,它的c值仍由式(9.4)获得。如果非叶结点S是结点R的右儿子,由于R的右分枝代表不包含边〈i,〉j的周游,因此应将R的归约成本矩阵A中的元素A(i,j)置成∞后,再归约此矩阵不全为∞的行和列(实际上只需重新归约第i行和第j列),即得S的归约成本矩阵∧B和矩阵约数Δ=min{A(i,k)}+min{A(k,j)}。在计算c(S)时,由于周游路线不包含边k≠jk≠i〈i,〉j,所以A(i,j)不必加入,即∧∧c(S)=c(R)+Δ(9.5)∧如果S是叶结点,则与前面一样有c(S)=c(S)。如果选取的边〈i,〉j在R的归约矩阵A中对应的元素A(i,j)为正数,则∧∧该边显然不可能使R右子树的c值为最大,因为c(S)=∧∧c(R)。所以,为了使R右子树的c值最大,应从R的归约成本矩阵元素为0的对应边中选取有最大Δ值的边。仍以式(9.2)的货郎担问题为例,用算法LCBB在动态二元树上进行检索。开始根结点1是E-结点(见∧图9.15),c(1)=25,归约矩阵C′中零元素对应的边为〈1,4〉,〈2,5〉,〈3,1〉,〈3,4〉,〈4,5〉,〈5,2〉和〈5,3〉,各边的Δ值分别为1,2,11,0,3,3和11,因此选取边〈3,1〉或图9.15式(9.2)的状态空间树∧〈5,3〉作为“分割”边可以使结点1右分枝的c值最大。假\n第9章分枝-限界法241∧∧定LCBB选取〈3,1〉。结点1生成结点2和3,其中c(2)=25,c(3)=36,与它们对应的归约成本矩阵为∞10∞01∞101701∞∞11201∞1120∞∞∞∞∞∞3∞02∞312∞04312∞0∞0012∞00012∞(a)结点2(b)结点3结点2成为下一个E-结点,边〈1,4〉,〈2,5〉,〈3,5〉,〈5,2〉和〈5,3〉的Δ分别是3,2,3,3和∧∧11,选取边〈5,3〉为“分割”边,生成结点4和5,c(4)=28,c(5)=36,与它们对应的归约成本矩阵为∞7∞0∞∞10∞01∞∞∞20∞∞020∞∞∞∞∞∞∞∞∞∞∞0∞∞0∞31∞0∞∞∞∞∞∞0∞12∞(c)结点4(d)结点5结点4成为下一个E-结点,边〈1,4〉,〈2,5〉,〈4,2〉和〈4,5〉的Δ分别是9,2,7和0,选取边〈1,4〉∧∧为下一条“分割”边,生成结点6和7,c(6)=28,c(7)=37,与它们对应的归约成本矩阵为∞∞∞∞∞∞0∞∞∞∞∞∞∞0∞∞∞00∞∞∞∞∞∞∞∞∞∞∞0∞∞∞∞0∞∞0∞∞∞∞∞∞∞∞∞∞(e)结点6(f)结点7结点6是下一个E-结点,此时需求的5条边已求出了3条,即{〈3,1〉,〈5,3〉,〈1,4〉},再求两条边就可得到一条周游路线,由矩阵(e)可知此时只剩下两条边{〈2,5〉,〈4,2〉},故它们就是这条周游路线所需要的边。至此LCBB求出了一条成本为28的周游路线5,3,1,4,2,5。U∧被修改成28,下一个应成为E-结点的结点是3,由于c(3)=36>U,故LCBB结束。此例对LCBB作了一点修改,即在“靠近”解结点时作的处理与“没靠近”时的不同。如在结点6处,由于距离解结点只有两级,此时就不再采用分枝-限界方法求结点6的儿子和∧孙子结点的c值,而是对结点6为根的子树中结点逐个检索来找出答案结点。这种对多结点子树采用分枝-限界法而对结点数少的子树采用完全检索的处理方法可使算法效率提高一些。这种处理方法对于图9.13同样适用。关于货郎担问题还可用另外一些分枝-限界方法求解,有兴趣的读者可参阅E.Horowi-tz和S.Sahni所著的“FundamentalsofComputerAlgorithms”(1978年)以及S.E.Good-man和S.T.Hedetniemi所著的“IntroductiontotheDesignandAnalysisofAlgorithms”(1977年)。\n242计算机算法基础习题九9.1证明定理9.1。9.2写一个用LIFO分枝-限界方法检索一个最小成本答案结点的程序概要LIFOBB。9.3给定一个带有限期的作业排序问题:n=5,(p1,p2,⋯,p5)=(6,3,4,8,5),(t1,t2,⋯,t5)=(2,1,∧2,1,1),(d1,d2,⋯,d5)=(3,1,4,2,4)。采用9.1节关于作业排序问题的c(·)和u(·)的定义,画出FI-FOBB,LIFOBB和LCBB对上述问题所生成的、大小可变的元组表示的部分状态空间树,求出对应于最优解的罚款值。9.4使用大小固定的元组表示写一个求解带限期作业排序问题的完整的LC分枝-限界算法。9.5证明定理9.4。9.6证明定理9.6。9.7在大小可变的元组表示下解算例9.2。9.8在大小可变的元组表示下解算例9.3。9.9画出LCKNAP对下列背包问题实例所生成的部分状态空间树:(1)n=5,(p1,p2,⋯,p5)=(10,15,6,8,4),(w1,w2,⋯,w5)=(4,6,3,4,2),M=12;(2)n=5,(p1,p2,⋯,p5)=(w1,w2,⋯,w5)=(4,4,5,8,9),M=15。9.10用LC分枝-限界法在动态状态空间树上做9.9题。使用大小固定的元组表示。9.11使用大小固定的元组表示下的动态状态空间树,写出背包问题的LC分枝-限界算法。9.12已知一货郎担问题的实例由下面成本矩阵所定义:∞731283∞614958∞618935∞11181498∞(1)求它的归约成本矩阵。∧(2)用与图9.12类似的状态空间树结构和9.3节定义的c(·)去获取LCBB所生成的部分状态空间∧树。标出每个结点的c值并写出其对应的归约矩阵。(3)用9.3节介绍的动态状态空间树方法做第(2)题。9.13使用下面的货郎担成本矩阵做9.12题:∞1110968∞73484∞4811105∞56955∞9.14使用像图9.12那样的静态状态空间树和归约成本矩阵方法写出实现货郎担问题的LC分枝-限界算法的有效程序。9.15使用动态状态空间树和归约成本矩阵方法写出实现货郎担问题的LC分枝-限界算法的有效程序。9.16对于任何货郎担问题实例,使用静态树的LC分枝-限界算法所生成的结点是否比使用动态树的LC分枝-限界算法生成的结点少?证明所下的结论。\n第10章NP-难度和NP-完全的问题10.1基本概念本章的内容包括了在算法研究方面的最重要的基本理论。这些基本理论对于计算机科学家、电气工程师和从事运筹学等方面的工作者都是十分有用的。因此,凡是在这些领域里的工作者建议读本章的内容。不过,在阅读本章之前,读者应熟悉以下基本概念:一是算法的事先分析计算时间,它是在所给定的不同数据集下,通过研究算法中语句的执行频率而得到的;二是算法时间复杂度的数量级以及它们的渐近表示。如果一个算法在输入量为n的情况下计算时间为T(n),则记作T(n)=O(f(n)),它表示时间以函数f(n)为上界。T(n)=Ω(g(n))表示时间以函数g(n)为下界。这些概念在第2章都作过详细的阐述。另一个重要概念是关于两类问题的区别,其中第一类的求解只需多项式时间的算法,而第二类的求解则需要非多项式时间的算法(即g(n)大于任何多项式)。对于已遇到和作过研究的许多问题,可按求解它们的最好算法所用计算时间分为两类。第一类问题的求解只需低次多项式时间。例如,本书前面讲过的有序检索的计算时间为O(logn),分类为2.81O(nlogn),矩阵乘法为O(n)等。第二类问题则包括那些迄今已知的最好算法所需时间2n为非多项式时间的问题,例如货郎担问题和背包问题的时间复杂度分别为O(n2)和n/2O(2)。对于第二类问题,人们一直在寻求更有效的算法,但至今还没有谁开发出一个具有多项式时间复杂度的算法。指出这一点是十分重要的,因为算法的时间复杂度一旦大于多项式时间(典型的时间复杂度是指数时间),算法的执行时间就会随n的增大而急剧增加,以致即使是中等规模的问题也不能解出。本章所讨论的NP-完全性理论,对于第二类问题,既不能给出使其获得多项式时间的方法,也不说明这样的算法不存在。取而代之的是证明了许多尚不知其有多项式时间算法的问题在计算上是相关的。实际上,我们建立了分别叫做NP-难度的和NP-完全的两类问题。一个NP-完全的问题具有如下性质:它可以在多项式时间内求解,当且仅当所有其它的NP-完全问题也可在多项式时间内求解。假如有朝一日某个NP-难度的问题可以被一个多项式时间的算法求解,那么所有的NP-完全问题就都可以在多项式时间内求解。下面将会看到,一切NP-完全的问题都是NP-难度的问题,但一切NP-难度的问题并不都是NP-完全的。10.1.1不确定的算法到目前为止在已用过的算法中,每种运算的结果都是唯一确定的,这样的算法叫做确定的算法(deterministicalgorithm)。这种算法和在计算机上执行程序的方式是一致的。从理\n244计算机算法基础论的角度看,对于每种运算的结果“唯一确定”这一限制可以取消。即允许算法每种运算的结果不是唯一确定的,而是受限于某个特定的可能性集合。执行这些运算的机器可以根据稍后定义的终止条件选择可能性集合中的一个作为结果。这就引出了所谓不确定的算法(nondeterministicalgorithm)。为了详细说明这种算法,在SPARKS中引进一个新函数和两条新语句:(1)choice(S)┄┄任意选取集合S中的一个元素。(2)failure┄┄发出不成功完成的信号。(3)success┄┄发出成功完成的信号。赋值语句X←choice(1∶n)使X内的结果是区域[1,n]中的任一整数(没有规则限定这种选择是如何作出的)。failure和success的信号用来定义此算法的一种计算,这两条语句等价于stop语句,但不能起return语句的作用。每当有一组选择导致成功完成时,总能作出这样的一组选择并使算法成功地终止。当且仅当不存在任何一组选择会导致成功的信号,那么不确定的算法不成功地终止。choice,success和failure的计算时间取为O(1)。能按这种方式执行不确定算法的机器称为不确定机(nondeterministicmachine)。然而,这里所定义的不确定机实际上是不存在的,因此通过直觉可以感到这类问题不可能用“快速的”确定算法求解。例10.1考察给定元素集A(1∶n),n≥1中,检索元素x的检索问题。需确定下标j,使得A(j)=x,或者当x不在A中时有j=0。此问题的一个不确定算法为j←choice(1∶n)ifA(j)=xthenprint(j);successendifprint(′0′);failure由上述定义的不确定算法当且仅当不存在一个j使得A(j)=x时输出“0”。此算法有不确定的复杂度O(1)。注意:由于A是无序的,因此确定的检索算法的复杂度为Ω(n)。例10.2[分类]设A(i),1≤i≤n,是一个尚未分类的正整数集。不确定的算法NSORT(A,n)将这些数按非降次序分类并输出。为方便起见,采用一辅助数组B(1∶n)。第1行将B初始化为零。在第2~6行的循环中,每个A(i)的值都赋给B中的某个位置,第3行不确定地定出这个位置,第4行弄清B(j)是否还没用过。因此,B中整数的次序是A中初始次序的某种排列。第7~9行验证B是否已按非降次序分类。当且仅当整数以非降次序输出时,算法成功地完成。由于第3行对于这种输出次序总存在一组选择,因此算法NSORT是一个分类算法,它的复杂度为O(n)。回忆前面讲过的各种确定的分类算法可知它们的复杂度应为Ω(nlogn)。算法10.1不确定的分类算法procedureNSORT(A,n)∥对n个正整数分类∥integerA(n),B(n),n,i,j1B←0∥B初始化为零∥2fori←1tondo\n第10章NP-难度和NP-完全的问题2453i←choice(1∶n)4ifB(j)≠0thenfailureendif5B(j)←A(i)6repeat7fori←1ton-1do∥验证B的次序∥8ifB(i)>B(i+1)thenfailureendif9repeat10print(B)11success12endNSORT通过允许作不受限制的并行计算,可以对不确定的算法作出确定的解释。每当要作某种选择时,算法就好像给自己复制了若干副本,每种可能的选择有一个副本,于是许多副本同时被执行。第一个获得成功完成的副本,将引起其它所有副本的计算终止。如果一个副本获得不成功的完成则只该副本终止。前面说过success和failure信号相当于确定算法中的stop语句,但它们不能用来取代return语句。上述解释是为了便于读者理解不确定算法。注意:对一台不确定机来说,当算法每次作某种选择时,它实际上是什么副本都不作,只是在每次作某种选择时,具有从可选集合中选择出一个“正确的”元素的能力(如果这样的元素存在的话)。一个“正确的”元素是相对于导致一成功终止的最短选择序列而定义的。在不存在导致成功终止的选择序列的情况下,则假定算法是在一个单位时间内终止并且输出“计算不成功”。只要有成功终止的可能,一台不确定机就会以最短的选择序列导致成功的终止。因为这种不确定机本来就是虚构和假想的,所以没有必要去注意机器在每一步是如何作出正确选择的。完全有可能构造出一些这样的不确定算法,它们的多种不同的选择序列都会导致成功完成。例10.2的过程NSORT就是这样的一个算法。如果整数A(i)有许多是相同的,那么在一个分类序列中将出现许多不同的排列。如果不是按已分好类的次序输出这些A(i),而是输出所用的排列,那么这样的输出将不是唯一确定的。今后只关心那些产生唯一输出的不确定算法,特别是只研究那些不确定的判定算法(nondeterministicdecisionalgorithm)。这些算法只产生“0”或“1”作为输出,即作二值决策,前者当且仅当没有一种选择序列可导致一个成功完成,后者当且仅当一个成功完成被产生。输出语句隐含于success和failure之中。在判定算法中不允许有明显的输出语句。显然,早先对不确定计算的定义意味着一个判定算法的输出由输入参数和算法本身的规范唯一地确定。虽然上面所述的判定算法的概念看来似乎限制过严,但事实上许多最优化问题都可以改写成判定问题并使其具有如下性质:该判定问题可以在多项式时间内求解,当且仅当与它相应的最优化问题可以在多项式时间内求解。从另一方面说,如果判定问题不能在多项式时间内求解,那么与它相应的最优化问题也不能在多项式时间内求解。例10.3[最大集团]图G=(V,E)的最大完全子图叫作G的一个集团(clique)。集团的大小用所含的结点数来量度。最大集团问题即为确定G内最大集团的大小问题。与之对应的判定问题是,对于某个给定的k,确定G是否有一个大小至少为k的集团。令DCLIQ-UE(G,k)是此集团判定问题的一个确定的判定算法。假设G的结点数为n,G内最大集团\n246计算机算法基础的大小可在多次应用DCLIQUE(G,k)而求得。对于每个k,k=n,n-1,n-2,⋯直到DCLIQUE输出1为止,过程DCLIQUE都要被引用一次。如果DCLIQUE的时间复杂度为f(n),则最大集团的大小可在n*f(n)时间内求出。假如最大集团的大小可以在g(n)时间内确定,于是其判定问题也可在g(n)时间内解出。因此最大集团问题可在多项式时间内求解,当且仅当集团的判定问题可在多项式时间内求解。例10.4[0/1背包]背包的判定问题是确定对xi,1≤i≤n,是否存在一组0/1的赋值,使得∑pixi≥R和∑wixi≤M。R是一个给定的数,而这些pi及wi都是非负的数。显然,如果背包的判定问题不能在确定的多项式时间内求解,则它的最优化问题也同样不能在确定的多项式时间内求解。在进一步讨论之前,为了测量复杂度必须先统一参数n。假设n是算法的输入长度;还假定所有的输入都是整数。有理数输入可以视为一对整数。一般说输入量的长度是以该量的二进制表示度量的,即如果输入量是十进制的10,相应的二进制表示就是1010,因此它的长度就是4。对于正整数k,用二进制形式表示时的长度就是log2k+1。0的长度规定为1。算法输入的大小或长度n是指输入的各个数长度的总和。因此,用不同的进位制时的长度是不同的,以r为基的正整数k,其长度为logrk+1。在十进制中(r=10)数100的长度为log10100+1=3。因为logrk=log2k/log2r,于是以r(r>1)为基输入的长度为c(r)·n,其中n是以二进制表示时的长度,c(r)是对于给定r后的一个常数。当使用基r=1给定输入量时,则称此输入是一进制形式的。在一进制形式中,数5的输入形式是11111。于是正整数k的长度即为k。注意:一进制形式输入的长度与其相应的基为r(r>1)的输入的长度间具有指数关系。例10.5[最大集团]最大集团判定问题的输入可以看作是一个边的序列和一个整数k。E(G)内的每条边又是一对数值(i,j)。对于每条边(i,j),如果采用二进制表示,则其输入的大小为log2i+log2j+2。于是任一实例的输入大小为n=∑(log2i+log2j+2)+log2k+1(i,j)∈E(G)i Mor∑(P(i)*X(i)) ,≤,=等)这几种形式之一时,M是什么?当i是一条(b)或(c)型的赋值语句时,则它必须选择正确的数组元素,考察一条(b)型指令:R(m)←X。此时公式M可以写成:M=W∧(∧Mj)1≤j≤u其中,u是R的维数。注意,由于对算法A的限制⑦,因此u≤P(n)。W断言1≤m≤u。对W的详细说明则留作习题,每个Mj断言,或者m≠j,或者m=j且只有R的第j个元素改变。假设X和M的值分别存放在字x和m中,并且R(1∶m)存放在字α,α+1,⋯,α+u-1中,Mj由下式给出:Mj=∨T(m,k,t-1)∨Z1≤k≤w\n第10章NP-难度和NP-完全的问题255其中,若j的二进制表示中的第k位为0,则T是B,否则T是B。Z定义为Z=∧((B(r,k,t-1)∧B(r,k,t))∨B(r,k,t-1)∧B(r,k,t)))1≤k≤w1≤r≠P(n)r≠α+j+1∧((B(α+j-1,k,t)∧B(x,k,t-1))∨(B(α+j-1,k,t)∧B(x,k,t-1)))1≤k≤w2w注意:M中的文字数目是O(P(n)),由于j是w位长,因此它只能表示小于2的数。w于是对于u≥2的情况需使用别的下标方案,一种简单的推广是允许多精度运算,下标变量j需要多少字就用多少字,所用字数取决于u,它至多需要log(P(n))个字,这会在Mj内引起2少许的变化,但M中文字的数目仍然保持在O(P(n))。由于程序在存取多精度下标j中单个的字时可以要求此程序模拟多精度运算,因此不需要明显地引入多精度运算。当i是一条(c)型指令时,M的形式与(b)型指令所得到的类似。下面讨论在如下情况时如何来构造M,这里i的形式为Y←choice(S),其中S既可是形如S={S1,S2,⋯,Sk}的集合,也可是r∶u这样的形式,假设Y由字y来提供。如果S是一个集合,则定义M=∨Mj1≤j≤kMj断言Y是Sj。通过选择Mj=a1∧a2∧⋯∧aw,这一点是很容易做到的,其中若Sj中的第l位是1则ai=B(y,l,t),若Sj的第l位是0则ai=B(y,l,t)。如果S的形式为r∶u,那么M是断言r≤Y≤u的公式(这一点留作习题)。在上述两种情况下Gj,t可以转换成CNF,而Gj,t的长度至多增加一个常量。(6)令i1,i2,⋯,ik为对应于A中成功语句的语句编号,则H由下式给出:H=S(i1,P(n))∨S(i2,P(n))∨⋯∨S(ik,P(n))易证,当且仅当在输入I的情况下算法A的计算成功地终止时,Q=C∧D∧E∧F∧G∧H是可满足的。此外,根据前面所述,Q可转换成CNF形式。公式C含有wP(n)个文字,D23含有l个文字,E含有O(lP(n))个文字,F含有O(lP(n))个文字,G含有O(lwP(n))个文3字,H至多含有l个文字。当lw为常数时,Q中出现的文字的总和为O(lwP(n))=32O(P(n))。由于Q中有O(wP(n)+lP(n))个不同的文字,因此每个文字可用2O(log(wP(n)+lP(n)))=O(logn)位写出来。因为P(n)至少为n,所以Q的长度为343O(P(n)logn)=O(n)。由A和I构造Q的时间也是O(P(n)logn)。上面的结构表明,NP中的每一个问题可约化为可满足性问题,也可约化为CNF-可满足性问题。因此若这两个问题的任何一个在P中,则NPP,从而P=NP。另外,可满足性是在NP中,因此由构造公式Q的CNF表明可满足性∝CNF-可满足性。将此和CNF-可满足性在NP中加在一起则意味着CNF-可满足性是NP-完全的。注意:由于可满足性∝可满足性且可满足性在NP之中,故可满足性也是NP-完全的。10.3NP-难度的图问题用来证明一个问题L2具有NP-难度的策略如下:(1)挑选一个已知其具有NP-难度的问题L1。(2)证明如何从L1的任一实例I(在多项式确定时间内)获得L2的一个实例I′,使得从\n256计算机算法基础I′的解能(在多项式确定时间内)确定L1实例I的解。(3)从(2)得出结论L1∝L2。(4)由(1),(3)及∝的传递性得出结论L2是NP-难度的。在下面的叙述中,对于头几个证明将完全按上述4步进行,以后的证明则只进行(1)和(2)两步,一个具有NP-难度的判定问题L2可以通过展示一个求解L2的具有多项式时间的不确定算法来证明它是NP-完全的。下面所涉及的NP-难度的判定问题都是NP-完全的。对其中一些问题构造多项式时间的不确定算法则留作习题。10.3.1集团判定问题(CDP)在10.1节已介绍过集团判定问题,在定理10.2中将证明CNF-可满足性∝CDP。利用可满足性∝CNF-可满足性及∝的可传递性,就可容易地建立关系:可满足性∝CDP,于是CDP是NP-难度的。由于CDP∈NP,所以CDP也是NP-完全的。定理10.2CNF-可满足性∝集团判定问题(CDP)。证明令F=∧Cj是一个CNF形式的命题公式,xj,1≤i≤n,是F中的变量。现证如1≤i≤k何由F构造图G=(V,E),使得当且仅当F是可满足的,则G就有一个大小至少为k的集团。如果F的长度为m,则在O(m)时间内可由F获得G。因此,若对于CDP有一个多项式时间算法,则使用这一构造法就能得到一个对于CNF-可满足性的多项式时间算法。对于任意的F,G=(V,E)被定义如下:V={〈σ,i〉|σ是子句Cj的一个文字};E={(〈σ,〉i,〈δ,〉j)|i≠j且σ≠δ}。例10.11给出了这种构造的一个样本。如果F是可满足的,则对于xi,1≤i≤n,存在一组真值指派,使得在这组指派下每个子句为真。因此,在这组指派下,在每个Ci中至少存在一个文字σ为真,令S={〈σ,i〉|σ在C中为真}是对于每个i恰好只含有一个〈σ,i〉的集合。S形成G内一个大小为k的集团。类似地,如果G有一个大小至少为k的集团K=(V′,E′),则令S={〈σ,〉i|〈σ,〉i∈V′}。显然,当G不再有大于k的集团时|S|=k。更进一步,若S′={σ|〈σ,i〉∈S,对于某些i},则由于G中〈δ,〉i和〈δ,〉j之间无边相连,故S′不能同时含有文字δ和它的补δ。于是,若xj∈S′就置xi=真,若xi∈S′就置x=假,而对不在S′中的变量则选取任意的真值就可使F中的所有子句得到满足。因此,当且仅当G有一个大小至少为k的集团时F是可满足的,证毕。例10.11考察F=(x1∨x2∨x3)∧(x1∨x2∨x3)。根据定理10.2的构造法生成的图如图10.1所示。这个图含有6个大小为2的集团。考虑具有结点{〈x1,1〉,〈x2,2〉}的那个集团,通过置x1=真和x2=真(即x2=假)使F被满足。x3既可置为真也可置为假而对F的可满足性无任何影响。10.3.2结点覆盖的判定问题对于图G=(V,E),集合SV,如果E中所有的边都至少有一个结点在S中,则称S是图G的一个结点覆盖,覆盖的大小|S|是S中的结点数。图10.1图和可满足性的一个样本\n第10章NP-难度和NP-完全的问题257例10.12考察图10.2中的图:S={2,4}是大小为2的一个结点覆盖,S={1,3,5}是大小为3的一个结点覆盖。结点覆盖判定问题(nodecoverdecisionprob-lem)简记为NCDP,它是对给定的图G和一个整数k,判定G是否有大小至多为k的结点覆盖。定理10.3集团判定问题(CDP)∝结点覆盖判定问题(NCDP)。证明令G=(V,E)和k定义一个CDP的实图10.2图与结点覆盖的一个样本例,假设|V|=n。下面来构造一个图G′,使得当且仅当G有一个大小至少为k的集团时G′有一个大小至多为n-k的结点覆盖。图G′给出如下:G′=(V,E),其中E={(u,v)|u∈V,v∈V且(u,v)瓝E}。现证当且仅当G有一个大小至少为k的集团时,G′有一个大小至多为n-k的结点覆盖。令K是G中任一大小为k的集团。由于不存在E中的边会连接K中的结点,因此剩在G中的n-k个结点必定覆盖E中所有的边,即G′有一个大小至多为n-k的结点覆盖。可以类似地证明,若S是G′的一个结点覆盖,则V-S必定形成G中的一个完全子图。由于G′可以在多项式时间内从G获得,因此,如果对NCDP有一个多项式时间的确定算法,则CDP可以在多项式确定时间内解出。证毕。注意:由于CNF-可满足性∝CDP,CDP∝NCDP且∝是传递的,因此可得NCDP是NP-难度的。10.3.3着色数判定问题(CN)图G=(V,E)的着色是对于所有的i∈V所定义的一个函数f:V→{1,2,⋯,k}。如果(u,v)∈E,则f(u)≠f(v)。着色数判定问题(chromaticnumberdecisionproblem)简记为CN,它是对于某个给定的k,确定是否能对G着色。例10.13图10.2所示的要成为二色图的可能方案是f(1)=f(3)=f(5)=1,而f(2)=f(4)=2。显然这个图不可能是1-着色的。为证明CN是NP-难度的要用到另一个NP-难度的问题SATY。SATY问题是带有以下限制的CNF-可满足性问题,它只允许CNF的命题公式中每个子句至多有3个文字。证明CNF-可满足性∝SATY则留下作为习题。定理10.4每个子句至多有3个文字的可满足性(SATY)∝着色数判定问题(CN)。证明令F是一个有r个子句且每个子句内至多有3个文字的CNF形式的命题公式。令xi,1≤i≤n,是F中的n个变量。可以假定n≥4(否则若n<4,那么可以通过对x1,x2和x3的八组可能的真值指派做彻底的试验来确定F是否可满足),当且仅当F是可满足的,则可在多项式时间内构造出一个n+1可着色的图G。图G=(V,E)定义为V={x1,x2,⋯,xn}∪{x1,x2,⋯,xn}∪{y1,y2,⋯,yn}∪{C1,C2,⋯,Cr}E={(xi,xi),1≤i≤n}∪{(yi,yj)|i≠j}∪{(yi,xj)|i≠j}∪{(yi,xj)|i≠j}∪{(xi,Cj)|xi瓝Cj}∪{(xj,Cj)|xi瓝Cj}\n258计算机算法基础为了弄清当且仅当F是可满足的,则G是n+1可着色的,首先要看到所有的yi形成一个有n个结点的完全子图,因此,每个y必须分配一种不同的颜色。不失一般性,可以假设G中yi的着色就用颜色i,因为yi也与除了xi和xi以外的所有xj和xj相连接,所以颜色i只能再分给xi和xi。然而(xi,xi)∈E,因此,这两个结点中有一个需要分配一种新的颜色n+1,不妨将分配新颜色n+1的那个结点称为“假”结点,其它结点称为“真”结点。用n+1种颜色对G着色的唯一方法是对于每一个i,将新颜色n+1着在{xi,xi}的一个之上,1≤i≤n。在这种情况下,其余的结点是否可不再用更多的颜色来着色呢?回答是肯定的,因为n≥4且每个子句至多有3个文字,所以每个Ci至少与一对结点xj和xi相连接。从而没有哪个Ci可以着以颜色n+1,也没有哪个Ci可以着以不在Ci中的xj或xj所着的颜色。上述两句话意味着仅能给Ci着的颜色是在子句Ci中那些被称为“真”结点的xj或xj所着的颜色。因此,当且仅当对应于每个Cj存在一个“真”结点,G是n+1可着色的。故当且仅当F是可满足的,G是n+1可着色的。证毕。10.3.4有向哈密顿环(DHC)有向图G=(V,E)中的一个哈密顿环是一个长度为n=|V|的有向环,它恰好经过每个结点一次,然后回到起始的结点,DHC问题是确定G是否有一个有向哈密顿环。例10.14图10.3中1,2,3,4,5,1是一个有向哈密顿环。若把边〈5,1〉从图中删去,则它就没有有向哈密顿环。定理10.5CNF-可满足性∝有向哈密顿环(DHC)。证明令F是CNF形式的命题公式。下面将设法构造出一有向图G,使得当且仅当G存在一向哈密顿环时,F可满足。由于这一构造可在F大小的多项式时间内完成,因此CNF-可满足性∝DHC。用一个例子来理解G的构造可带来极大的方便。所采用的例子是图10.3图和哈密顿环的样本F=C1∧C2∧C3∧C4。其中,C1=x1∨x2∨x4∨x5,C2=x1∨x2∨x3,C3=x1∨x3∨x5,C4=x1∨x2∨x3∨x4∨x5。假设F有r个子句C1,C2,⋯,Cr和n个变量x1,x2,⋯,xn。画一个有r行和2n列的数组,第i行表示子句Ci,每个变量xi由两个相邻的列表示,其中一列表示文字xi,另一列表示文字xi。图10.4列出了这一数组。当且仅当xi是Cj中的一个文字,就在列xi和行Cj处插入一个○*。同样,当且仅当xi是Cj中的一个文字,就在列xi和行Cj处插入一个○*。在xi和xi的每对列之间引入两个结点ui和vi,ui在列的顶端而vi在列的底部。对于每个i,从ui向上到vj,画两条由边组成的链,一条把列xi的所有○*连接起来,另一条则连接起xi的所有○*(参看图10.4)。然后再画边〈ui,vi+1〉,1≤i≤n。在每一行Ci的右端引入一个方框i,1≤i≤r,画边〈ur,1〉和〈r,v1〉,再画边〈i,i+1〉,1≤i 2是很容易的。令ai,1≤i≤n是分划问题的一个实例。定义n个处理时间为ti=ai,1≤i≤n的作业。当且仅当对这些ai存在一个分划时,则对于这个作业集,在两台处理器上就有一个其完成时间至多为Σti/2的不抢先调度。证毕。定理10.11分划问题∝最小WMFT不抢先调度。证明这里仍只对m=2的情况加以证明,同样,将其扩展到m>2是很简单的事。令ai,1≤i≤n,定义分划问题的一个实例。对于n个作业且w1=ti=ai,1≤i≤n,构造一个双处理器问题。对于这个作业集,当且仅当所有的ai有一分划,那么就存在一种其加权平均完成1212时间至多为∑ai+(∑ai)的不抢先调度S。为了证实这一点,令在处理器P1上的作业24==的权和时间是(w1,t1),⋯,(wk,tk),而在P2上的作业则有(w1,t1),⋯,(w1,t1)。假定这就是作业分别在它们的处理器上处理时的次序。于是对这个调度S有:WMFT=w1t1+w2(t1+t2)+⋯+wk(t1+⋯+tk)=====+w1t1+w2(t1+t2)+⋯+wl(t1+⋯+tl)121212=∑wi+(∑wi)+(∑wi-∑wi)2221212由此可见,WMFT≥∑wi+(∑wi)。这个值当且仅当所有的wi(也就是所有的24ai)有一分划时才能获得。证毕。10.4.2流水线调度这里沿用6.8节所定义的术语。如果有n个要调度的作业,当m=2时其最小完成时间的调度可以在O(nlogn)时间内做到;但当m=3时,无论是抢先调度或是不抢先调度,要得到其最小完成时间的调度都是具有NP-难度的。对于不抢先调度,这一点是容易证明的。下面用抢先调度来证明这一结论。这证明对于不抢先情况也适用。定理10.12分划问题∝最小完成时间的抢先流水调度问题(m>2)。证明只对三处理器的情况作出证明。令A={a1,a2,⋯,an}定义分划问题的一个实例。构造如下的抢先调度实例FS,有n+2个作业和m=3台处理器,每个作业至多有2个非零的任务:t1,i=ai,t2,i=0,t3,i=ai1≤i≤nt1,n+1=T/2,t2,n+1=T,t3,n+1=0\n264计算机算法基础t1,n+2=0,t2,n+2=T,t3,n+2=T/2n其中,T=∑ai1现在来证明,当且仅当A有一个分划,上述流水线调度实例有一个完成时间至多为2T的抢先调度。(1)如果A有一个分划u,则存在一个完成时间为2T的不抢先调度。图10.9给出了一种这样的调度。图10.9一种可能的调度(2)如果A不存在任何分划,则FS的所有抢先调度的完成时间必定大于2T。这一点可用反证法加以证明。假设对于FS存在一种完成时间不大于2T的抢先调度,于是可得以下观察结论:①任务t1,n+1必须在时间T之前完成(因t2,n+1=T,而且在t1,n+1完成之前不能开始)。②任务t3,n+1不能在时间T之前开始,因为t2,n+2=T。结论①意味着,在处理器1上最先开始的T个单位时间只有T/2是空的。令V是在时间T以前在处理器1上所完成任务的下标的集合(不包括任务t1,n+1),由于A不存在任何分划,所以∑t1,i T/2i瓝V1≤i≤n没包括在V中的作业在时间T之前不能在处理器3上着手处理,这是因为直到时间T为止在处理器1上对它们的处理还没完成。这一点与结论②结合起来则意味着,在时间T,留待处理器3处理的工作的时间总量是:t3,n+2+∑t3,i>Ti瓝V1≤i≤n所以,调度长度必定大于2T。证毕。10.4.3作业加工调度与流水线调度一样,作业加工调度问题中也有m台不同的处理器。被调度的n个作业都要求完成若干个任务。作业Ji的第j个任务要求tk,i,j的时间,它将在处理器Pk上执行。任一作业Ji的各任务将依1,2,3,⋯的次序执行,在任务j-1(假若j>1)完成以前不能执行任务j。值得注意的是,对于一个作业而言,它可能有许多任务在同一台处理器上被执行。在不抢先的调度中,任务一旦开始处理就一直进行到完成为止而不得被中断。FT(S)和\n第10章NP-难度和NP-完全的问题265MFT(S)的定义可以很自然地推广到这个问题。即使m=2,得到最小完成时间的抢先调度或最小完成时间的不抢先调度方案都是NP-难度问题。对于不抢先的情况,利用分划来证明是很容易的。下面对抢先的情况给出证明。这个证明对不抢先情况也成立,不过对该情况不是一种最简单的证明而已。定理10.13分划问题∝最小完成时间的作业加工调度问题(m>1)。证明只对使用两台处理器的情况进行证明。令A={a1,a2,⋯,an}定义分划问题的一个实例。构造具有n+1个作业和m=2台处理器的作业加工问题的实例JS。作业1,⋯,nt1,i,1=t2,i,2=ai1≤i≤n作业n+1t2,n+1,1=t1,n+1,2=t2,n+1,3=t1,n+1,4=T/2n其中,T=∑ai。1现证明当且仅当A有一分划时,上述作业加工问题有一个完成时间至多为2T的抢先调度。(1)如果A有一分划u,则存在一完成时间为2T的调度(见图10.10)。图10.10另一种调度(2)如果A不存在任何分划,则对于JS的所有调度,其完成时间必定大于2T。为看出这一点,假设对于JS有一个完成时间至多为2T的调度。于是作业n+1必须像图10.10那样被调度。此外,在P1和P2上还不可能有空闲时间。令R是在[0,T/2]时间内调度到P1上的作业集合。令R′是R的一个子集,它包含了在这段时间内已在P1上完成了其第一个任务的所有作业。由于这些ai不存在分划,故∑ti,j,1 1)个处理器将它们合并成长度为p+q的分类序列。处理器个数k的取值不同可能产生复杂度不同的并行归类算法。这里仅就处理器个数k=pq的情况构造算法。算法的基本思想是,利用分治策略对序列进行分组,并且这种分组方式是动态地和递归地进行。归并过程可简述如下。首先,在两序列中选定一些特定元素,并加以标记,这些标记元素将两序列分成了若干组,接着将第一序列中诸标记元素与第二序列中的诸标记元素进行比较,以确定第一序列中的每一标记元素应插入第二序列中的哪一组中;然后将第一序列中的诸标记元素与第二序列中它所插入的那一组中的其余元素进行比较,以确定第一序列中的每一个标记元素应插入第二序列中的哪一个位置上才能使序列保持有序。插入后,撇开原第二序列中的特定元素不管,则第一序列中的特定元素将第二序列分成若干个组,并且两个序列中的组构成组对。注意,我们规定组中元素不包含特殊元素,因此对于每个组对均存在3种情况:①第一组为空;②第二组为空;③第一、二组都不为空(这时第一组和第二组中元素分别来自第一和第二序列)。称情况③中的组对为非空组对。下面的工作是针对每一组对将第一组中元素插入第二组中去。对于情况①,不需插;对于情况②,是直接搬移;对于剩下的非空组对(若有的话),它们各自构成了独立的归并问题,因此,可并行地对各非空组对递归调用原算法,如此下去,直到不存在非空组对为止。下面给出并行归并算法的非形式化描述:procedurePARA-MERGE(p,q)∥将两个长度分别为p、q的已分类序列A和B用p·q个处理器归并成一个有序∥序列,约定p≤q∥\n第11章并行算法283(1)将A、B序列中位置分别是ip和iq(i=1,2,⋯)的一些元素打上*号;(2)将A中每个带*号的元素与B中每个带*号的元素同时进行比较;(3)将A中带*号的元素与其所插入的B组中的每一个元素进行比较,并把它们插入B中;(4)若有情况②的组对,则将组对中第一组元素搬入B中;(5)若有情况③的组对,则按处理器的个数=pq的分配原则给每个子问题配置处理器,然后对各个子问题并行调用PARA-MERGE算法。endPARA-MERGE现在来分析一下算法中第(5)步递归归并时,原来的pq台处理器够不够用?当执行到第(5)步时,设序列A和B中各组中的元素个数分别为pi和qi,显然,序列A的各组元素个数之和∑pi≤p;序列B的各组元素之和∑qi≤q。根据柯栖不等式∑piqi≤∑pi∑qi所以,∑piqi≤∑piqi≤∑pi∑qi≤pq。由此可见,在对各组对的并行递归调用时,处理器是够用的。下面分析算法的运算时间:在SIMD-CREW模型上,因为序列A和B中要打*号的元素至多有p和q个,所以至多用p+q个处理器在常数时间内完成算法中的第(1)步;在算法的第(2)步中,至多比较pq次,因此,至多用pq个处理器可在常数时间内完成算法的第(2)步;同样可证明至多用pq个处理器在常数时间内完成算法的第(3)步和第(4)步。在算法的第(5)步中因为任意一个非空组对的第一组元素个数小于等于p-1≤q,若设PARA-MERGE算法的运算时间为T(p),则第(5)步的运算时间≤T(p)。因此,T(p)满足关系式C1p足够小T(p)≤T(p)+C2否则其中,C1、C2是常数。解此关系式,可得1/2T(p)≤T(p)+C21/4≤T(p)+2C2⋯≤T(2)+(loglogp)C2=O(loglogp)由此得到PARA-MERGE算法在SIMD-CREW模型上的运算时间为O(loglogp)。对于SIMD-EREW模型,由于算法的第(2)和第(3)步中存在读冲突,因此,需要调用广播算法,这时PARA-MERGE算法的运算还与q有关,若记为T(p,q),则其满足关系式C1logqp足够小T(p,q)≤T(p)+C2logq否则解此关系式,得到T(p,q)≤Clogq+logq·loglogp=O(logqloglogp)。因此,在SIMD-EREW模型上,PARA-MERGE算法的运算时间为O(logq·loglogp)。\n284计算机算法基础有了并行归并算法后,并行归并分类算法就可通过并行归并来构造。设序列的长度为kn=2,k≥1并行归并分类算法描述如下:00第一并行步进行(2,2)并行归并;11第二并行步进行(2,2)并行归并;⋯⋯k-1k-1第k并行步:进行(2,2)并行归并。下面分析此算法在SIMD-CREW模型上的运算时间和性能。iii设运算时间为sort(n),当i>1时(2,2)并行归并的运算时间为O(loglog2),所以kkisort(n)≤∑(Cloglog2)=C∑logi≤Ckloglogni=1i=1即sort(n)=O(lognloglogn)(11.4)设算法所需处理机个数为P(n),所以k-10k-210k-1P(n)=max(22,22,⋯,22)=O(n)(11.5)由于最佳的串行分类算法的运算时间为O(nlogn),因此,并行归并分类算法的加速和效率分别是Sp(n)=O(n/loglogn)(11.6)Ep(n)=O(1/loglogn)(11.7)对于n不是2的幂的情况,只需在序列中增加若干元素,这些元素都大于原序列中的每个元素,并且使得序列长度为2的幂。可以证明,经过这样的预处理后,结论式(11.4)、(11.5)、(11.6)和(11.7)都仍然正确。在SIMD-EREW模型上,并行归并分类算法的运算时间和性能评价留作习题,不在这里赘述。11.3.4求图的连通分支的并行算法无向图G(V,E)的一个连通分支是G的一个最大连通子图。在SIMD共享存储模型上,求图的连通分支的方法主要有3种:采用某种形式的搜索方法、利用图的邻接矩阵的传递闭包法和顶点倒塌法。这里只介绍传递闭包法,顶点倒塌法将在SIMD互连网络中介绍。定义11.1设无向图G(V,E)的邻接矩阵是A,若矩阵B的元素定义如下:1顶点i与顶点j之间有路径存在bij=0顶点i与顶点j之间没有路径存在则称B是A的自反传递闭包。邻接矩阵A与其自反传递闭包B存在下列关系:nB=(I+A)其中,I为单位矩阵;符号“+”定义为逻辑加;n为图结点个数。下面根据这一关系来设计求连通分支的并行算法。假定共享存储器中保存了图G(V,E)的邻接矩阵A,定义G中任一个分支号为此分支中结点号的最小值。算法首先计算出A的自反传递闭包B,然后计算出矩阵C,其中,∞bij=0C(i,j)=jbij=1\n第11章并行算法285最后由d(i)=min(c(i,1),c(i,2),⋯,c(i,n))计算出G中任意结点i(1≤i≤n)所在的分支号D(i)。其算法如下:procedureconnected-componentsG(V、E)(1)foreachpij:1≤i,j≤npardo∥初始化∥ifi=jthena(i,j)←a(i,j)VIendifb(i,j)←a(i,j)d(i)←iendfor(2)forl←1tologndo∥计算传递闭包B∥foreachpijk:1≤i,j,k≤npardob′(i,j,k)=b(i,k)∧b(k,j);nb(i,j)=∨b′(i,j,k)k=1endforrepeat(3)foreachpij:1≤i,j≤npardo∥计算C∥ifb(i,j)=1thenc(i,j)←jelsec(i,j)←∞endifendfor;(4)foreachpij:1≤i,j≤npardo∥求每个结点所在的连通分支标号∥d(i)←min{c(i,j)|1≤j≤n}endforendconnected-components242logn算法的第(2)步是依次并行求出(I+A),(I+A),⋯,(I+A),由于自反传递闭包nn+Tn具有性质:(I+A)=(I+A),其中T为任意正整数,因此第(2)步可求出(I+A)。在第n(2)步中,语句b(i,j)=∨b′(i,j,k)是用n个处理器求b′(i,j,1)∨b′(i,j,2)∨⋯∨b′(i,j,k=1n),因此,可用求和并行算法,用n-1个处理器在O(logn)时间内完成。在第(4)步中,语句d(i)←min{c(i,j)|1≤j≤n}也可利用求和算法,用n-1个处理器在O(logn)时间内完成。由于存在读冲突,因此,此算法是SIMD-CREW模型上的算法。定理11.1在SIMD-CREW模型上,计算无向图G(V,E),|V|=n的连通分支con-23nected-components算法需要O(logn)时间和O(n)个处理器。证明因为算法的第(1)步需O(1)时间和O(n)个处理器;第(2)步由logn次串行循3环组成,对于每一次串行循环操作需O(logn)时间和O(n)个处理器,所以第(2)步需232O(logn)时间和O(n)个处理器;第(3)步需O(1)时间和O(n)个处理器;第(4)步需223O(logn)时间和O(n)个处理器。因此,算法需O(logn)时间和O(n)个处理器。证毕。如果计算模型容许写冲突,那么对算法进行适当的修改可使运动时间降为O(logn)。2这项工作的关键是:在SIMD-CRCW模型上设计使用O(n)个处理器,能在O(1)时间求出n个元素中最大元素的并行算法。具体做法如下:\n286计算机算法基础2假定我们要找c1,c2,⋯,cn这几个元素的最大值,则第(1)步用n个处理器,在O(1)时间内求出tij(1≤i,j≤n),其中1ci≥cjtij=0否则在第一步中存在读冲突,这一步的工作使得矩阵T=[tij]n×n具有如下性质:ck是c1,c2,⋯,cn中的最大元素的充要条件是矩阵T的第k行中n个元素皆为1。证明由矩阵T中元素tij(1≤i,j≤n)的定义可直接推出上述性质。详证略。2第(2)步用O(n)个处理器,在O(1)时间内求矩阵T中n个元素皆为1的某一行,并将其行号赋予k,此步可由下列语句实现:foreachpij:1≤i,j≤npardoiftij=0thentij←0endifendfor;foreachpi:1≤i≤npardoiftij=1thenk←iendifendfor显然,第(2)步中存在写冲突,在SIMD-CRCW模型上通过上面两步可确定c1,c2,⋯cn中的最大元素是ck。242logn下面对connected-components算法中第(2)步进行修改。在矩阵B,B,B,⋯,B2l中,每个矩阵的矩阵元素都是0或1,因此B=[cij],其中cij=max{bi1∧b1j,bi2∧b2j,⋯,bin∧bnj},bij为矩阵B中的元素,1≤i,j≤n,1≤l≤logn。这样,connected-components算法的第(2)步可改写成:forl←1tolognforeachpijk:1≤i,j,k≤npardob′(i,j,k)=b(i,k)∧b(k,j)endforforeachi,j:1≤i,j≤npardob(i,j)=max{b′(i,j,1),b′(i,j,2),⋯,b′(i,j,n)}endforrepeat2由于求n个元素的最大值可用O(n)个处理器在O(1)时间实现,因此,修改后算法的4第(2)步的运算时间为O(logn),需O(n)个处理器。对于整个算法有下面结论。定理11.2在SIMD-CRCW模型上,求无向图G(V,E),|V|=n的所有连通分支的自4反传递闭包算法需O(logn)时间和O(n)个处理器。由上面的讨论不难证明本定理。详证略。11.4SIMD互连网络模型上的并行算法11.4.1超立方模型上的求和算法m假定在m-维超立方模型上,n=2,n个待加的数的集合表示为:A=(a0,a1,⋯,an-1);\n第11章并行算法287且对于所有的0≤i≤n-1,处理器Pi存有局部变量ai。下面在此模型上构造一个并行求和n-1算法,使得算法结束时a0就是总和∑ai。i=0对于处理器Pi,0≤i≤n-1,设其号码i的二进制表示是im-1im-2⋯i0其中,ij=0或1,0≤j≤m-1。这样,可按处理器号码i的第m-1位值im-1将n个处理器分成两类:第一类中im-1=0,第二类中im-1=1。利用超立方模型中结点相邻关系可建立二类处理器集合中元素的一一对应关系。根据这种对应关系,即可构造并行算法。算法的非形式化描述如下:第一并行步对于n个处理器,以im-1的值分类,第一类中的处理器取其在第二类中所对应的处理器中的局部变量,然后与其本身的局部变量相加作为它的局部变量;第二并行步对于n/2个处理器,(即上一步中第一类的处理器),以im-2的值分类,第一类中的处理器取其在第二类中所对应的处理器的局部变量,然后与其本身的局部变量相加作为它的局部变量;⋯⋯第m并行步对于2个处理器,(即上一步中第一类中的处理器),以i0的值分类,第一类中的处理器取其在第二类中所对应的处理器的局部变量,然后与其本身的局部变量相加作为它的局部变量。由于每一步并行操作后,第一类处理器以“+”的形式保存了n个局部变量a0,a1,⋯,an-1的信息,故当执行完第m步后第一类中仅有处理器P0,它的局部变量a0就是n个数的总和。算法的形式化描述如下:procedureSUMMATION(a0,a1,⋯,an-1)forj=(logn)-1to0dojd←2;foreachPi:0≤i≤d-1pardotiai+d;ai←ai+tiendforrepeatendSUMMATION说明:符号表示数据从其相邻的处理器的局存中传往一个活动的处理器中的局存中。由于算法的外循环执行logn次,而每次循环的时间均为常数,所以以上算法的运算时间为Θ(logn)。因为求和问题的最佳串行算法的运算时间为Θ(n),所以,SUMMATION算法的加速比为Sp(n)=Θ(n/logn)效率为Ep(n)=Sp(n)/n=Θ(1/logn)例11.1在超立方模型上求16个数的和,其过程如图11.9所示。\n288计算机算法基础图11.9超立方模型上16个数的求和11.4.2一维线性模型上的并行排序算法一维线性模型是SIMD互连网络模型中最简单和最基本的互连形式,系统中有n个处理器,编号从1到n,每个处理器Pi均与其左、右近邻Pi-1、Pi+1相连,(P1和Pn除外)。我们将在此模型上构造n个数的并行排序算法。假定待排序的n个数的输入序列S={x1,x2,⋯,xn},处理器Pi(1≤i≤n)存有输入数据xi,算法步骤如下。第一并行步所有奇数编号的处理器Pi接收来自Pi+1中的xi+1。如果xi>xi+1则Pi和Pi+1彼此交换其内容。第二并行步所有偶数编号的处理器Pi接收来自Pi+1中的xi+1。如果xi>xi+1则Pi和Pi+1彼此交换其内容。交替重复上述两并行步,经n/2次迭代后算法结束。算法的形式化描述如下:procedureOETS(x1,x2,⋯,xn)fork=1ton/2doforeachPi:i=1,3,⋯,2n/2-1pardoifxi>xi+1thenxi→xi+1endif\n第11章并行算法289endforforeachPi:i=2,4,⋯,2(n-1)/2pardoifxi>xi+1thenxi←→xi+1endifendforrepeatendOETS例11.2对输入序列S={7,6,5,4,3,2,1},上述算法的排序过程见图11.10。OETS算法的正确性由定理11.3给予保证。定理11.3OETS算法至多经过n步可完成排序。证明此定理可通过对n施行归纳法来证明。(1)归纳基础:当n≤3时,可用穷举法验证定理是对的。(2)归纳假定:假定排序m个数算法OETS至多需图11.10OETS算法排序示例要m步。(3)归纳步骤:试图证明排序m+1个数,算法至多需要m+1步。为此,可借助排序图(SortGraph)来研究算法OETS对S={x1,x2,⋯,xm+1}的操作情况。在此图中,追踪S中最大元素M的轨迹。根据M起始时是位于奇数编号和偶数编号的处理器中,可有两种不同情况,分别示于图11.11(a)和(b)中,但每种情况都可认为在k步内可完成排序m+1个数,同时M的轨迹将排序图分成A和B两部分。假定现在M不存在了,如图11.12(a)所示擦去M的轨迹,将A和B如同图11.12(b)那样合并之,这样从第2行向下就是一个完整的m个数的排序图。根据归纳假定可知:k-1=m,所以k=m+1。证毕。下面进行算法分析,很容易求出OETS算法的运算时间T(n)=O(n),进而推出算法的2成本c(n)=T(n)p(n)=O(n),加速Sp(n)=O(logn)。图11.11在排序图中最大元素的轨迹图11.12证明定理11.3的排序图11.4.3树形模型上求最小值算法d在求集合S={x1,x2,⋯,xn}的最小元素问题中,首先假定n=2(d>0),因此,树形模dd+1d+1型共有2个叶子,总共有2-1个结点,即有P(n)=2-1个处理器。其中每个叶处理\n290计算机算法基础器能存放一个数,非叶处理器能存放两个数并可决定何者为小。算法的基本思想是:首先将S中的n个元素加载到各叶处理器中,然后从d级开始(根为第一级)每个非叶处理器从左、右儿子中取出两个数进行比较,并保存较小者,最后使根结点保存集合S中的最小元素。其算法如下:procedureMIN(x1,⋯,x2d)dd+11foreachPi:2≤i≤2-1pardo读取xi-2d+1endfor2forj=dto1doj-1jforeachPi:2≤i≤2-1pardo从左、右儿子中取出两个数进行比较,并保存最小者;endforrepeatendMIN算法分析如下:很显然,算法的第1步运算时间为O(1);因为d=logn,所以算法的第2步的运算时间为O(logn),因此算法的运算时间T(n)=O(logn)。由于求极值的最佳串行算法的运算时间为O(n),所以MIN算法的加速Sp(n)=O(n/logn)。算法的成本c(n)=T(n)×P(n)=O(nlogn),显然该算法不是成本最佳的。11.4.4二维网孔模型上的连通分支算法这里采用顶点倒塌法在二维网孔模型上求无向图G(V,E)的连通分支。定义11.2图G的一个超顶点集A是G的一个连通子图,此集由A中最小标号结点标识,这个结点称为A的根。并且对A中任一结点i赋一个指针信息:D(i)=k,其中k为A的根的标号。顶点倒塌法的基本思想是:一开始G中每个结点都当成一个超顶点集,然后是超顶点集的合并、扩大过程,此过程由一系列循环完成。每次循环分为三步:第一步对于G中每个超顶点集i,给其每个结点ij赋个0值,使其满足当存在与ij相邻的结点且此结点不在集i中时,c(ij)=min{D(s)/s与ij相邻,且s不在超顶点集i中},否则c(ij)=∞;第二步对于G中每个超顶点集i,修改其根结点i的D值,使其满足min{c(t)|D(t)=i,c(t)≠i}存在与i集相邻的超顶点集D(i)=i否则到此步为止,每个超顶点集i的根结点的D指针或指向与集i相邻的标号最小的超顶点集,或指向其本身(此时i是G的一个连通分支);第三步将由第二步连接起来的所有超顶点集扩大成一个超顶点集。关于循环的次数,由定理11.4给出。定理11.4在无向图G(V,E),|V|=n中,用顶点倒塌法求其连通分支的循环过程至多须执行log2n次,就可保证每个超顶点集是G的一个连通分支。\n第11章并行算法291证明由顶点倒塌法可知,对于任意一个超顶点集i,当且仅当没有其它的超顶点集与它相邻时超顶点集i才不能扩大,因此当超顶点集i不能扩大时,它就是图G的一个连通分支。设执行完k次循环后,G中可扩大超顶点集的个数为N(k),因此,有nk=0N(k)=N(k-1)/2k>0当k=logn时,N(logn)≤N(logn-1)/22≤N(logn-2)/2⋯logn≤N(0)/2≤1因为可扩大的超顶点集的个数不会等于1,所以N(logn)=0,即至多进行logn次循环后,每个超顶点集是G的一个连通分支。在叙述连通分支算法之前,首先引入算法所须执行的几个子过程和函数:(1)子过程RAR(a(i),b(c(i)))的功能是将未屏蔽的处理器i中局部变量a(i)的值等于处理器c(i)中的局部变量b(c(i))的值;(2)子过程RAW(a(i),b(c(i)))的功能是将未屏蔽的处理器i中局部变量a(i)的值写到处理器c(i)中的局部变量b(c(i))中;函数BITS(i,j,k)的功能是返回一个数,此数由整数i(i>0)的二进制表示第j位到第k位组成(0位是最低位),如BITS(17,3,1)=0,BITS(10,3,2)=2BITS(16,4,4)=1,BITS(15,2,3)=3(3)子过程Reduce(D,n)的功能是,对于一串由根结点的D指针链接的超顶点集,将此串中每个超顶点集的所有结点的D指针赋一个值,此值是串中最后一个超顶点集的根的标号。子过程具体算法如下:ProcedureReduce(D,n)forb←1tologndoforeachi:1≤i≤npardoifBITS(D(i),logn-1,b)=BITS(i,logn-1,b)thenRAR(D(i),D(D(i)))endifendforrepeatendReduce下面给出在二维网孔模型上用顶点倒塌法求图的连通分支的并行算法。假定无向图kG(V,E)有n=2个结点,图G的度数为d,令adj(i,j)是结点i的邻接表(1≤i≤n,1≤j≤n)。如果结点i有di(di≤d)条关联边,那么对于di+1≤j≤d,有adj(i,j)←∞,对于1≤j≤d,adj(i,j)是与结点i相邻的某一结点标号,每个顶点的邻接表存放在对应编号处理器的局\n292计算机算法基础部存储器中。其算法如下:procedureCCOMA输入:每个处理器i存放着邻接表adj(i,j),1≤j≤d输出:每个处理器i有一局部变量D(i),它是结点i所在连通分支的标识,D(i)等于i所在连通分支的最小标号结点(1≤i≤n)。1foreachi:1≤i≤npardo∥初始化,每个结点是个超顶点集∥D(i)←iendfor2forb←0tologn-1do∥循环进行logn次顶点倒塌法∥2.1foreachi:1≤i≤npardo∥C指针初始化∥c(i)←∞endfor2.2forj←1toddo∥每个结点i找邻接结点的最小超顶点集,并将此集的标号赋予i结点的C指针∥foreachi:1≤i≤npardoRAR(temp(i),D(adj(i)));iftemp(i)=D(i)thentemp(i)←∞endif;c(i)←min{c(i),temp(i)}endforrepeat2.3foreachi:1≤i≤npardo∥用D指针将某些相邻超顶点集连成串∥RAW(c(i),D(D(i)));∥D(i)是根结点∥ifD(i)=∞thenD(i)←iendififD(i)>ithenRAR(D(i),D(D(i)))endifendfor;2.4Reduce(D,n)repeatendCCOMA对于上述算法作如下说明:(1)经过执行算法的第2.3步中的子过程RAW(c(i),D(D(i)))后,对于任意一个超顶点集i,其相邻的标号最小的超顶点集D(i)的标号D(i)不一定小于i,但D(i)的相邻的标号最小的超顶点集的标号D(d(i))一定小于或等于i。因此,经过执行算法的第2.3步后,对于由D指针链接的两个超顶点集i和D(i),有i≥D(i)。(2)在算法的第2.3步中执行子过程RAW(c(i),D(D(i)))时可能存在写冲突,解决冲突的策略是把最小的c(i)值写入D(D(i))中。在给出算法的复杂性之前,首先引进一个引理,在分析复杂性时要用到它。引理11.1在n个处理器组成的二维网孔上,实现过程RAR及过程RAW需O(n)时间。证明由11.1中给出的二维网孔阵列图(见图11.4),当二维网孔由n个处理器组成时,最坏情况下两结点信息交换步数为2(n-1),(即对顶拐角上两结点的信息交换),即为\n第11章并行算法293O(n)。同样可证明对于二维网孔连接的两个变种在最坏情况下同一行上两结点信息交换步数为n-2,所以在最坏情况下两结点信息交换的步数也为O(n),因此在二维网孔上实现过程RAR及过程RAW需O(n)时间。k定理11.5已知无向图G(V,E),其中|V|=2=n,G的度数为d,那么在n个处理器组成的二维网孔上求G(V,E)的所有连通分支的CCOMA算法需O(dnlogn)时间。证明因为算法中第2.2步需O(dn)时间,而第2.2步需执行logn次,故整个算法需O(dnlogn)时间。11.5MIMD共享存储模型上的并行算法11.5.1并行求和算法假定有p个处理器,其标记为P0,P1,⋯,Pp-1,全局存储器有变量a0,a1,⋯,an-1,它们包含有待加的各数的值,全局变量g存放最终结果。算法的思路是:对于每个处理器Pi(0≤i≤p),它们各自利用其局部变量li计算ai+ai+p+ai+2p+⋯+ai+k,p(n-p≤i+kip≤n),然j后,将求得的子和加到全局变量g中。显然,此算法存在存储冲突,解决的办法是当一个处理器访问全局变量g时对其加锁,访问完毕后立即开锁,其算法如下:procedureSummation-MIMDg←0;foreachPi:0≤i x(l)thenk←k+1elseifx(j)=x(l)andj>lthenk←k+1endifendift(k)←x(j)repeatrepeatendforendENUERSORT由于每个处理机至多确定数组X中的n/p个元素位置,而每确定一个元素的位置需O(n)时间,因此对X进行排序需n/p*O(n)时间。因为进程同步开销为O(p),因此整个2并行算法的时间开销t(n)=n/p*O(n)+O(p)=O(n/p)。由于最佳串行分类算法需O(nlogn)时间,因此此并行算法的加速比为O(plogn/n)。注意:ENUERSORT算法存在读冲突,因此它是MIMD-CREW模型上开发的算法。11.5.4二次取中的并行选择算法首先引进在MIMD共享存储模型上求m个数的部分和算法。算法输入为序列A={a1,a2,⋯,am},输出为T={t1,t2,⋯,tm},其中ti=a1+a2+⋯+ai,1≤i≤m。处理机个数为m。其算法如下:procedurePS-MIMD(A,T,m)forj=0tologm-1dojfori=2+1tompardoti←aijti←ti+ai-2jendforjfori=2+1tompardoai←tiendfor\n296计算机算法基础repeatendPS-MIMD引理11.2PS-SIMD算法的复杂度为O(mlogm)。j证明显然此算法求部分和操作需O(logm)时间。对于i的每个定值有m-2个处理logm-1jj器并行操作,且同步二次,开销为O(m-2)时间,因为∑(m-2)=O(mlogm)所以算法在j=0同步上的开销为O(mlogm),由此推出算法的时间复杂度为O(mlogm)。1/2假定输入系列S={x1,x2,⋯,xn},在MIMD共享存储器模型上利用N=n个处理器求S中第k(1≤k≤n)小元素的并行算法,其非形式化描述如下:1/2(1)将S分成N段,每段至多n个元素。每段指派一个处理器,各段同时并行求出各自的中值(使用串行二次取中算法);(2)递归调用并行选择算法求出中值的中值m;iii(3)以m为划分元,并行地对各段进行划分。记i(1≤i≤N)段分成S,U,和R三个子段,它们分别由段中小于、等于和大于m的元素组成;iiiiii(4)并行求出S(1≤i≤N)中元素个数ks,U中元素个数ku和R中元素个数kr;11212N12(5)利用PS-MIMD算法,并行求出ksks+ks⋯,ks+ks+⋯+ks,并分别赋予ts,ts,N⋯,ts;11212N12(6)利用PS-MIMD算法并行求出kuku+ku,⋯,+ku+ku+⋯+ku并分别赋予tu,tu,N⋯,tu;11212N12(7)利用PS-MIMD算法并行求出krkr+kr,⋯,kr+kr⋯+kr并分别赋予tr,tr,⋯,Ntr;iii(8)根据S(1≤i≤N)的末地址ts并行地将N个S子段中元素写入S的相应位置中,记这些元素组成子段S1;iii(9)根据R(1≤i≤N)的末地址tr并行地将N个R子段中元素写入S的相应位置中,记这些元素组成子段S2;NNN(10)利用ts和tu和tr,判断第k个元素是等于m还是在S1或S2中,对于后两种情况则可对S1或S2递归调用并行选择算法。下面对二次取中并行选择算法进行算法分析。引理11.3在二次取中并行选择算法中,大于划分元素m的元素个数至多为(3/4)n;小于划分元素的元素个数至多为(3/4)n。证明因为m1,m2,⋯,mN中至少有N/2个元素大于等于m,又因为在第i段中至少2有[N/2]个元素大于等于mi;因此S中至少有(N/4)个元素大于等于m。2由此推出S中至多有n-N/4个元素小于m,即至多有(3/4)n个元素小于划分元素。同理可证明,S中至多有(3/4)n个元素大于划分元素。定理11.7在MIMD共享存储模型上,二次取中的并行选择算法在最坏情况下需O(nlogn)时间。证明记算法的运算时间为T(n),不难验证算法中第(1)、(2)、(3)、(4)、(8)、(9)步操作时间为O(n)或T(n)。由于PS-MIMD算法的运算时间为O(mlogm)(其中m为加数\n第11章并行算法297个数),因此算法中第(5)、(6)、(7)步操作时间都为O(nlogn)。由引理11.3知,算法中第(10)步的操作时间为T((3/4)n)。下面计算算法的同步开销。算法中需要同步的是第(1)、(3)、(4)、(8)、(9)步,其时间开销都为O(n)。因此,算法的运算时间T(n)满足T(n)≤cnlogn+T(n)+T((3/4)n)(11.8)下面用数学归纳法证明T(n)≤40cnlogn。4当n≤5时,可选定适当大的c使不等式T(n)≤40cnlogn成立。设5≤n≤m-1时结论成立。当n=m时,由式(11.8)得T(m)≤cmlogm+T(m)+T(3/4m)。由归纳法的1/440c(m)logm33m假设得:T(m)≤cmlogm++40cmlog(3/4m)。因为40clog(3/4)m2444<0.87×40cmlogm,又可证:当m≥5时,140c(m)4logm/2≤0.1×40cmlogm所以,T(m) m,xi∈S},然后递归调用原算法对S1和S3中元素进行排序。不难证明改进后的快速分类算法在最坏、最好和平均情况下的运算时间都是O(nlogn)。假定处理器个数N=n,在对改进的串行快速排序并行化时,可利用前面介绍的二次取中并行选择算法确定划分元m。在对二个子排序问题的递归调用上有两种方案。第一种方案是二个递归调用并行执行。由于此方案在最坏情况下所需的处理器个数P(n)=n/2+n/2>n,所以不可行。第二种方案是二个递归调用串行执行。由于每次递归调用只需要一半的处理器进行工作,使得大量的处理器无事可做,从而直接影响了算法的并行度,所以第二种方案也不理想。为了提高算法的并行度,可按下述方式解决:3次调用二次取中并行选择算法。在S中选择3个元素m1,m2和m3,它们分别是序列S中第n/4小、第n/2小和第3n/4小元素。以它们为划分元素,将S分成S1、S2、S3、S4、U1、U2和U3共7个序列,其中,S1={xi|xi m3,xi∈S}U1={xi|xi=m1,xi∈S};U2={xi|xi=m2,xi∈S};U3={xi|xi=m3,xi∈S}。然后并行地递归调用原算法对S1和S2同时进行排序,接\n298计算机算法基础着并行地递归调用原算法对S3和S4同时进行排序,从而完成对序列S的排序。由于此方式在最坏情况下所需的处理器个数P(n)=max{n、2n/4}=n,所以是可行的。按照上述方式,在MIMD共享存储模型上的并行快速排序算法描述如下:procedurePQS(S)输入:S={x1,x2,⋯,xn},|S|=n输出:非降有序序列处理器数:N=n(1)若|S|<3,则调用串行排序算法对S进行排序,算法结束。(2)调用二次取中并行选择算法,确定S中第n/4小元素m1。(3)调用二次取中并行选择算法,确定S中第n/2小元素m2。(4)调用二次取中并行选择算法,确定S中第3n/4小元素m3。(5)将S分成N段,每段指定一个处理器,以m1,m2和m3为划分元素,并行地对各段进行划iiiiiii分。记第i(1≤i≤N)段分成S1、S2、S3、S4、U1、U2和U37个子段,其中iiS1={xi|xi m3,xi属于第i段};iiU1={xi|xi=m1,xi属于第i段};U2={xi|xi=m2,xi属于第i段};iU3={xi|xi=m3,xi属于第i段}。iiiiiii(6)并行计算出每段中7个子段的元素个数|S1|、|S2|、|S3|、|S4|、|U1|、|U2|和|U3|,1≤i≤N。11212N(7)利用PS-MIMD算法并行求出|S1|、|S1|+|S1|,⋯,|S1|+|S1|+⋯+|S1|。iiiiii(8)类似于(7),分别求出|S2|、|S3|、|S4|、|U1|、|U2|和|U3|,1≤i≤N的部分和。i(9)根据(7)的计算结果,并行地将N个S1(1≤i≤N)中元素写入S相应的位置中,记这些元素组成子段S1。iiiiii(10)类似于(9),根据(8)的计算结果,分别把U1、S2、U2、S3、U3和S4中元素并行写入S的相应位置中,记这些元素分别组成子段U1、S2、U2、S3、U3和S4。(11)对子段S1和S2并行调用PQS算法。(12)对子段S3和S4并行调用PQS算法。endPQS记PQS算法的运行时间为T(n),不难证明当|S|≥3时T(n)满足关系式:T(n)≤cnlogn+2T(1/4n)(11.10)由此关系式可推出下面结论。定理11.8在MIMD共享存储模型上,当输入规模为n,处理器个数N=n时,PQS2算法的运算时间为O(nlogn)。证明记算法的运算时间为T(n),因此,当n<3时定理显然成立。当n≥3时,不妨设kn为4的幂,设n=4,因此T(n)≤cnlogn+2T(1/4n)22≤cnlogn+cnlog(n/4)+2T(1/4n)⋯⋯logn≤cn(logn+log(n/4)+⋯+log4)+24T(1)\n第11章并行算法299=2cn(k+(k-1)+⋯+1)+nT(1)=O(nlogn)证毕。11.5.6求最小元的并行算法这里将在MIMD共享存储模型上利用p个处理器建立求序列I={A(1),A(2),⋯,A(n)}的最小元的并行算法。算法的基本思想是,当n>p时,将序列中的元素分成元素个数基本相同的p个组,从处理器Pi(1≤i≤p)求出第i组中的最小元素,这样把求n个元素的最小问题转化为求p个元素的最小问题。当n≤p时,进行logn次迭代,每次迭代时,将元素按中心点对称的方法两两分组,求出每组的最小元,使得经过一次迭代后元素个数减少一半,最终求出最小元素。其算法如下:procedureMIMDMIN(A(1),A(2),⋯,A(n),min)输入:I={A(1),A(2),⋯,A(n)}输出:I中最小元min处理器个数:pifn>pthenforeachPi:1≤i≤ppardoforj=i+ptonsteppdoifA(j)1doforeachPi:1≤i≤k/2pardoifA(k-i+1)|AL|+|AE|如果A=AE,则返回元素m,否则按下式计算k′值:k若A′=ALk′=k-|AL|-|AE|〗若A′=AG(4)递归调用本算法,以求出A′中第k′小元素。endSELECT下面把上述串行算法并行化,产生在MIMD-CL模型上选择问题的并行算法。t首先假定处理机个数p=2-1,处理机按树形结构连接。在执行算法之前,数组A中n个元素已平均分配到p个处理机的局部内存中,在处理机Pi{1≤i≤p}的局部存储器中,存储单元mi存放局部存储器所存放的数组A中元素的个数。并行算法的非形式化描述如下:procedureMIMD-CL-PS(1)根结点通知其余节点将其所保存的元素个数送往根;其余节点向根发送各自所保存的元素p个数;根结点计算|A|=∑|mi|,如果|A|=1,根结点通知该元素所在结点将此元素送往根i=1结点,算法结束;否则就执行以下各步。(2)随机地从|A|个元素中选出一个元素m作为划分元素并送往根结点。其过程是①根结点在区间[1,|A|]中随机选择一整数j。②按先根周游次序循环地进行以下操作:结点i判定mi-j是否大于等于0,若mi-j≥0,则结点i将其保存的第j个元素送往根作为划分元素;否则j:=j-mi并将j发送给下一个结点。③根结点将划分元素m发送给其它所有结点。(3)每个结点i(1≤i≤p)将其局部存储器中的元素按m划分成ALi,AEi和AGi三个子集合,它们分别包含小于、等于和大于m的那些元素,并将|AL|,|AE|和|AG|发送给根结点。(4)根结点计算出:ppp|AL|=∑|AL||AE|=∑|AEi||AG|=∑|AGi|i=1i=1i=1若|AL| |AL|+|AE|,则k←k-|AL|-|AE|,并且根结点通知每个结点i保存集合AGi中元素,并且mi←|AGi|。(5)递归调用本算法。\n第11章并行算法303endMTMD-CL-PS定理11.10在串行SELECT算法中,至多进行了O(n)次递归调用,平均递归调用O(logn)次。证明在最坏情况下,每进行一次递归调用元素个数减少一个,因此,在SELECT算法中至多递归调用O(n)次。设在SELECT算法中平均调用T(n)次。因为经过一次划分选择后,下一次递归调用所涉及的元素个数可能为0,1,2,⋯,n-1个,并且各种情况出现的概率相同,所以,平均涉及(n-1)/2个元素。由此建立T(n)的递推关系式:1+T((n-1)/2)n>1T(n)=1n=1由此不难推出T(n)=O(logn)。证毕。并行MIMD-CL-PS算法是利用并行划分来加快划分速度的,而每次划分的结果与串行SELECT算法的划分结果是一致的。因此,MIMD-CL-PS算法至多递归调用O(n)次,平均递归调用O(logn)次,由此可推出下面结论:定理11.11MIMD-CL-PS算法的通信复杂度在最坏情况下为O(np),在平均情况下为O(plogn)。证明在算法的第(1)步骤中,非根结点向根结点发送各自所保存的元素个数的过程是:首先是由叶子结点向其父结点发送,当一个结点i收到其左、右儿子发送来的值后将此二值与mi相加然后再发送给它的父结点。因此第(1)步骤的信息交换数为O(p)。类似地可推出第(2)、第(3)、第(4)步骤的信息交换数都为O(p)。由于MIMD-CL-PS算法在最坏情况下要运行O(n)遍,在平均情况下要运行O(logn)遍,因此算法的通信复杂度在最坏情况下为O(pn),在平均情况下为O(plogn)。证毕。2定理11.12在最坏情况下MIMD-CL-PS算法的运算时间为O(n/p+nlogp)。证明不难验证,算法的最坏情况是:每递归调用一次,数组A中元素个数仅减少一个,并且每连续n/p次递归调用后,仅使一个处理机保存的元素个数为零。因此,在最坏情况下算法要运行O(n)遍,在每一遍中划分操作时间为O(n/p)。由此推出整个算法的操作时22间为O(n/p)。因为二元树的深度为O(logp),所以在最坏情况下算法的运算时间为O(n/p+nlogp)。证毕。11.6.2求极值问题的并行算法令A={a1,a2,⋯,an},求集合A中元素的极值分为求极大值和最小值。不失一般性,仅考虑求极大值。在MIMD异步通信模型上求极值的并行算法与网络的拓扑结构密切相关,这里首先介绍在树形网络结构上的求极大值并行算法。假定有p个处理器,它们以二叉树的方式彼此相连,处理器P1为树的根。集合A中n个元素被分成大小大致相等的p个集合:A1,A2,⋯,Ap并假定子集合Ai(1≤i≤p)中的元素已存入处理器Pi中的局部存储器中。算法的基本思想是,首先根结点P1通知其余结点对各自局部存储器中的元素求极大值,然后根结点P1求出A1中的极大值。当其余结点求出它们各自保存的元素的极大值后,由叶子结点开始向父结点送其极值,当父结点收到左、右儿子的极值后与其本身极值进行比较,取最大者为它的极值,并将此值发送给它们的父结点,当根结点收到左、右儿子的极值\n304计算机算法基础后,通过比较,就产生了集合A中的极值。其算法如下:procedureTCMAX根结点P1的算法:send(m)messagetoL(P1);∥向其左儿子发送消息m∥send(m)messagetoR(P1);∥向其右儿子发送消息m∥callMax(A1,t(1));∥求集合A1中的极大值并赋予t(1)∥uponreceiving(M)fromsondo:ifM>t(1)thent(1)←Mendifuponreceiving(M)fromsondo:ifM>t(1)thent(1)←Mendif非根结点Pi的算法:uponreceiving(m)fromfathendo:ifPi不是叶子thensend(m)messagetoL(Pi);send(m)messagetoR(Pi);endifCallMax(Ai,t(i));ifPi是叶子thensend(M)messagetofarther;∥向其父发极大值信息M(=t(i))∥elseuponreceiving(M)fromsondo:ifM>t(i)thent(i)←Mendif;uponreceiving(M)fromsondo;ifM>t(i)thent(i)←Mendif;send(M)messagetofather∥向其父发极大值信息M(=t(i))∥endif;TCMAXend对于TCMAX算法,必须假定处理器的个数为2的幂减1,因为,此假定保证了树中每个非叶子结点都有两个儿子。当处理器个数不满足上述假定时,须对算法作适当的修改。如何修改留作习题。下面对TCMAX算法进行复杂性分析。定理11.12在具有p个处理机器的树形网络结构上,对于集合A={a1,a2,⋯,an},求其元素极大值的TCMAX算法的通信复杂性为O(p),时间复杂性为O(n/p+logp)。证明在整个算法中,结点间进行通信的信息有两类,即通知儿子进行求极大值操作的信息m和传送子树中元素极大值的信息M。这两类信息的信息交换次数都等于树的边数,因此算法的通信复杂性为O(p)。对于每个处理器,它们求出各自局部存储器中元素极大值的操作时间为O(n/p)。因为通信树的深度为O(logp),因此不难推出通信时间为O(logp),最后得到算法的时间复杂性为O(n/p)+O(logp)=O(n/p+logp)。下面介绍在环形网络拓扑结构中求极大值的并行算法。假定在MIMD-CL计算模型\n第11章并行算法305中,p个处理器松散耦合成一个环,集合A={a1,a2,⋯,an}中的元素已分散存放在p个处理器的局部存储器中,各个处理器存储的元素个数大致相等,每个处理器只能和其近邻交换信息,没有中心控制器存在。在环形结构中求集合A中元素的极大值算法可分为两大类:在第一类算法中,每个处理器可以向其左、右近邻发送和接收信息,在第二类算法中,每个处理器只能向其左近邻发送信息和从其右近邻接收信息。这里,我们仅介绍第二类算法。[12]下面给出的RLMAX算法是基于Chang和Roberts提出的一种改进的单向算法的设计思想,但必须增加一个限制条件,限定集合A中无相同元素。算法的非形式化描述如下。procedureRLMAX(1)每个结点Pi(1≤i≤p)求出其局部存储器中所保存的元素的极大值Mi。(2)每个结点Pi将Mi传给其左邻结点。(3)每个结点Pi收到来自其右邻结点的信息后进行下列操作:①当Mi小于收到的信息时,Pi将收到的信息传给其左邻结点;②当Mi大于收到的信息时,则将收到的信息丢弃;③当Mi等于收到的信息时,则Mi就是集合A中元素的极大值。endRLMAX根据环形网的结构性质、单向传递的约定和对集合A的限制条件,任何信息在返回到产生它的结点之前,必须经过其它所有结点,因此,当且仅当A中元素的最大值作为信息时,此信息才能返回到产生它的结点。由此可推出算法是正确的。关于RLMAX算法的时间复杂性和通信复杂性,有如下结论:定理11.13RLMAX算法的运算时间为O(p+n/p)。证明因为此算法的运算时间由通信时间和每个处理器求部分元素的极大值操作时间所决定,又由于p个处理器同时启动,所以通信时间是A中极大值通过环网一周的时间,即为O(p),由于求部分元素极大值操作的时间为O(n/p),所以整个算法的运算时间为O(p+n/p)。证毕。定理11.14RLMAX算法的通信复杂性在最好情况下为O(p),在最坏情况下为2O(p),在平均情况下为O(plogp)。证明因为最好情况是,除了极大值外,每个信息只传递一次,即沿传递方向各结点的信息值按由小到大的次序排列,所以总的信息传递数为:p+p-1=2p-1,即为O(p)。最坏情况是沿传递方向各结点的信息值按由大到小的次序排列。不妨设M1>M2>⋯p2>Mp,因此信息Mi传递的次数为p-i+1,所以总的信息传递次数为∑(p-i+1)=O(p)。i=1最后求在平均情况下信息传递次数。令P(gi,k)为第i小信息gi须传递k次的概率,因此它就是沿传递方向有连续k-1个近邻的部分极大值小于gi,同时gi的第k个近邻的部分极大值大于gi的概率。因为部分极大值小于gi的结点数为i-1,大于gi的节结点数为p-i,所以i-1k-1p-iP(gi,k)=·p-1p-kk-1\n306计算机算法基础因为信息为极大值时,它传递p次,所以只考虑p-1个信息。又由于它们每一个至多传递p-1次,所以第i小信息gi被传递的次数的数学期望为p-1Egi=∑k·P(gi,k)k=1对于所有的信息,其期望的传递数为p-1p-1E=p+∑∑k·P(gi,k)i=1k=1111=p(1+++⋯+)23p其中,调和级数的部分和为c+logp,所以信息平均传递数为O(plogp)。证毕。如果去掉对集合A的限制条件,显然RLMAX算法是不正确的。对于集合A中存在相同元素的情况,如何求A中元素的极大值,则留作习题。11.6.3网络生成树的并行算法对于MIMD异步通信计算模型,其通信网络可以看作是一个无向连通图。在此模型中,从某个结点把一个消息广播到整个网络中,有两种常见的方法:一种称之为洪水淹没法,这种方法的通信开销非常大;另一种是基于网络的一棵生成树进行广播。显然,它的信息交换次数等于网络中结点个数减去1。不难证明,这是网络中进行广播的最小通信开销,因此,该方法是网络中进行信息传递的好方法。利用网络的生成树进行广播,必须首先在网络中建立一棵网络生成树。由此可见,建立网络生成树的问题是MIMD异步通信计算模型中的一个极其重要而又非常基本的问题。求网络生成树的并行算法的基本思想是:首先在网络中任意选定一结点s作为树的根,然后s向它的邻接结点发送访问消息“V”。当一个结点m首次收到消息“V”后,把发送者当作自己的父结点F(m);若一个已访问过的结点收到消息“V”后,就给发送者回送一个应答消息“A”。同时,若m除父结点外还有其它邻接结点,则向这些邻接结点发送消息“V”。若m没有其它的邻接结点,则向父结点回送应答消息“R”。当根结点收到它所有邻接结点的应答消息后,向所有儿子结点发送消息“T”并终止其算法的执行。一个结点收到消息“T”后,若其是非叶子结点,则向儿子结点发送消息“T”并终止其算法;若是叶子结点,则终止其算法。算法的非形式化描述如下:procedureNST根结点s的算法:(1)向它的所有邻接结点发送消息“V”;(2)当收到消息“V”后,向发送者发应答消息“A”;(3)当收到来自某一邻接结点m的消息“R”后,则把结点m当其子结点即:S(s)∪{m}→S(s);∥S(s)是结点s的一个信息域,用以保存其子结点名∥(4)当收到全部邻接结点回送的消息“A”或消息“R”后,则向全部邻接结点发送消息“T”并且结束算法。非根结点t的算法:(1)当收到某一邻接结点m发送的消息“V”后,则\n第11章并行算法307①若结点t首次收到消息“V”,则认结点m为其父结点,并且在结点t的邻接表中去掉结点m。然后判定邻接表是否为空,若为空则向父结点m发消息“R”;若不为空,则向表中结点发消息“V”;②若结点t不是首次收到消息“V”,则向结点m发消息“A”。(2)当收到某一邻接结点m发送的消息“R”后,则①认m为其子结点;②若收到了它的全部邻接结点(除父结点外)发送的消息“A”或消息“R”,则向父结点发送消息“R”。(3)当收到某一邻接结点m发送的消息“A”后,结点t检查它的所有邻接结点(除父结点外)是否都已向t发送了消息“A”或“R”,若是,则向其父结点发送消息“R”。(4)当收到某一邻接结点m发送的消息“T”后,则结点t向其子结点发送消息“T”然后结束其算法。endNST算法正确性的简要说明:证明NST算法的正确性,关键是要验证下面4点:(1)对于除根结点s外,所有结点有且仅有唯一父结点;(2)算法能判定出叶子结点;(3)每个结点上的算法都能结束;(4)算法的认子结点过程正确。对于第(1)点,由于消息“V”从结点s开始,传遍所有结点,并且任一结点(不等于)只有当它首次收到消息“V”后才认父,所以除结点s外,每个结点有且仅有唯一父结点。对于第(2)点,算法是根据一结点t能否收到其全部邻接结点(除父结点外)发送的“A”信息来判别结点t是否是叶子,不难验证,这一条件正是判定一结点是否是叶子结点的充要条件,因此,算法能正确判定出叶子结点。对于第(3)点,任意结点t上的算法是否结束是根据以t为根的子树是否已完全形成来判定的。在算法中,一旦判定一结点是叶子,就可结束此结点上的算法,当一结点t为根的子树形成后,就向其父发送消息“R”,因此,任意一个结点,当收到它的全部邻接结点(除父结点外)发送来的消息“R”或消息“A”后,说明此结点为根的子树已完全形成。这里,消息“R”是由底向上,一级级传递的,当根结点s判定出树已形成后,再由顶向下发送消息“T”,通知各个结点结束其算法,因此每个结点上的算法都能结束;对于第(4)点,算法是根据消息“R”来认儿子的,由第(3)点的验证过程,不难看出算法的认儿过程是正确的。关于NST算法的通信复杂性和时间复杂性,有如下结论。定理11.15NST算法的通信复杂性为O(m),其中m为网络中通信链的条数。证明对于网络中每条通信链,不难验证消息“V”至多通过二次;消息“R”至多通过一次;消息“A”至多通过二次;消息“T”至多通过一次。因此,整个算法至多发送6m条消息,即算法的通信复杂性为O(m)。证毕。定理11.16NST算法的时间复杂性为O(n),其中n为网络中结点的个数。证明由算法本身可以看出,其操作时间由发送消息“V”“,R”“,A”和“T”的时间和判别邻接表中所有结点(除父结点外)是否都发送来消息“R”和“A”的时间开销所决定。因为网络中每个结点的度数有限,因此算法的操作时间为O(1)。设从起点到最远距离的结点间的长度为l,则算法的通信时间为O(l),因为在最坏情况下l=n,因此算法的时间复杂度为O(n)。证毕。\n308计算机算法基础习题十一11.1当处理机个数N不为2的幂时须对BROADCAST算法进行修改,证明修改后算法的运算时间和性能保持不变。11.2用形式化描述方式设计在SIMD-CREW模型上的并行归并分类算法。11.3分析在SIMD-EREW模型上的并行归并分类算法的运算时间及算法性能。11.4在SIMD共享存储模型上将串行插入分类算法并行化,并进行算法分析。11.5指出在Connected-Components算法中哪些语句存在读冲突。11.6在SIMD-CREW模型上设计连通图的宽度优先并行搜索算法,并进行算法分析。11.7在SIMD一维线性模型上设计几个数的并行求和算法,并进行算法分析。11.8在SIMD超立方模型上设计并行求极值算法,并分析算法的运算时间和性能。11.9设A,B分别是m×k和k×n矩阵,在MIMD共享存储模型上设计求矩阵C=A×B的并行算法,并对算法进行算法分析。11.10对于S={18,35,21,24,29,13,33,17,31,27,15,28,11,22,19,25,34,32,16,12,23,30,26,14,20},假定N=5,k=6,请用图表示二次取中并行选择算法的执行过程。11.11在PQS算法中,证明当n≥3时,其运算时间T(n)满足:nT(n)≤Cnlogn+2T4其中n是元素个数,C是一个常数。11.12对于序列S={20,15,24,11,17,22,13,19,16,25,12,21,26,18,23,14},模拟PQS算法的执行过程。11.13修改TCMAX算法,使之适合于一般的树形网络结构模型,并对修改后的算法进行性能分析。11.14当集合A中容许有相同元素时,编写在环形结构中求集合A中元素极大值的单向传递算法,并分析算法的时间复杂性和通信复杂性。\n参考文献1EHorowitz,SSahni.FundamentalsofComputerAlgorithms.NewYork:ComputerSciencePress,19782DEKnuth.TheArtofComputerProgramming,Vo13.London:Addison-WesleyPublishingCompany,19733SEGoodman.S.T.Hedetniemi.IntroductiontoTheDesignandAnalysisofAlgorithms.NewYork:McGraw-HillPublishingCompany19774A.V.Aho,JEHopcroft,JDUllman.TheDesignandAnalysisofComputerAlgorithms.London:Addi-son-WesleyPublishingCompany,19745游兆永.线性代数与多项式的快速算法.上海:上海科学技术出版社,19806洪帆.离散数学基础.武汉:华中工学院出版社,19837马仲蕃,魏权龄,赖炎连.数学规划讲义.北京:中国人民大学出版社,19818E.Horowitz.S.Sahni.FundamentalsofDataStructures.NewYork:ComputerSciencePress,Pitman,19769SaraBaase.ComputerAlgorithms.IntroductiontoDesignandAnalysis.London:Addison-WesleyPub-lishingCompany,197810K.Hwang,F.A.Briggs.ComputerArchitectureandParallelProcessingNewYork:McGraw-Hill,PublishingCompany,198411N.Deo,C.Y.Pang,R.E.Lord.TwoParallelAlgorithmsforShortestPathProblems.Int'lConf.onParallelProcessing.1980.244~25312E.F.Moore.TheShortestPathThroughaMaze.Proc.Int’lSymp.onTheoryofSwitching,2,1959,285~29213E.Chang,R.Roberts.AnImprovedAlgorithmforDecentralizedExtrema-FindinginCircularConfigu-rationsofProcesses,Cornm.ACM,22(5),1979,281~28314陈国良.并行算法———排序和选择.合肥:中国科学技术大学出版社,199015唐策善,梁维发.并行图论算法.合肥:中国科学技术大学出版社,1991