分支定界算法
分支定界算法是求解整数规划或者混合整数规划问题的最常用方法之一,其基本思想是将松弛可行域不断分解为较小的区域,并从该区域当中获取最好解;若该区域仍然无法满足变量约束条件,则进一步划分可行域。这一过程为分支过程且分支过程中将会对解进行判断,若劣于当前最好解,该支将被舍去。若找到符合约束条件的整数解,则需要对当前解进行更新,划定问题的界限,称之为定界。
为什么自定义算法
cplex求解器具备求解整数规划的功能,如博客【cplex求解整数规划示例-按行添加】或者【cplex求解整数规划示例-按列添加】,但是为什么还需要自定义分支定界算法呢?原因在于自定义的分支定界算法能够更加适应求解问题的需求,包括但不限于剪枝优化以及结合其他精确求解技术,例如分支定价和分支切割技术。
整数规划问题
由于本文的目的是记录自定义分支定界算法的实现过程,以简易的问题示例进行操作说明,相应整数规划问题的目标值为340,变量分别取值为4和2。
算法流程
(1)确定松弛模型的解;
(2)确定是否含有非整数变量;若有,继续(3);否则继续(6);
(3)任一选择一个非整数变量;
(4)确定变量的分支;左支变量范围设置为小于变量的整数值;右支设置为大于变量的整数加1;其余变量不变;
(5)确定左支和右支模型的解,跳转至(2);
(6)打印结果。
优先队列的使用
在分支定界的实现过程中,建议使用优先队列存储模型结果,便于每一次操作均对最好/坏的模型进行分支操作。
松弛模型
第一次分支
第二次分支
第三次分支
其余分支
算法代码
import ilog.concert.*;
import ilog.cplex.IloCplex;
import java.util.Comparator;
import java.util.PriorityQueue;
//调用cplex实现分支定界算法
//整数规划示例:
//max z=40x_1+90x_2
//9x_1+7x_2<=56
//7x_1+20x_2<=70
//x_1,x_2>=0且为整数
//最优值:x_1=4,x_2=2,z=340
//数据参数定义
class ModelData{
//目标系数
double[] objectiveCoefficient={40,90};
//约束系数
double[][] constraintCoefficient={{9,7}, {7,20}};
//约束值
double[] constraintValue={56,70};
//变量数量
int variableNumber=2;
//约束数量
int constrainNumber=2;
//模型下界
double lowBound=Double.MIN_VALUE;
}
//节点记录类
class Node{
//模型数据
ModelData data;
//模型目标值
double nodeObj;
//模型解
double[] nodeResult;
//构造函数
public Node(ModelData data){
this.data=data;
nodeObj=data.lowBound;
nodeResult=new double[data.variableNumber];
}
//复制节点
public Node nodeCopy(){
Node newNode=new Node(data);
newNode.nodeObj=nodeObj;
newNode.nodeResult=nodeResult.clone();
return newNode;
}
}
//使用cplex求解整数规划
public class BranchAndBoundDemo {
//定义数据
ModelData data;
//定义节点
Node node1,node2;
//当前最好解
double curBest;
//当前最好方案
Node curBestNode;
//定义优先队列
PriorityQueue<Node> queue = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.nodeObj > o2.nodeObj ? -1 : 1;
}
});
//模型对象
IloCplex model;
//模型变量
IloNumVar[] x;
//变量对应的取值
double[] xValue;
//模型目标值
double modelObj;
//构造函数
public BranchAndBoundDemo(ModelData data){
this.data=data;
xValue=new double[data.variableNumber];
}
//模型建立
private void buildModel() throws IloException {
model=new IloCplex();
model.setOut(null);
x=new IloNumVar[data.variableNumber];
for(int i=0;i<data.variableNumber;i++){
x[i]=model.numVar(0,1e15, IloNumVarType.Float,"x["+i+"]");
}
//设置目标函数
IloNumExpr obj = model.numExpr();
for(int i=0;i<data.variableNumber;i++){
obj=model.sum(obj,model.prod(data.objectiveCoefficient[i],x[i]));
}
model.addMaximize(obj);
//添加约束
for(int k=0;k<data.constrainNumber;k++){
IloNumExpr expr = model.numExpr();
for(int i=0;i<data.variableNumber;i++){
expr=model.sum(expr,model.prod(data.constraintCoefficient[k][i],x[i]));
}
model.addLe(expr,data.constraintValue[k]);
}
}
//模型求解
private void solveModel() throws IloException {
if (model.solve()){
modelObj=model.getObjValue();
System.out.println("模型目标值:"+model.getObjValue());
System.out.println("模型变量值:");
for(int i=0;i< data.variableNumber;i++){
xValue[i]=model.getValue(x[i]);
System.out.print(model.getValue(x[i])+"\t");
}
System.out.println();
}
else{
System.out.println("模型不可解");
}
}
//模型解复制到节点
private void modelCopyNode(Node node){
node.nodeObj=modelObj;
node.nodeResult=xValue.clone();
}
//分支定界过程
private void branchAndBoundMethod() throws IloException {
//设置下界
curBest=0;
//建立模型
buildModel();
//求解模型
solveModel();
//将模型结果复制到节点
node1=new Node(data);
modelCopyNode(node1);
//将节点加入队列
queue.add(node1);
//分支次数
int count=1;
//算法过程
while(!queue.isEmpty()){
System.out.println("第"+count+"次分支过程");
//取出队首节点
Node node=queue.poll();
System.out.println("选定模型:"+node.nodeObj);
System.out.println("选定模型方案取值:");
for(int i=0;i< data.variableNumber;i++){
System.out.print(node.nodeResult[i]+"\t");
}
System.out.println();
//节点目标值劣于当前最好值,不再分支
if(node.nodeObj<curBest){
System.out.println("队列长度:"+queue.size());
count++;
continue;
}
else{
//获取非整数变量
int idIndex = -1;
for (int i = 0; i <node.nodeResult.length; i++) {
if (node.nodeResult[i] != (int) node.nodeResult[i]) {
idIndex = i;
break;
}
}
if(idIndex!=-1) {
System.out.println("非整数变量:第" + (idIndex+1) + "个变量的取值为" + node.nodeResult[idIndex]);
}
//无非整数变量,更新解和界
if(idIndex==-1){
System.out.println("获得整数解");
curBest=node.nodeObj;
curBestNode=node;
}
//有非整数变量,进行分支
else{
//左支
node1=chooseBranch(node,idIndex,true);
if(node1!=null && node1.nodeObj>curBest)queue.add(node1);
//右支
node2=chooseBranch(node,idIndex,false);;
if(node1!=null && node2.nodeObj>curBest)queue.add(node2);
}
}
System.out.println("队列长度:"+queue.size());
count++;
}
System.out.println("模型结果:");
System.out.println(curBestNode.nodeObj);
for(int i=0;i< data.variableNumber;i++){
System.out.print(curBestNode.nodeResult[i]+"\t");
}
}
//选择分支
private Node chooseBranch(Node node,int idIndex,boolean leftOrRight) throws IloException {
Node newNode=new Node(data);
//复制节点信息-避免返回空值
newNode=node.nodeCopy();
//设置变量取值范围
setVarsBound(node,idIndex,leftOrRight);
//模型求解
if(model.solve()){
solveModel();
modelCopyNode(newNode);
}
else{
System.out.println("模型不可解");
newNode.nodeObj=Double.MIN_VALUE;
}
return newNode;
}
private void setVarsBound(Node node,int idIndex,boolean leftOrRight) throws IloException {
//设置变量分支-左支
if(leftOrRight){
for(int i=0;i<node.nodeResult.length;i++){
if(i==idIndex){
x[idIndex].setLB(0);
x[idIndex].setUB((int)node.nodeResult[idIndex]);
}
else{
x[i].setLB(node.nodeResult[i]);
x[i].setUB(node.nodeResult[i]);
}
}
System.out.println("非整数变量范围:"+0+"\t"+(int)node.nodeResult[idIndex]);
System.out.println("左支模型:");
System.out.println(model);
for(int i=0;i<node.nodeResult.length;i++){
if(i==idIndex){
System.out.println("变量"+(i+1)+":\t"+(0)+"\t"+((int)node.nodeResult[idIndex]));
}
else{
System.out.println("变量"+(i+1)+":\t"+(node.nodeResult[i])+"\t"+(node.nodeResult[i]));
}
}
}
//设置变量分支-右支
else{
for(int i=0;i<node.nodeResult.length;i++){
if(i==idIndex){
x[idIndex].setLB((int)node.nodeResult[idIndex]+1);
x[idIndex].setUB(Double.MAX_VALUE);
}
else{
x[i].setLB(node.nodeResult[i]);
x[i].setUB(node.nodeResult[i]);
}
}
System.out.println("非整数变量范围:"+((int)node.nodeResult[idIndex]+1)+"\t"+Double.MAX_VALUE);
System.out.println("右支模型:");
System.out.println(model);
for(int i=0;i<node.nodeResult.length;i++){
if(i==idIndex){
System.out.println("变量"+(i+1)+":\t"+((int)node.nodeResult[idIndex]+1)+"\t"+(Double.MAX_VALUE));
}
else{
System.out.println("变量"+(i+1)+":\t"+(node.nodeResult[i])+"\t"+(node.nodeResult[i]));
}
}
}
}
public static void main(String[] args)throws IloException{
ModelData data =new ModelData();
BranchAndBoundDemo lp=new BranchAndBoundDemo(data);
lp.branchAndBoundMethod();
}
}
========================================
今天到此为止,后续记录其他cplex技术的学习过程。
以上学习笔记,如有侵犯,请立即联系并删除!