一、数学模型
决策变量:两两城市之间是否行走,设为0,1变量
目标函数:总距离最短
约束条件:
- 每个城市只能到达一次
- 无子回路(此处运用Miller-Tucker-Zemlin formulation(MTZ)模型)
解释:
- V为城市点集
- 经过城市点位的结果用二维数组表示,每行每列之和为1时则能使每个城市只能到达一次
- \ {0}表示起点除外即1<i≠j<n
- MTZ约束解释如下所示
其中ui,uj为新的变量,其值不具有任何物理意义,只谈大小。
二、CPLEX求解
2.1 程序结构
2.1.1 主方法运行
- 确定算法计算用时
- 读取文件数据,取得各城市位置
- 调用初始化参数方法和cplex求解方法
- 打印时间结果
2.1.2 读取数据方法
- IO流读取文件数据
- 将数据转化,储存到List集合中
- return数据
2.1.3 初始化参数
距离矩阵、数组、参数等初始化
2.1.4 两点距离计算方法
欧式距离
2.1.5 CPLEX求解方法
- 决策变量声明
- 目标函数声明
- 约束条件声明
- 路线结果输出
2.2 数据集
1 6734 1453
2 2233 10
3 5530 1424
4 401 841
5 3082 1644
6 7608 4458
7 7573 3716
8 7265 1268
9 6898 1885
10 1112 2049
11 5468 2606
12 5989 2873
13 4706 2674
14 4612 2035
15 6347 2683
16 6107 669
17 7611 5184
18 7462 3590
19 7732 4723
20 5900 3561
2.3 java+cplex源码
package test;
import ilog.concert.IloException;
import ilog.concert.IloIntVar;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* ClassName: Test02
* Package: test
* Description:cplex求解TSP问题
*
* @Author:
* @Create: 2023/6/4 - 16:59
* @Version:
*/
public class Test02 {
List<double[]> locationList;//存储每一个城市的坐标[x,y]的集合
double[] location;//每个城市的坐标
int cityNum;//城市数量
public double[][] distance;//距离矩阵
int startIndex;//出发点
//读取数据
public List<double[]> getLocationList(){
locationList = new ArrayList<>();
File file = null;
FileInputStream fileInputStream = null;
StringBuilder sb = null;
try {
file = new File("D:\\IDEA2022\\IDEA projects\\algorithm implementation\\CplexTest\\data.txt");//创建文件对象
fileInputStream = new FileInputStream(file);//创建文件输入流对象
//读入操作
int len;
sb = new StringBuilder();//创建该string类型的可扩展对象,运用append()读取文本内容
while ((len = fileInputStream.read()) != -1){
sb.append((char)len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileInputStream != null){
fileInputStream.close();//关闭资源
}
} catch (Exception e) {
e.printStackTrace();
}
}
//将读取的数据转到List<double[]> locationList中
String[] data = sb.toString().split("\n");//split("\n")表示文本按行分隔后返回string数组值
for (String s : data){//遍历数组
String[] s1 = s.split(" ");//将data数组中的每一个元素(即文本中的每一行)用空格进行分割
location = new double[]{Double.parseDouble(s1[1]),Double.parseDouble(s1[2])};//存储城市坐标
locationList.add(location.clone());//存储所有城市坐标
}
return locationList;//返回各城市坐标值
}
//初始化参数
public void initVar(){
startIndex = 0;
cityNum = locationList.size();
distance = new double[cityNum][cityNum];
for (int i = 0;i < cityNum;i++){
for (int j = i;j < cityNum;j++){
if (i == j){
distance[i][j] = Double.MAX_VALUE;//同一点(对角线)不可到达,设置距离为无穷大
} else {
distance[i][j] = getDistance(locationList.get(i),locationList.get(j));
distance[j][i] = distance[i][j];
}
}
}
}
//计算两点间的距离
public double getDistance(double[] location1,double[] location2){
double distance = Math.sqrt(Math.pow(location1[0] - location2[0],2) + Math.pow(location1[1] - location2[1],2));
return distance;
}
//cplex求解
public void solver() throws IloException {
IloCplex cplex = new IloCplex();
//决策变量声明
IloIntVar[][] intVars = new IloIntVar[cityNum][cityNum];//两两城市之间是否到达,为0,1变量
for (int i = 0;i < cityNum;i++){
for (int j = 0;j < cityNum;j++){
if (i != j){
intVars[i][j] = cplex.intVar(0,1);
}
}
}
//目标函数声明
IloLinearNumExpr target = cplex.linearNumExpr();
for (int i = 0;i < cityNum;i++){
for (int j = 0;j < cityNum;j++){
if (i != j){
target.addTerm(distance[i][j],intVars[i][j]);
}
}
}
cplex.addMinimize(target);//求目标函数最小值
//约束条件
//约束条件1:每个城市只能到达一次,即决策变量二维数组里,每一行的和为1,每一列的和为1
for (int i = 0;i < cityNum;i++){
IloLinearNumExpr expr_row = cplex.linearNumExpr();//行的表达式
IloLinearNumExpr expr_col = cplex.linearNumExpr();//列的表达式
for (int j = 0;j < cityNum;j++){
if (i != j){
expr_row.addTerm(1,intVars[i][j]);
expr_col.addTerm(1,intVars[j][i]);
}
}
cplex.addEq(expr_row,1);
cplex.addEq(expr_col,1);
}
//约束条件2:MTZ模型,ui - uj + n*xij <= n - 1,0 < i≠j <=n
IloNumVar[] u = new IloNumVar[cityNum];
for (int i = 1;i < cityNum;i++){
u[i] = cplex.numVar(0,Double.MAX_VALUE);
} //该赋值步骤可用IloNumVar[] u = cplex.numVarArray(cityNum, 0, Double.MAX_VALUE)替代
for (int i = 1;i < cityNum;i++){
for (int j = 1;j < cityNum;j++){
if (i != j){
IloLinearNumExpr expr = cplex.linearNumExpr();
expr.addTerm(1,u[i]);
expr.addTerm(-1,u[j]);
expr.addTerm(cityNum,intVars[i][j]);
cplex.addLe(expr,cityNum - 1);
}
}
}
cplex.setOut(null);//取消cplex自带的输出信息
//求解并打印结果
if (cplex.solve()){
List<Integer> bestPath = new ArrayList<>();//储存最佳路径编号
bestPath.add(startIndex);
int index = startIndex;
while (true){
for (int i = 0;i < cityNum;i++){
if (index != i && cplex.getValue(intVars[index][i]) > 1e-06){
//因为cplex.getValue()方法返回的是double值,精度问题,不能写成cplex.getValue(intVars[index][i]) == 1
index = i;
bestPath.add(index);
break;
}
}
if (index == startIndex){
break;
}
}
System.out.println("最短路径为:" + bestPath);
System.out.println("最短路径长度为:" + cplex.getObjValue());
} else {
System.err.println("此题无解");
}
}
//主方法运行
public static void main(String[] args) throws IloException {
long startTime = System.currentTimeMillis();//开始时间(毫秒),该时间返回的是long值
Test02 cplexTSP = new Test02();
cplexTSP.getLocationList();//读取数据
System.out.println("cplex求解器求解tsp问题:" + cplexTSP.locationList.size() + "个城市");
cplexTSP.initVar();//初始化参数
cplexTSP.solver();//cplex求解器求解
System.out.println((System.currentTimeMillis() - startTime) / 1000d + "s");//所花费的时间,1000d指1000是double值,也就是1000.0
}
}