DFJ模型易於理解,可拓展性強,但純DFJ模型需要一次性求解節點的所有子集,需要用到回溯法,相對效率較低。當節點爲n時,所需的子集合規模達2^n-2n-2個,具有指數級別複雜度。當節點個數爲101個時,基本卡着不動了。
下面是Java調用CPLEX解決TSP問題的DFJ模型。這裏較爲精巧的是遞歸求解子集,在我另一篇博文中有所介紹,此處不再細解。博文鏈接如下:https://blog.csdn.net/u011561033/article/details/95493064
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import ilog.concert.*;
import ilog.concert.IloCopyManager.Check;
import ilog.cplex.*;
public class Main {
public static int _cityNum;
public static double[][] _cityDis;
public static ArrayList<Integer> _set;
public static ArrayList<ArrayList<Integer>> _setPool;
public static ArrayList<Integer> _setCG;// for constraint generation
public static ArrayList<ArrayList<Integer>> _setCGPool;// for constraint
// generation
public static long _time;
public static double _ansLength;
public static double[][] _ansCity;
public static void main(String[] args) {
String filename = "E:\\JAVA\\TSP1\\data\\gr120.tsp";
// 讀取標準化後的文件
ReadData(filename);
// 運行算法
DFJsolve();// DFJ方法
// 輸出結果
WriteAns(filename + ".ans");
// 檢查是否滿足約束
// checkOpt();
System.out.println("finish");
}
// DFJ方法解決
public static void DFJsolve() {
try {
_ansCity = new double[_cityNum][_cityNum];
IloCplex cplex = new IloCplex();
IloIntVar[][] x = new IloIntVar[_cityNum][_cityNum];// 0-1變量
// 設定變量取值範圍
for (int i = 0; i < _cityNum; i++) {
for (int j = 0; j < _cityNum; j++) {
if (i != j)
x[i][j] = cplex.intVar(0, 1);
else
x[i][j] = cplex.intVar(0, 0);
}
}
// 目標函數
IloLinearNumExpr tempObj = cplex.linearNumExpr();
for (int i = 0; i < _cityNum; i++) {
tempObj.add(cplex.scalProd(x[i], _cityDis[i]));
}
cplex.addMinimize(tempObj);
// 添加約束 1 和 2
for (int i = 0; i < _cityNum; i++) {
IloLinearIntExpr constraint2 = cplex.linearIntExpr();
for (int j = 0; j < _cityNum; j++) {
constraint2.addTerm(x[j][i], 1);
}
cplex.addEq(cplex.sum(x[i]), 1);
cplex.addEq(constraint2, 1);
}
// 添加約束 3
// 設置點的子集,個數從2到n-2
for (int size = 2; size <= _cityNum - 2; size++) {
SubSet(size);
System.out.println(size + "點子集求解完畢,一共" + _setPool.size() + "個集合");
while (!_setPool.isEmpty()) {
_set = _setPool.get(0);
IloLinearNumExpr expression = cplex.linearNumExpr();
for (int i = 0; i < _set.size(); i++)
for (int j = i + 1; j < _set.size(); j++) {
expression.addTerm(1, x[_set.get(i) - 1][_set.get(j) - 1]);
expression.addTerm(1, x[_set.get(j) - 1][_set.get(i) - 1]);// imp
}
cplex.addLe(expression, _set.size() - 1);
_setPool.remove(0);
}
}
// 輸出結果
long start = System.currentTimeMillis();
boolean success = cplex.solve();
long end = System.currentTimeMillis();
_time = end - start;
if (success) {
_ansLength = cplex.getObjValue();
for (int i = 0; i < _cityNum; i++)
_ansCity[i] = cplex.getValues(x[i]);
} else
System.out.println("cplex.solve() failed.");
} catch (IloException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 設置點的子集
public static void SubSet(int size) {
_setPool = new ArrayList<ArrayList<Integer>>();
_set = new ArrayList<>();
DoSubSet(1, 0, size);
}
// 遞歸求解子集
public static void DoSubSet(int cur, int cnt, int size) {
if (cnt == size) {
_setPool.add(new ArrayList<>(_set));
return;
}
for (int i = cur; i <= _cityNum; i++) {
_set.add(i);
DoSubSet(i + 1, cnt + 1, size);
_set.remove(_set.size() - 1);
}
}
// 檢查是否滿足條件 繞圈走一遍
public static boolean checkOpt() {
System.out.println("check\n--------------");
_setCGPool = new ArrayList<ArrayList<Integer>>();
boolean[] flag = new boolean[_cityNum];
for (int i = 0; i < _cityNum; i++) {
if (flag[i])
continue;
_setCG = new ArrayList<Integer>();
int st = i;
int cnt = 0;
System.out.println("\n");
while (true) {
System.out.print(st + " ");
_setCG.add(st);
flag[st] = true;
for (int j = 0; j < _cityNum; j++) {
if ((int) (_ansCity[st][j] + 0.00001) == 1) {
st = j;
break;
}
}
cnt++;
if (st == i || cnt >= _cityNum)
break;
}
_setCGPool.add(new ArrayList<Integer>(_setCG));
}
if (_setCGPool.size() == 1) {
System.out.println("\nsuccess");
return true;
} else {
System.out.println("\nfail");
return false;
}
}
// 輸出答案
public static void WriteAns(String filename) {
System.out.print(filename + "\n------------------------------\n");
System.out.printf("rum time(sec)= %-10.2f\n", _time / 1000.0);
System.out.printf("min= %-10.2f\n", _ansLength);
for (int i = 0; i < _cityNum; i++) {
for (int j = 0; j < _cityNum; j++)
System.out.print((int) (_ansCity[i][j] + 0.00001) + " ");
System.out.println();
}
}
// 讀取標準化後的數據
public static void ReadData(String filename) {
File file = new File(filename);
BufferedReader reader = null;
try {
System.out.println("正在讀取" + filename);
reader = new BufferedReader(new FileReader(file));
String tempString = null;
tempString = reader.readLine();
_cityNum = Integer.parseInt(tempString.trim());
_cityDis = new double[_cityNum][_cityNum];
for (int i = 0; i < _cityNum; i++) {
tempString = reader.readLine();
String[] arg = tempString.split(" ");
for (int j = 0; j < arg.length; j++) {
_cityDis[i][j] = Double.parseDouble(arg[j]);
_cityDis[j][i] = _cityDis[i][j];
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
}
}