## 分支定界 (branch and bound) 算法
是一种在问题的解空间树上搜索问题的解的方法。但与回溯算法不同,分支定界算法采用广度优先或最小耗费优先的方法搜索解空间树,并且,在分支定界算法中,每一个活结点只有一次机会成为扩展结点。
除少数整数规划可以用线性规划的单纯形法直接求解外,一般整数规划必需寻找新的求解方法。这里我们介绍全整数规划的分枝定界方法,它也可以用于求解混合整数规划问题和0-1规划问题。下面我们从一个例子出发来讨论它的思路和步骤。
例1 解下面的整数规划问题
![这里写图片描述](https://box.kancloud.cn/2016-07-25_5795bdc030ec6.jpg "")
解:
(1)首先我们注意到问题(4.3)的可行解集为图4-2的阴影部分内的整数格子点组成的集合,暂时不考虑整数限制条件(5)。解相应的线性规划(1)~(4),即(4.3)的松弛问题:
![这里写图片描述](https://box.kancloud.cn/2016-07-25_5795bdc045e4d.jpg "")
用线性规划的方式得到如下的图案
![图 4-2](https://box.kancloud.cn/2016-07-25_5795bdc05ef9c.jpg "")
得最优解
![这里写图片描述](https://box.kancloud.cn/2016-07-25_5795bdc074674.jpg "")
由(1)和(5)可知ILP的最优解
![这里写图片描述](https://box.kancloud.cn/2016-07-25_5795bdc088c2c.jpg "")
,且必为整数,从而可以断言
![这里写图片描述](https://box.kancloud.cn/2016-07-25_5795bdc0a0093.jpg "")
。这一过程称为定界,即给出ILP0问题目标函数最优解z*的下界和上界。
(2)其次我们注意到线性规划(4.4)的解![这里写图片描述](image/56a4e7211a040.gif "")
具有小数,但这两个变量在(4.3)中都必须是整数,那就是说必须把小数部分**划掉**。我们注意到,对!
而言(对!)
也是如此),最终的最优解不会在3和4之间取值,亦即必然有:
这种表达式实际上是将!
在3和4间的小数部分划掉了,把可行域![]
分成了!
和!
,显然这种分法把原来线性规划的解A点**(2.25,3.75)**排除出去了。但没有排除任何整数可行解。这一过程称为**分枝**,即用两个矛盾的约束条件(4.5)分别代入原问题(4.3)形成两个子问题ILP1和ILP2:
解ILP1的松弛问题ILP1得到
解ILP2的松弛问题ILP2得到
(3)修改上下界:从LP1和LP2的解我们知道有
(4)再分枝:
下面我们就是要划掉!
LP1的解中!
的小数部分,增加约束!
对ILP1进一步的分枝,即
求解LP3得:
对于LP4,不难看出,无可行解。
(5)再修改界,此时我们又有!
(6)再分枝,继续对ILP3进行分枝(由于!)
是小数,增限制条件!又得到
求解LP5得到:
求解LP6得到:
至此,所有的子问题都已探明,求解结束。我们得到了ILP0(即原问题)的最优解:
### 分枝定界法的一般步骤如下:
第一步,先不考虑原问题的整数限制,求解相应的松弛问题,若求得最优解,检查它是否符合整数约束条件;如符合整数约束条件,即转下一步。
第二步,定界。在各分枝问题中,找出目标函数值最大者作为整数规划最优值!
的上界记为!
,从已符合整数条件的分枝中,找出目标函数值最大者作为下界,记为![](image/56a4e75f0c1ee.gif "")
。即 ![](image/56a4e760ba596.gif "")
第三步,分枝。根据对变量重要性的了解,在最优解中选择一个不符合整数条件的![](image/56a4e76275d32.gif "")
,令![](image/56a4e76427c09.gif "")
,(![](image/56a4e765d20cb.gif "")
不为整数)则用下列两个约束条件:
![](image/56a4e7678f9d8.gif "")
其中![](image/56a4e76942229.gif "")
表示不超过![](image/56a4e76af08d9.gif "")
的最大整数,分别加入问题形成两个子问题。
第四步,应用对目标函数估界的方法,或对某一分枝重要性的了解,确定出首先要解的某一分枝的后继问题,并解此问题。若所获得的最优解符合整数条件,则就是原问题的解,若不符合整数条件,再回到第二步,并参照第四步终止后继问题。
在上述过程中,要不断应用分枝、定界、估界来进行判断。当我们求解某子问题的松弛问题时,只要出现下列情况之一,该问题就已探明:
1. 松弛问题没有可行解,则原问题也没有可行解;
2. 松弛问题的最优解恰好全取整数,则该最优解也是其对应的子问题的最优解;
3. 松弛问题的最大值小于现有的下界z,则无论其最优解是否取整数值,都将对应的子问题剪枝;
已探明的子问题就不再用分枝了,如果所有的子问题都已探明,则原整数规划的最优解就一定可以求出,或可以判定它无解。
JAVA实现分支定界代码:
~~~
package sy2;
import sy1.*;
/*
* 假设这里所解的整数规划问题的目标函数取的是max
*/
public class FenZhiDingJie {
double A[][]; //原矩阵的系数矩阵
String D[]; //原矩阵的符号矩阵
double b[]; //原矩阵的常数矩阵
public int count=0;
int index=-1;
public double C[];//目标函数的初始系数向量
public double Uz;//目标函数值下界
public double Lz;//目标函数值上界
public double z;//现在的目标函数值
public double X[];//定义最优解
public double zX[];//定义最优解
public double yX[];//定义最优解
public double Z;//定义整数线性规划的最优值
public double Ix[];//定义整数线性规划的最优解
public double As[][];
public double bs[];
public String Ds[];
public double Xs[];
public int lc;
int M,N;
//初始化分支定解法
FenZhiDingJie(double[][] a,double[] B,String[] d,double[] c){
M=B.length;
N=c.length;
A=a;
b=B;
D=d;
C=c;
X=new double[N];
zX=new double[N];
yX=new double[N];
Ix=new double[N];
}
//分支定解过程
public void FZDJ(double[][] a,double[] B,String[] d,double[] c,double x[]){
boolean flag1=true;
boolean flag2=true;
//利用两阶段法解出该整数线性规划的最优值的上界(第一次使用两阶段法)
while(count<10){
if(count==0){
System.out.println("\n第 "+count+" 次迭代");
TwoStepMethod tm=new TwoStepMethod(A,b,D,C);
if(!tm.flag){
break;
}
X=tm.X;
x=X;
Lz=0;
Uz=-tm.z;
System.out.print("该整数线性规划问题对应的松弛线性规划问题的最优解是:x={");
for(int i=0;i<N-1;i++){
System.out.print(tm.X[i]+",");
}
System.out.print(tm.X[N-1]+"}\n");
System.out.println("该整数线性规划问题对应的松弛线性规划问题的最优值是:"+Uz);
index=AllInteger(X,index);
if(index==-1){
System.out.println("该整数线性规划问题的最优解即为它的松弛线性规划问题的最优解,如上所示!");
}
//第一次分支
else{
count++;//跳入分支定解环节
}// else*/
}// if
else{//接下来实现继续的分支定解法
//System.out.println(Lz);
double A1[][]=new double[B.length+1][N];
double yA1[][]=new double[B.length+1][N];
double ytA[][]=new double[B.length+1][N];
double tA[][]=tempA(a,B.length,index);
for(int i=0;i<B.length+1;i++){
for(int j=0;j<N;j++){
A1[i][j]=tA[i][j];
yA1[i][j]=tA[i][j];
ytA[i][j]=tA[i][j];
//System.out.print(ytA[i][j]+" ");
}
//System.out.println();
}
double yb1[]=new double[B.length+1];
double b1[]=new double[B.length+1];
double ytb[]=tempb(B,(int)x[index]+1);
double ztb[]=tempb(B,(int)x[index]);
for(int i=0;i<B.length+1;i++){
b1[i]=ztb[i];
yb1[i]=ytb[i];
//System.out.println(b1[i]+" "+yb1[i]);
}
//左分支
String D1[]=new String[B.length+1];
String ztD[]=LeftD(d);
for(int i=0;i<B.length+1;i++){
D1[i]=ztD[i];
}
String yD1[]=new String[B.length+1];
String ytD[]=RightD(d);
for(int i=0;i<B.length+1;i++){
yD1[i]=ytD[i];
//System.out.print(yD1[i]+" ");
}
TwoStepMethod ztm=new TwoStepMethod(tA,ztb,ztD,c);//左分支利用两阶段法求解
TwoStepMethod ytm=new TwoStepMethod(ytA,ytb,ytD,c);//左分支利用两阶段法求解
if(count==1){
As=yA1;
bs=yb1;
Ds=yD1;
Xs=ytm.X;
}
if(ztm.flag){
double zz=ztm.z;
zX=ztm.X;
index=AllInteger(zX,index);
if(index!=-1){
//System.out.println(DingJie(z,Uz));
if(DingJie(zz,Uz)){
//左分支
System.out.print("第"+count+"次迭代得到一个松弛线性规划的最优解:\nzx={");
for(int i=0;i<N-1;i++){
System.out.print(zX[i]+",");
}// for i
System.out.print(zX[N-1]+"}\n");
System.out.println("对应的最优值是:"+-zz);
count++;
if(-zz<Lz){
flag1=false;
lc=count;
}
}// if
else{
System.out.println("不存在可行解");
break;
}//else
}else{
System.out.print("第 "+count+" 次迭代得到一个可行解:\nzx={");
for(int i=0;i<N-1;i++){
System.out.print(zX[i]+",");
}//for i
System.out.print(zX[N-1]+"}\n");
System.out.println("对应的最优值是:"+-zz);
Lz=-zz;
Z=Lz;//记录可行解对应的目标函数值
Ix=zX;//记录可行解
count++;
//lc=count;
}//else
}// if
else{
flag1=false;
System.out.println(count+++"次迭代无可行解");
}
if(ytm.flag){
double yz=ytm.z;
yX=ytm.X;
index=AllInteger(yX,index);
if(index!=-1){
//System.out.println(DingJie(z,Uz));
if(DingJie(yz,Uz)){
if(DingJie(yz,Uz)){
//右分支
System.out.print("第"+count+"次迭代得到一个松弛线性规划的最优解:\nyx={");
for(int i=0;i<N-1;i++){
System.out.print(yX[i]+",");
}
System.out.print(yX[N-1]+"}\n");
System.out.println("对应的最优值是:"+-yz);
//System.out.println(2);
if(-yz<Lz){
flag2=false;
break;
}
//System.out.println(2);
count++;
}//if
else{
System.out.println("不存在可行解");
break;
}
}
}else{
System.out.println("第 "+count+" 次迭代得到一个可行解:\nyx={");
for(int i=0;i<N-1;i++){
System.out.print(yX[i]+",");
}
System.out.print(yX[N-1]+"}\n");
System.out.println("对应的最优值是:"+-yz);
Uz=-yz;
Z=Uz;//记录可行解对应的目标函数值
Ix=yX;//记录可行解
}
}// if
else{
flag2=false;
System.out.println(count+++"次迭代无可行解");
}
if(flag1){
index=AllInteger(zX,index);
FZDJ(A1,b1,D1,c,zX);
count++;
}else{
break;
}
index=AllInteger(Xs,index);
FZDJ(As,bs,Ds,c,Xs);
break;
}
}//else
}//while
//确定可行解存在性
public boolean DingJie(double z,double Uz){
boolean flag=true;
//首先保证算出的最优解对应的目标函数值位于整数线性规划问题的目标函数值的上下界中
if(z<=Lz || z>=Uz){
flag=true;//表示该分支继续往下分不可能找到一个可行解
}// if
else{
flag=false;
}
return flag;
}
//判断所给向量的元素是否全为整数如果不是返回对应第一个非整数的下标
public int AllInteger(double X[],int index){
boolean flag=true;
for(int i=0;i<X.length;i++){
if(X[i]-(int)X[i]!=0){//表示最优解中存在分数
flag=false;//表示此解不是该整数规划的最优解
index=i;//记录下最优解中非整数解的位置
break;
}// if
}// for
if(flag){
index=-1;
}
return index;
}
//左分支各参数处理过程
public String[] LeftD(String d[]){
String D[]=new String[d.length+1];
//对D做相应的转换
for(int i=0;i<D.length;i++){
if(i<d.length){
D[i]=d[i];
}
else{
D[i]="<";
}
}
return D;
}
//右分支各参数处理过程
public String[] RightD(String d[]){
String D[]=new String[d.length+1];
//对D做相应的转换
for(int i=0;i<D.length;i++){
if(i<d.length){
D[i]=d[i];
}
else{
D[i]=">";
}
}
return D;
}
//每次分支扩展参数过程
public double[][] tempA(double a[][],int m,int index){
double A[][]=new double[m+1][N];
//对原约束条件添加一个限制
for(int i=0;i<m+1;i++){
if(i<m){
for(int j=0;j<N;j++){
A[i][j]=a[i][j];
}// for
}// if
else{
for(int j=0;j<N;j++){
if(j==index){
A[i][j]=1;
}//if
else
{
A[i][j]=0;
}// else
}//for j
}// else
}//for i
return A;
}
public double[] tempb(double B[],int Lx){
double b[]=new double[B.length+1];
//对b做相应的变换
for(int i=0;i<B.length+1;i++){
if(i<B.length){
b[i]=B[i];
}// if
else{
b[i]=Lx;
}// else
}// for i
return b;
}
}
~~~
~~~
package sy2;
import java.util.Scanner;
import sy1.TwoStepMethod;
/*
* 代码测试程序
*/
public class Text {
public static void main(String args[]){
int M=2,N=2;//M是约束条件个数,N是未知数个数
double A[][]=new double[M][N];
double b[]=new double[M];
String D[]=new String[M];//原符号向量
double C[]=new double[N];//原目标函数的系数向量
Scanner S=new Scanner(System.in);
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
A[i][j]=S.nextDouble();
//System.out.print(" ");
}
//System.out.println();
}
for(int i=0;i<M;i++){
b[i]=S.nextDouble();
//System.out.print(" ");
}
for(int i=0;i<M;i++){
D[i]=S.next();
//System.out.print(" ");
}
for(int i=0;i<N;i++){
C[i]=S.nextDouble();
//System.out.print(" ");
}
System.out.println("请输出初始矩阵的方程个数"+M);
System.out.println("请输出初始未知数的个数"+N);
System.out.println("请输出初始矩阵的系数矩阵:");
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
System.out.print(A[i][j]+" ");
}
System.out.println();
}
System.out.println("请输出初始矩阵的常数项:");
for(int i=0;i<M;i++){
System.out.print(b[i]+" ");
}
System.out.println("\n"+"请输出初始矩阵的符号项:");
for(int i=0;i<M;i++){
System.out.print(D[i]+" ");
}
System.out.println("\n"+"请输出目标函数的系数向量:");
for(int i=0;i<N;i++){
System.out.print(C[i]+" ");
}/*
TwoStepMethod tm=new TwoStepMethod(A,b,D,C);
if(tm.flag){
System.out.print("\n请输出通过两阶段法得到的最优解:X={");
for(int i=0;i<N-1;i++){
System.out.print(tm.X[i]+",");
}
System.out.print(tm.X[N-1]+"}\n");
System.out.println("请输出通过两阶段法得到的最优值是:"+tm.z);
}*/
System.out.println("\n\n分支定界法过程:");
double A1[][]=new double[M][N];
for(int i=0;i<N;i++){
for(int j=0;j<M;j++){
A1[i][j]=A[i][j];
}
}
double b1[]=new double[M];
for(int i=0;i<M;i++){
b1[i]=b[i];
}
String D1[]=new String[M];
for(int i=0;i<M;i++){
D1[i]=D[i];
}
double x[]=new double[N];
FenZhiDingJie fzdj=new FenZhiDingJie(A, b, D, C);
fzdj.FZDJ(A1, b1, D1, C,x);
System.out.print("该整型线性规划问题的最优解为X={");
for(int i=0;i<N-1;i++){
System.out.print(fzdj.Ix[i]+",");
}
System.out.print(fzdj.Ix[N-1]+"}");
System.out.println("\n该整型线性规划问题的最优值为:Z="+fzdj.Z);
}
}
~~~