【TSP问题】数学模型与CPLEX求解

文章介绍了如何使用数学模型和CPLEX求解器来解决旅行商问题,目标是最小化路径总距离。文章详细展示了Java代码,包括读取数据、初始化参数、计算两点间距离以及设置CPLEX的决策变量、目标函数和约束条件。代码实现了每个城市只能访问一次的约束,并应用了Miller-Tucker-Zemlin模型防止子回路。最后,文章给出了一个数据集示例和求解过程。
摘要由CSDN通过智能技术生成

一、数学模型

决策变量:两两城市之间是否行走,设为0,1变量

目标函数:总距离最短

约束条件:

  1. 每个城市只能到达一次
  2. 无子回路(此处运用Miller-Tucker-Zemlin formulation(MTZ)模型)
    在这里插入图片描述

解释:

  1. V为城市点集
  2. 经过城市点位的结果用二维数组表示,每行每列之和为1时则能使每个城市只能到达一次
  3. \ {0}表示起点除外即1<i≠j<n
  4. MTZ约束解释如下所示

​ 其中ui,uj为新的变量,其值不具有任何物理意义,只谈大小。
在这里插入图片描述

二、CPLEX求解

2.1 程序结构

2.1.1 主方法运行

  1. 确定算法计算用时
  2. 读取文件数据,取得各城市位置
  3. 调用初始化参数方法和cplex求解方法
  4. 打印时间结果

2.1.2 读取数据方法

  1. IO流读取文件数据
  2. 将数据转化,储存到List集合中
  3. return数据

2.1.3 初始化参数

距离矩阵、数组、参数等初始化

2.1.4 两点距离计算方法

欧式距离

2.1.5 CPLEX求解方法

  1. 决策变量声明
  2. 目标函数声明
  3. 约束条件声明
  4. 路线结果输出

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
    }
}

2.4 结果展示

在这里插入图片描述

TSP(Traveling Salesman Problem,旅行商问题)是一个NP难问题,是一类重要的组合优化问题,其目标是在给定的城市中找到一条最短的路径,使得每个城市都被访问一次且仅访问一次,并返回起点城市。下面是一个TSP问题的模型及cplex求解代码。 模型: 我们将城市表示为节点,将城市之间的距离表示为边。下面是一个TSP问题的模型: - 变量:对于每一对城市i和j,定义二元变量x[i][j],表示是否从城市i到城市j。 - 目标函数:最小化所有城市之间的距离之和,即minimize(sum(d[i][j]*x[i][j])). - 约束条件: - 每个城市只能被访问一次,即对于每个城市i,sum(x[i][j])=1。 - 从每个城市出发,只能到达一个城市,即对于每个城市j,sum(x[i][j])=1。 - 排除子回路,即对于任意的子集S(包含两个或两个以上的城市),满足sum(x[i][j])>=2,其中i和j分别为S中的任意两个城市。 cplex求解代码: 下面是一个使用cplex求解TSP问题的Python代码示例: ```python import cplex import numpy as np # 定义问题数据 n = 5 # 城市数量 cities = np.arange(n) # 城市编号 d = np.random.rand(n,n) # 城市之间的距离 # 创建cplex模型 prob = cplex.Cplex() prob.objective.set_sense(prob.objective.sense.minimize) # 添加变量 x = {} for i in cities: for j in cities: if i != j: x[i,j] = prob.variables.add(names=["x({},{})".format(i,j)], lb=[0], ub=[1], types=["B"]) # 添加约束条件 for i in cities: prob.linear_constraints.add(lin_expr=[cplex.SparsePair(ind=[x[i,j] for j in cities if j != i], val=[1]* (n-1))], senses=["E"], rhs=[1]) for j in cities: prob.linear_constraints.add(lin_expr=[cplex.SparsePair(ind=[x[i,j] for i in cities if i != j], val=[1]* (n-1))], senses=["E"], rhs=[1]) for S in range(2, n): for i in cities: idx = [j for j in cities if j != i] for T in itertools.combinations(idx, S-1): T = list(T) for j in T: idx.remove(j) prob.linear_constraints.add(lin_expr=[cplex.SparsePair(ind=[x[i,j] for j in T+idx], val=[1]* S + [1]* (n-S))], senses=["G"], rhs=[1]) # 设置目标函数 prob.objective.set_linear([(x[i,j], d[i,j]) for i in cities for j in cities if i != j]) # 求解问题 prob.solve() # 输出结果 print("Solution status = ", prob.solution.get_status()) print("Optimal value = ", prob.solution.get_objective_value()) for i in cities: for j in cities: if i != j and prob.solution.get_values(x[i,j]) > 0: print("x({},{}) = {}".format(i,j,prob.solution.get_values(x[i,j]))) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值