分枝限界法概述
分枝限界法和回溯法一样,也是一种在问题的解空间树上搜可行解的穷举算法。
其中“分枝”指的是“分枝限界法”搜索可行解采用的策略为广度优先搜索或实现方法和思路类似的代价优先搜索。
广度优先搜索的概念和实现前面已经介绍过很多次了,这里不再做赘述;代价优先搜索的目的是以某种定义下的优先级,按照优先级从高到低的顺序处理所有的结点。
(从低到高和从高到低本质上是一样的,这里以从高到低为例)。
如果状态结点能够按照优先级不严格(可以相等)降低的顺序,在解空间树上按照结点高度从高到低,同高度结点从左到右或从右到左的排列,那我们可以用广度优先搜索来达到代价优先搜索的目的(广度优先搜索的搜索顺序和代价优先搜索的搜素顺序相同)。
但是状态空间树上的状态结点是通过决策联系起来的,决策对于状态优先级相关的属性的变化多样(可能增大,可能减小,可能线性,可能非线性),状态结点按照优先级从高到低的顺序在状态空间树上的出现往往会是跳跃式的,与解空间树的结构无关。
如果策略能够始终保持状态结点到子节点优先级相关的属性递增或递减(即树上的结点从属性数值的角度来看符合堆的性质),我们可以在代价优先搜索中用堆数据结构来存储状态空间中未处理过的结点,处理节点过程中根据可选策略扩展结点加入容器的方法和广度优先搜索相同。
根据堆数据结构的性质,我们每次取堆顶(队首)结点进行处理,即是剩余未处理结点中优先级最高的结点(未加入容器的子孙节点,容器中一定存在一个结点的优先级高于该子孙节点的优先级,所以是未处理中结点中优先级最高的结点),这样一直处理即是按照优先级最高到低的顺序处理所有的状态节点。
(具体原因和用反证法证明,不做赘述)。
“限界”指的是限界函数,和回溯法中的限界函数一样,在广度(代价)优先搜索将被扩展的结点加入到(优先)队列中时,删除掉一些不必要的子节点,从而将选择变得更有效以加速搜索的进程。
个人归纳认为:回溯法=(状态空间树上的)一般深度优先搜索+限界函数,分枝限界法=(状态空间树上的)一般广度(代价)优先搜索+限界函数。
作为两种在状态空间书上搜索可行解的算法,我们一般考虑状态空间树的宽度和深度来选择使用回溯法还是分枝限界法:存在根节点到部分叶子结点的路径过长时,选择分枝限界法避免进入某条过长路径的搜索;如果可选择的决策过多,即树的宽度过大时,选择回溯法来避免扩展状态空间树同一高度过多的状态节点。
如果两种情况同时存在,可以采取限定单次回溯法搜素最大深度的方法来避免回溯法搜素进入过深的分支,不断扩大单次回溯法搜素的最大深度直到某次的回溯法寻找到可行解为止,这种策略称为迭代加深。
如果状态节点的属性满足代价优先搜索的要求,即子节点的优先级都高于父节点的优先级,我们也可以用分枝限界法去寻找特定意义上的最优解。
如果属性值(优先级)按照层次从上到下递减且最优解的“优”指的就是按照层次从上到下递减的优先级,那么以代价优先搜索为模板搜素到的第一个可行解即为我们的最优解,在这种问题背景下不需要进行与当前最优解比较判断能否得到更优解的剪枝函数。
确定可行解(最优解)所进行的决策
即在图(树)的搜索问题中,求解根结点到当前结点的路径,一般有两种通用的策略:
第一种是对每个节点保存根节点到该节点的路径,在扩展时通过根结点到父节点的路径获得根结点到子节点的路径。
这种方法实现起来简单,但是往往会浪费空间和时间。
第二种是在扩展子节点时对每个结点保存它在状态空间上的前驱节点,即父节点,然后通过对父节点的回溯得到该节点到根节点的路径,逆序即是根节点到该节点的路径。
分枝限界法的时间性能
分枝限界法和回溯法本质上都属于穷举法(广度优先搜索和深度优先搜索),在最坏情况下与穷举法的复杂度相同(甚至更糟,因为需要进行限界值的计算和限界函数的判断),实际求解效率基本上由限界函数决定。
求解0/1背包问题
即前面介绍过很多遍的0/1背包问题。
重量小于规定重量的结点通过决策构成解空间树,由于0/1背包问题的决策保证子节点中的价值属性一定大于等于父节点的价值属性,结点的价值属性用于确定结点的优先级,我们可以通过优先队列式分枝限界法,即以代价优先搜索为模板的分枝限界法求解0/1背包问题的最优解。
代价预先搜索求解0/1背包问题,就是在解空间树上以状态节点的价值属性作为优先级高度的标准进行上的代价优先搜索。
限界函数中的左孩子剪枝策略与回溯法中的左孩子剪枝策略一致;上一章节中提到0/1背包的右孩子剪枝相当于固定右子节点后续的策略向左,即将所有还没有选择的物品加入背包来得到一个价值的上界。
实际上这种价值上界的估计是比较粗糙的,因为剩余的物品我们往往无法全部加入到当前的背包中。这种上界估计的方式就相当于固定策略跳跃到右子树最左边的状态节点,没有进行真正的搜索,实际上这个扩展得到的“最左边的状态节点”可能并不满足问题规范的条件,即不是一个满足要求的可行解(实际不存在于解空间树上),上界被估计得过高了。
那如果我们是通过实际搜索,搜索到右子树满足问题条件的最左状态节点呢?即课件上的原做法:
这种价值上界的估计方法在重量小价值高的物品出现在解空间树下层的决策时会出现反例。解决的方案也很简单,就是避免重量小价值高的物品出现在解空间树的下层,或者说避免重量小价值高的物品在物品序列中出现在重量大价值小的物品之后,即对物品序列进行一个预处理:在进行分枝限界法之前,先按照“价值/重量”的值对物品序列进行从大到小的排序。文章来源:https://www.toymoban.com/news/detail-486977.html
对应的队列式分枝限界法求解如下(只介绍一个框架,在能够使用代价优先搜索的情况下一般还是使用代价优先搜索):文章来源地址https://www.toymoban.com/news/detail-486977.html
//队列中的状态结点类型
struct NodeType{
int no;//结点编号
int i;//当前结点在搜索空间中的层次
int w;//搜素到当前结点的总重量
int v;//搜素到当前结点的总价值
int x[MAXN];//搜素到当前状态结点进行过的决策
double ub;//限界函数bound预估的价值上界
};
//计算状态结点e的价值上界
//需要对物品价值/重量的值进行一个排序
void bound(NodeType &e){
//固定后续的决策向左,搜索右子树满足问题条件的最左状态节点
int i=e.i+1;
int sumw=e.w;
double sumv=e.v;
while ((sumw+w[i]<=W)&&i<=n){
sumw+=w[i]; sumv+=v[i]; i++;
}
//如果所有剩余的物品不能全部装入背包,对于不能完全装入背包的物品,采用分块极限的方式估计价值上界
if (i<=n) e.ub=sumv+(W-sumw)*v[i]/w[i];
//如果所有剩余的物品都能装入背包
else e.ub=sumv;
}
//将结点加入容器并通过结点的层次判断可行解,通过比较得到最优解
void EnQueue(NodeType e,queue<NodeType> &qu){
//通过结点的层次判断可行解
if (e.i==n){
if (e.v>maxv){
//保存最优解的信息
maxv=e.v;
for (int j=1;j<=n;j++) bestx[j]=e.x[j];
}
}
else qu.push(e);//非叶子结点进队
}
//队列式分枝限界法求0/1背包的最优解
void bfs(<
到了这里,关于第六章——分枝限界法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!