/*//如果j>=wi,则在不装物品i和装入物品i之间做出选择
/*不装物品i的最优值:
m(i+1,j)
/*装入物品i的最优值:
m(i+1,j-wi)+vi
/*所以:
/*m(i,j)=max{m(i+1,j),m(i+1,j-wi)+vi},j>=wi
/*
贪心算法的基本要素:
贪心选择性质和最优子结构性质。
2.贪心法的基本思路:
0-1背包问题与背包问题类似,所不同的是在选择物品
装入背包时,可以选择一部分,而不一定要全部装入背包。
这两类问题都具有最优子结构性质,相当相似。
但是背包问题可以用贪心法求解,而0-1背包问题却不能用贪心法求解。
贪心法之所以得不到最优解,是由于物品不允许分割,因此,无法保证最终能将背包装满,部分闲置的背包容量使背包单位重量的价值降低了。
事实上,在考虑0-1背包问题时,应比较选择物品和不选择物品所导致的方案,然后做出最优解。
由此导出了许多相互重叠的子问题,所以,0-1背包问题可以用动态规划法得到最优解。
在这里就不再用贪心法解0-1背包问题了。
3.回溯算法求解0-1背包问题
1.0-l背包问题是子集选取问题。
一般情况下,0-1背包问题是NP难题。
0-1背包
问题的解空间可用子集树表示。
解0-1背包问题的回溯法与装载问题的回溯法十分类
似。
在搜索解空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。
当
右子树有可能包含最优解时才进入右子树搜索。
否则将右子树剪去。
设r是当前剩余
物品价值总和;cp是当前价值;bestp是当前最优价值。
当cp+r≤bestp时,可剪去右
子树。
计算右子树中解的上界的更好方法是将剩余物品依其单位重量价值排序,然后
依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。
由此得到的价值是
右子树中解的上界。
2.解决办法思路:
为了便于计算上界,可先将物品依其单位重量价值从大到小排序,此后只要顺序考
察各物品即可。
在实现时,由bound计算当前结点处的上界。
在搜索解空间树时,只要其左儿子节点是一个可行结点,搜索就进入左子树,在右子树中有可能包含最优解是才进入右子树搜索。
否则将右子树剪去。
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。
如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。
否则,进入该子树,继续按深度优先的策略进行搜索。
回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。
而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。
这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
2.算法框架:
a.问题的解空间:
应用回溯法解问题时,首先应明确定义问题的解空间。
问题的解空间应到少包含问题的一个(最优)解。
b.回溯法的基本思想:
确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
换句话说,这个结点不再是一个活结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
3.运用回溯法解题通常包含以下三个步骤:
a.针对所给问题,定义问题的解空间;
b.确定易于搜索的解空间结构;
c.以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索;
4.分支限界法求解0-1背包问题
1.问题描述:
已知有N个物品和一个可以容纳M重量的背包,每种物品I的重量为WEIGHT,一个只能全放入或者不放入,求解如何放入物品,可以使背包里的物品的总效益最大。
2.设计思想与分析:
对物品的选取与否构成一棵解树,左子树表示不装入,右表示装入,通过检索问题的解树得出最优解,并用结点上界杀死不符合要求的结点。
效率分析:
动态规划法:
由于函数Knapsack中有一个两重for循环,所以时间复杂度为O[(n+1)x(m+1)].空间复杂度也是O[(n+1)x(m+1)],即O(nm).
回溯法:
由于计算上界的函数MaxBoundary需要O(n)时间,在最坏情况下有
个右儿子结点需要计算上界,所以解0-1背包问题的回溯法算法BackTrack所需要的计算时间为
.
限界分支法:
在使用限界分治法时,就是使用更好的限界剪枝函数使得不必要的解被剔除,但是在最坏情况下的解仍然是和回溯法是相同的。
本算法中也是用到了计算上界的函数MaxBoundary需要O(n)的时间,而且在最坏情况下有
个结点需要计算上界,所以在最坏情况下的时间复杂度仍然为
。
【典型实例4】运动员最佳配对问题
1)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现及效率分析。
分析:
回溯法求解本问题的思路:
假设男运动员已经按照1到n排好序不动,用一个数组w存放配对的女运动员的编号,即第i号男运动员配第w[i]号女运动员,初始时设w[i]=i,然后不断的重新排列w数组,每得到一次排列,就要计算在此排列下的配对总和,若发现比之前的总和大,则更新最优解。
套用排列树框架,做好初始化后开始回溯,关键在于到达叶子节点时,需要计算sum+=p[i][w[i]]*q[w[i]][i],若发现sum比之前的最优值大,则更新最优值和配对顺序,回溯完成后则可得到最大总和及其相应的运动员配对方法。
2)分支限界法求解问题的一般思路,分支限界法求解本问题的思路及其C/C++程序实现及效率分析。
分支限界法求解本问题的思路:
效率分析:
【典型实例5】旅行售货员问题
1)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现及效率分析。
2)分支限界法求解问题的一般思路,分支限界法求解本问题的思路及其C/C++程序实现及效率分析
分析:
回溯法即遍历解空间树法,旅行售货员问题是一个排列树的解空间树,解旅行售货员问题即为遍历一个排列树。
回溯法的基本思路:
(1)回溯法有“通用的解题法”之称。
用它可以系统地搜索一个问题的所有解或最优解。
回溯法是一个既带系统性又带有跳跃性的搜索算法。
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发,搜索解空间树。
(2)算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。
如果肯定不包含,则跳过对以该结点为根的子树的系统性搜索,逐层向其祖先结点回溯(称为剪枝)。
否则,进入该子树,继续按深度优先的策略进行搜索。
(3)回溯法在用来求问题的所有解(或最优解?
)时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。
而在求任一解时,只要搜索到一个解就结束。
这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适合于解一些组合数较大的问题。
本问题的求解思路
1回溯法
旅行售货员问题的解空间是一棵排列树。
对于排列树的回溯搜索与生成1,2,…,n的所有排列的递归算法perm类似。
开始时
,相应的排列树由
的所有排列构成。
在递归算法backtrack中,当i=n时,当前的扩展结点是排列树的叶结点的父结点。
此时算法检测图G是否存在一条从顶点
到顶点
的边和从顶点
到顶点1的边。
如果这两条边都存在,则找到一条旅行售货员回路。
此时算法还需要判别这条回路的费用是否优于当前已经找到的最优回路的费用bestc。
如果是,则必须更新当前的最优值bestc和当前的最优解bestx。
当
时,当前的扩展结点位于排列树的第
层。
图G中存在从顶点
到达顶点
的边时,
构成图G中的一条路径,且当
的费用小于当前最优值时算法进入排列树的第i层;否则,则剪去相应的子树。
算法中用变量cc记录当前路径
的费用。
2分支限界法
分枝限界法的基本思想:
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
问题的解空间树时表示问题解空间的一棵有序树,常见的由子集树和排列树。
在搜索问题的解空间树时,分支限界法与回溯法的主要不同在于它们对当前扩展结点所采用的扩展方式。
在分支限界法中,每一个活结点只有一次机会成为扩展检点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。
在这些儿子结点总,导致不可行的解或者非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后从活结点表中取下一结点成为当前扩展结点,并重复上述扩展过程。
这个过程一直持续到找到所需的解或活结点表空为止。
从活结点表中选择下一扩展结点的不同方式将导致不同的分支限界法。
最常见的有以下两种方式。
(1)队列式(FIFO)分支限界法
队列式分支限界法将活结点表组织成一个队列,并按照队列的先进先出FIFO(firstinfirstout)原则选取下一个结点为当前扩展结点。
(2)优先队列式分支限界法
优先队列式的分支限界法将活结点表组织成一个优先队列,并按照优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。
优先队列中规定的结点优先级常用一个与该结点相关的数值p表示。
结点优先级的高低与p值的大小相关。
最大优先队列规定p值较大的结点优先级较高。
在算法是现实通常用最大堆来实现最大优先队列,用最大堆的removeMax运算抽取堆中下一个结点成为当前扩展结点,体现最大效益优先的原则。
类似的,最小优先队列规定p值较小的结点优先级较高。
在算法实现时通常用最小堆来实现最小优先队列,用最小堆的removeMin运算抽取堆中下一个结点成为当前的扩展结点,体现最小费用优先的原则。
用优先队列式分支限界法解具体问题式,应该根据具体问题的特点确定选用最大优先队列或者最小优先队列表示解空间的活结点表。
考察4城市旅行售货员的例子,如图3-1所示。
该问题的解空间树一棵排列树。
解此问题的队列式分支限界法以排列树中结点B作为初始扩展结点。
此时,活结点队列为空。
由于从图G的顶点1到顶点2,3,4均有边相连,所以结点B的儿子结点C,D,E均为可行结点,它们被加入到活结点队列中,并舍去当前扩展结点B。
当前活结点队列中的队首结点C成为下一个扩展结点。
由于图G的顶点2到顶点3和4有边相连,故结点C的2个儿子结点F和G均为可行结点,从而被加入到活结点队列中。
接下来,结点D和结点E相继成为扩展结点而被扩展。
此时,活结点队列中的结点为F,G,H,I,J,K。
结点F成为下一个扩展结点,其儿子结点L是一个叶结点。
找到了一条旅行售货员回路,其费用为59。
从下一个扩展结点G得到叶结点M,它相应的旅行售货员回路的费用为66。
结点H依次成为扩展结点,得到结点N相应的旅行售货员回路,其费用为25。
这已经时最好的一条回路。
下一个扩展结点时结点I。
以结点I为根的子树被剪去。
最后,结点J,K被依次扩展,活结点队列成为空,算法终止。
算法搜索得到最优值为25,相应的最优解时从根结点到结点N的路径(1,3,2,4,1)。
解同一问题的优先队列式分支限界法用一极小堆来存储活结点表,。
其优先级是结点的当前费用。
算法还是从排列树的结点B和空优先队列开始。
结点B被扩展后,它的3个儿子结点C,D和E被一次插入堆中。
此时,由于E是堆中具有最小当前费用的节点,所以处于堆顶位置,它自然成为下一个扩展结点。
结点E被扩展后,其儿子结点J和K被插入当前堆中,它们的费用分别为14和24。
此时堆顶元素是结点D,它成为下一个结点。
如此,它的两个儿子结点H和I被插入堆中。
此时,堆中含有结点C,H,I,J,K。
在这些结点中,结点H具有最小费用,从而它成为下一个扩展结点。
扩展结点H后得到一条旅行售货员回路(1,3,2,4,1),相应的最小费用为25。
接下来结点J成为扩展结点,由此得到另外一条旅行售货员回路(1,4,2,3,1),相应的费用为25。
此后的扩展结点为K,I。
由结点K得到的可行解费用高于当前最优解。
结点I本身的费用已高于当前最优解。
从而它们都不是最好的解。
最后,优先队列为空,算法终止。
三、问题的求解结果与算法分析
1问题的求解结果
(1)当结点数为N=4,其各个点之间的边的权矩阵为
时,
即其解为(1,4,2,3,1),最优解值为13。
2算法分析
(1)回溯法
如果不考虑更新bestx所需的计算时间,则算法backtrack需要
计算时间。
由于算法backtrack在最坏的情况下可能需要更新当前最优解
次,每次更新bestx需
计算时间,从而整个算法的计算时间复杂性为
。
(2)分支限界法
由于是NP问题,其时间复杂度很高,当相对于回溯法而言,分支限界法剪掉了一些不必要的计算,效率有很大的提高,但是在最坏的情况下可能需要满历所有的结点。
此时的时间复杂度也是很高的。
【典型实例6】n后问题
1)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现及效率分析。
2)LasVegas算法求解问题的一般思路,LasVegas算法求解本问题的思路及其C/C++程序实现及效率分析。
分析:
回溯法一般思路:
在用回溯法搜索解空间树时,通常采用两种策略来避免无效搜索,提高回溯法的搜索效率。
其一是约束函数在扩展结点处剪去不满足约束的子树;其二是用限界函数剪去不能得到最优解的子树(如前面的M点和G点)