第7章 分支限界算法

分支限界算法

分支限界法类似于回溯法,是一种在问题的解空间树上搜索问题解的算法。
分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。

分支限界法常以广度优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法:
队列式(FIFO)分支限界法:按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
优先队列式分支限界法:按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
最大优先队列:使用最大堆,体现最大效益优先
最小优先队列:使用最小堆,体现最小费用优先

1、背包问题

在这里插入图片描述
从活结点表中选择下一个活结点作为新的扩展结点,分支限界算法通常可以分为两种形式:
FIFO(First In First Out)分支限界算法
按照先进先出(FIFO)原则选择下一个活结点作为扩展结点,即从活结点表中取出结点的顺序与加入结点的顺序相同。
优先队列分支限界算法
在这种情况下,每个结点都有一个耗费或收益。
根据问题的需要,可能是要查找一个具有最小耗费的解,或者是查找一个具有最大收益的解。

限界函数

利用约束函数和限界函数剪去无效的分支,提高搜索效率。
求解最大值问题时:
维护一个活节点表,从活结点表中选择一个结对作为扩展节点
对扩展节点的每个分支,计算器上界值Bound(i).
如果当前最大目标函数值bestc>=Bound(i),那么结点 i 就不会放入活结点表中个,否则放入。从而完成剪枝操作。

【例】一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如:
阵列
4 10
0234500067
1034560500
2045600671
0000000089
有4个细胞。

在这里插入图片描述
0234500067
1034560500
2045600671
0000000089
【算法分析】
⑴从文件中读入m*n矩阵阵列,将其转换为boolean矩阵存入bz数组中;
⑵沿bz数组矩阵从上到下,从左到右,找到遇到的第一个细胞;
⑶将细胞的位置入队h,并沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为false;
⑷将h队的队头出队,沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为false;
⑸重复4,直至h队空为止,则此时找出了一个细胞;
⑹重复2,直至矩阵找不到细胞;
⑺输出找到的细胞数。
【参考程序】

#include<cstdio>
using namespace std;
int dx[4]={-1,0,1,0},   // x,y 方向上的增量
    dy[4]={0,1,0,-1};
int bz[100][100],num=0,n,m;   //二维数组,存储原始矩阵 
void doit(int p,int q){  //p,q矩阵的行列号
   int x,y,t,w,i;
   int h[1000][2];  //顺序队列,记录入队细胞元素在二维数组中的位置
   num++;  //细胞个数增1
   bz[p][q]=0;  //细胞元素清0
   t=0;w=1;  //队列指针。t队首,w 队尾
   h[1][1]=p;  h[1][2]=q;       //遇到的第一个细胞入队
        do    {
           t++;                                    //队头指针加1
           for (i=0;i<=3;i++){     //沿细胞的上下左右四个方向搜索细胞
                x=h[t][1]+dx[i];
                y=h[t][2]+dy[i];
                if ((x>=0)&&(x<m)&&(y>=0)&&(y<n)&&(bz[x][y])){
                     w++;   
                     h[w][1]=x;  h[w][2]=y;           bz[x][y]=0;
                }                                          //本方向搜索到细胞就入队
          }
      }while (t<w);                             //直至队空为止
}

int main(){
   int i,j; 
   char s[100],ch;
   scanf("%d%d\n",&m,&n);
   for (i=0; i<=m-1;i++ )
     for (j=0;j<=n-1;j++ )
       bz[i][j]=1;                              //初始化
   for (i=0;i<=m-1;i++)    {
       gets(s); 
       for (j=0;j<=n-1;j++)        if (s[j]=='0') bz[i][j]=0;
    }
   for (i=0;i<=m-1;i++)
    for (j=0;j<=n-1;j++)
      if (bz[i][j])        doit(i,j);                                //在矩阵中寻找细胞
   printf("NUMBER of cells=%d",num);
   return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(1)旅行商问题优先队列式分支限界算法的数据结构
#define inf 1000000 //∞
#define NUM 100
int n; //图G的顶点数
int a[NUM][NUM]; //图G的邻接矩阵
int NoEdge = inf; //图G的无边标志
int cc; //当前费用(子路的长度)
int bestc; //当前的最小费用(完整路的长度)
在这里插入图片描述

定义优先队列
priority_queue <node> H;
int minOut[NUM];   	//各个顶点的最小出边费用
int minSum = 0;    	//最小出边费用之和
//计算各个顶点的最小出边费用
int i, j;
for(i=1; i<=n; i++)
{
  int Min = NoEdge;
  for(j=1; j<=n; j++)
    if( a[i][j]!=NoEdge && (a[i][j]<Min || Min==NoEdge))
      Min = a[i][j];
  if (Min==NoEdge)  return NoEdge; 	//无回路
  minOut[i] = Min;
  minSum += Min;
}
初始化
node E;
for(i=1; i<=n; i++)
  E.x[i] = i;
E.s = 1;
E.cc = 0;
E.rcost = minSum;
int bestc = NoEdge;
搜索排列树
while (E.s<n) {    //非叶结点
 if (E.s==n-1) { //当前扩展结点是叶结点的父结点
   //再加2条边构成回路  //所构成的回路是否优于当前最优解
  if (a[E.x[n-1]][E.x[n]]!=NoEdge && a[E.x[n]][1]!=NoEdge 
   && (E.cc+a[E.x[n-1]][E.x[n]]+a[E.x[n]][1]<bestc 
   || bestc==NoEdge))  {
   //费用更小的回路 
   bestc = E.cc+a[E.x[n-1]][E.x[n]]+a[E.x[n]][1];
   E.cc = bestc;
   E.lcost = bestc;
   E.s++;
   H.push(E);
  }
  else delete[ ] E.x; 		//舍弃扩展结点
 }
 else{      //搜索树的内部结点
  for (i=E.s+1; i<=n; i++) //产生当前扩展结点的儿子结点
  if (a[E.x[E.s]][E.x[i]]!=NoEdge)  {   //可行儿子结点
   int cc = E.cc+a[E.x[E.s]][E.x[i]];
   int rcost = E.rcost-minOut[E.x[E.s]];
   //限界函数B(i)
   int B = cc+rcost;
   //子树可能包含最优解
   if(B<bestc || bestc==NoEdge)
   {
    //结点E插入优先队列
	……
   }
  }
  //完成结点扩展
  delete [] E.x;
}

 //队列为空时,搜索结束
 if(H.empty()) break;
 else
 {
  E=H.top(); 	//取下一个扩展结点
  H.pop();
 }
}
if (bestc==NoEdge) return NoEdge; 	//表示无回路
//输出最优解
for(i=1; i<=n; i++)
 printf("%d ", E.x[i]);
printf("\n");
//清空剩余的队列元素
while (!H.empty()) H.pop();
return bestc; 		//返回最优解

在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最佳调度问题是一种经典的优化问题,分支限界法是一种常用的解决优化问题的算法,下面是该算法在解决最佳调度问题时的思路: 1. 确定目标函数和约束条件。 最佳调度问题的目标是使得完成所有任务的时间最小化,约束条件是每个任务的开始时间和结束时间要满足一定的限制条件。 2. 初始化可行解。 根据约束条件,初始化一个可行解,例如将每个任务按照开始时间从小到大排序。 3. 计算当前可行解的目标函数值。 根据目标函数的定义,计算当前可行解的目标函数值。 4. 构造子问题。 将当前可行解分成两个子问题,分别是将第一个任务提前一段时间和将第二个任务提前一段时间。这两个子问题的解空间都是当前可行解的子集。 5. 对子问题进行限界。 对于每个子问题,根据约束条件计算出它们的最早完成时间和最晚完成时间,然后将它们作为限界条件,舍弃不满足限界条件的子问题。 6. 选择下一个子问题。 从剩余的子问题中选择一个具有最小限界值的子问题,作为下一个需要求解的子问题。 7. 重复步骤3-6。 重复执行步骤3-6,直到找到最优解或者发现无解。 以上就是分支限界法解决最佳调度问题的基本思路。在实际应用中,还需要根据具体问题的特点进行一些优化,例如选择合适的限界策略和子问题分解方法等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值