【带源码】使用QT和CPLEX完成旅行商问题(TSP)模型求解和可视化

描述

读研期间主要方向为模型的求解和启发式算法,每次使用C++调用CPLEX后的结果都需要用python再画图可视化,非常麻烦。但是Qt的界面和操作都比较简单,容易上手,就使用QT完成旅行商问题可视化的部分。对于需要使用C++调用CPLEX和想要学习Qt的同学们都可以学习一下。

环境

1.CPLEX12.9
2.Qt 5.14.2 (MSVC2017)
3.windows10

文件列表

1.mainwindow.h,mainwindow.cpp
2.tsp_cplex.h,tsp_cplex.cpp
3.main.cpp

配置环境

cplex和Qt的安装不在这里展开,可以在其它地方博客中搜索。
新建工程文件后,在.pro文件中需要加入以下路径

INCLUDEPATH +="D:/Program Files/CPLEX/concert/include"
INCLUDEPATH += "D:/Program Files/CPLEX/cplex/include"
DEFINES += IL_STD
DEFINES += _CRT_SECURE_NO_WARNINGS
QMAKE_LFLAGS_RELEASE += /OPT:REF /OPT:ICF
QMAKE_LFLAGS_DEBUG += /OPT:REF /OPT:ICF
LIBS += "D:/Program Files/CPLEX/concert/lib/x64_windows_vs2017/stat_mdd/concert.lib"
LIBS += "D:/Program Files/CPLEX/cplex/lib/x64_windows_vs2017/stat_mdd/ilocplex.lib"
LIBS += "D:/Program Files/CPLEX/cplex/lib/x64_windows_vs2017/stat_mdd/cplex1290.lib"

其中INCLUDEPATHLIBS的路径按照电脑实际cplex安装的目录填写。

问题描述和模型

旅行商问题又叫TSP问题,是一类非常经典的运筹学问题,问题描述为是旅行家要旅行n个城市,要求各个城市经历且仅经历一次然后回到出发城市,并要求所走的路程最短。这个很多博客都讲过。具体的目标函数和约束条件都对应于代码,这里的子环约束使用的是Miller-Tucker-Zemlin模型,对于编程来说较容易,但是效率是比不过(DFJ)模型的。

  • 模型如下:

最小总路程时间S,其中 i i i j j j指的是两个不同的城市, c i j c_{ij} cij表示两个城市间的距离或成本:

m i n S = ∑ i = 0 n c i j ∗ x i min \quad S=\sum_{i=0}^n c_{ij}*x_i minS=i=0ncijxi
s u b j e c t t o subject \quad to subjectto
∑ i = 0 n x i = 1 , ∀ j ( 1 ) \sum_{i=0}^n x_i=1, \forall j\quad\quad\quad(1) i=0nxi=1j(1)
∑ j = 0 n x j = 1 , ∀ i ( 2 ) \sum_{j=0}^n x_j=1,\forall i\quad\quad\quad(2) j=0nxj=1i(2)
u i − u j + ( n – 1 ) ∗ x i j < = n − 2 , ∀ i , j ( 3 ) u_i-u_j+(n–1)*x_{ij}<=n-2,\forall i,j\quad(3) uiuj+n–1xij<=n2ij(3)
其中约束(1)和(2)分别表示任何一个点只能有一条边进或出,约束3是MTZ约束,消除子环的。

代码部分

main函数

这里是Qt creator自动生成的,我也放上来吧

  • main.cpp的代码如下:
#include "mainwindow.h"
#include "ilcplex/ilocplex.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

Qt界面的代码

mainwindow类的代码主要完成界面的取点画点,以及将结果绘制在图上的操作

  • mainwindow.ui的文件是拖出来的,这里直接放图片
    在这里插入图片描述

  • mainwindow.h的代码如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtWidgets/QMainWindow>
#include<qpainter.h>
#include<QMouseEvent>
#include<qdebug.h>
#include<qfile.h>
#include<qfiledialog.h>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void mousePressEvent(QMouseEvent* event) override;
    void paintEvent(QPaintEvent* event) override;
private:
    Ui::MainWindow *ui;
    bool solve_d = false;//定义没有解决
    bool action = false;
    QImage m_Image;
    QVector<int> path;
    QVector<QPoint> points;
private slots:
   void on_click_solve(); //
   void on_click_clear();

   void on_actionopen_triggered();


};
#endif // MAINWINDOW_H

  • mainwindow.cpp的代码如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "tsp_cplex.h"
#define M_PI       3.14159265358979323846   // pi
#include <qdebug.h>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow),solve_d(false)
{
    ui->setupUi(this);
    setPalette(QPalette(Qt::white));
    setAutoFillBackground(true);
       // resize(300, 300);
    connect(ui->pushButton_2, SIGNAL(clicked()), this, SLOT(on_click_solve()));
    connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(on_click_clear()));
    ui->label2->installEventFilter(this); //这行不能省
    ui->label2->setScaledContents(true);


}
MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::mousePressEvent(QMouseEvent *event)
{

    if (event->button() == Qt::LeftButton)
     {
         points.append(event->pos());
         //update();
    }
    update();
}

void DrawLineWithArrow(QPainter& painter, QPen pen, QPoint start, QPoint end)
{
    painter.setRenderHint(QPainter::Antialiasing, true);

    qreal arrowSize = 20;
    painter.setPen(pen);
    painter.setBrush(pen.color());

    QLineF line(end, start);

    double angle = std::atan2(-line.dy(), line.dx());
    QPointF arrowP1 = line.p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,
        cos(angle + M_PI / 3) * arrowSize);
    QPointF arrowP2 = line.p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,
        cos(angle + M_PI - M_PI / 3) * arrowSize);

    QPolygonF arrowHead;
    arrowHead.clear();
    arrowHead << line.p1() << arrowP1 << arrowP2;
    painter.drawLine(line);
    painter.drawPolygon(arrowHead);
}

void MainWindow::paintEvent(QPaintEvent *event)
{

    QPainter painter(this);

    painter.setPen(QPen(Qt::red, 10));  // 设置点的大小
    for (QPoint& point : points) {
        //painter.drawPoint(point);
        painter.drawPoint(point);
    }

    if (solve_d) {

        QPen Pen = QPen(QColor(18, 157, 221), 2, Qt::SolidLine);
        for (auto i = path.begin(); i < path.end() - 1; i++)
            DrawLineWithArrow(painter,Pen,points[*i], points[*(i+1)]);

        DrawLineWithArrow(painter, Pen, points[*(path.end() - 1)], points[*path.begin()]);
    }
}

void MainWindow::on_click_solve()
{
    tsp_cplex solver(points);
    solver.solve();
    double value = solver.cost;
    path = solver.minPath;
    ui->lineEdit->setText(QString::number(value));
    solve_d = true;
    update();
}
void MainWindow::on_click_clear()
{
    solve_d = false;

    points.clear();
    update();
}

void MainWindow::on_actionopen_triggered()
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Open Image"), QDir::homePath(), tr("(*.jpg)\n(*.bmp)\n(*.png)"));   //打开图片文件,选择图片
    QImage *myimage=new QImage();
    myimage->load(filename);
    myimage->scaled(ui->label2->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);//放缩图片,以固定大小显示
    m_Image=*myimage;
    ui->label2->setPixmap(QPixmap::fromImage(*myimage));
}

主要模型文件的代码

test_cplex的代码主要完成对界面点的处理,以及调用cplex生成结果的过程

  • test_cplex.h的代码如下:
#ifndef TSP_CPLEX_H
#define TSP_CPLEX_H
#include <ilcplex/ilocplex.h>
#include<qdebug.h>

typedef IloArray<IloBoolVarArray> BoolVarMatrix;//二维bool变量
typedef IloArray<IloIntVarArray> IntVarMatrix;//二维整型变量
typedef IloArray<IloNumVarArray> NumVarMatrix;//二维连续性变量
typedef IloArray<IloIntArray> IntMatrix;//二维整型常量
typedef IloArray<IloNumArray> NumMatrix;//二维连续常量


class tsp_cplex
{
public:
    tsp_cplex(QVector<QPoint>);
    void solve();
    void show();
    QVector<int> minPath;
    double cost = 0;

private:
    QVector<QPoint> m_points;
};

#endif // TSP_CPLEX_H
  • test_cplex.cpp的代码如下:
#include "tsp_cplex.h"
#include<iostream>
using namespace std;
ILOSTLBEGIN
tsp_cplex::tsp_cplex(QVector<QPoint> t)
{
    m_points = t;
}

void tsp_cplex::solve()
{
    for (QPoint& point : m_points) {
            cout << point.x() << "  " << point.y() << endl;
        }
        IloEnv env;
        IloModel model(env);
        const int N_CITIES = m_points.size();
        //定义二维决策变量
        BoolVarMatrix x(env,N_CITIES );
        IloIntVarArray u(env,N_CITIES);
        for (int i = 0; i < N_CITIES; i++)
        {
            x[i] = IloBoolVarArray(env, N_CITIES);
            for (int j = 0; j <N_CITIES; j++)
            {
                x[i][j] = IloBoolVar(env);
            }
        }

        for (auto i = 1u; i < N_CITIES; ++i)
        {
            u[i] = IloIntVar(env,2,N_CITIES);
        }

        float** d = new float* [N_CITIES];
        for (int k = 0; k < N_CITIES; ++k)
        {
            d[k] = new float[N_CITIES];
            memset(d[k], 0, sizeof(float) * N_CITIES);

        }

        //计算客户之间的距离
        for (int i = 0; i < N_CITIES; i++) {
            for (int j = 0; j < N_CITIES; j++) {
                if (i == j) {
                    d[i][j] = 0;
                }
                else {
                    d[i][j] = floor(sqrt((m_points[i].x() - m_points[j].x()) * (m_points[i].x() - m_points[j].x())
                        + (m_points[i].y() - m_points[j].y()) * (m_points[i].y() - m_points[j].y())) * 10) / 10;
                }
            }
        }

        for (int i = 0; i < N_CITIES; i++) {
            cout << endl;
            for (int j = 0; j < N_CITIES; j++) {
                cout << d[i][j]<<  '\t';

            }
        }

        IloExpr sum_xij(env);
        for (int j = 0; j < N_CITIES; j++) {
            sum_xij.clear();
            for (int i = 0; i < N_CITIES; i++) {
                sum_xij += (x[i][j]);
            }
            model.add(sum_xij == 1);
        }

        //约束2 仅有一条边出
        for (int i = 0; i < N_CITIES; i++) {
            sum_xij.clear();
            for (int j = 0; j < N_CITIES; j++) {
                sum_xij += (x[i][j]);
            }
            model.add(sum_xij == 1);
        }

        //不能返回它自己
        for (int i = 0; i < N_CITIES; i++) {
            model.add(x[i][i] == 0);
        }

        //子回路约束
        for (int i = 1; i < N_CITIES; i++) {
            for (int j = 1; j < N_CITIES; j++) {
                if (i == j)continue;
    model.add(u[i] - u[j] + (N_CITIES-1) *x[i][j] <= N_CITIES - 2);
                }

            }
        //目标函数
        IloExpr obj(env);
        for (int i = 0; i < N_CITIES; i++) {
            for (int j = 0; j < N_CITIES; j++) {
                obj += d[i][j] * x[i][j];
            }
        }
        model.add(IloMinimize(env, obj));
        IloCplex cplex(model);

        bool solved = false;
        try {
            // Try to solve with CPLEX (and hope it does not raise an exception!)
            solved = cplex.solve();
        }
        catch (const IloException& e) {
            std::cerr << "\n\nCPLEX Raised an exception:\n";
            std::cerr << e << "\n";
            env.end();
            throw;
        }

        if (solved) {
            // If CPLEX successfully solved the model, print the results
            std::cout << "\n\nCplex success!\n";
            std::cout << "\tStatus: " << cplex.getStatus() << "\n";
            std::cout << "\tObjective value: " << cplex.getObjValue() << "\n";
        }
        else {
            std::cerr << "\n\nCplex error!\n";
            std::cerr << "\tStatus: " << cplex.getStatus() << "\n";
            std::cerr << "\tSolver status: " << cplex.getCplexStatus() << "\n";
        }
        cost = cplex.getObjValue();
        
        auto current_vertex = 0;
        auto starting_vertex = 0;

        do {
            std::cout << current_vertex << "---->";
            minPath.append(current_vertex);
            for (auto i = 0u; i < N_CITIES; ++i) {
                if (cplex.getValue(x[current_vertex][i]) > .5) {
                    current_vertex = i;
                    break;
                }
            }
        } while (current_vertex != starting_vertex);

        std::cout <<starting_vertex;

        env.end();

    return;
}

效果展示

可以通过直接在屏幕中取点,然后点击 计算。得到最优路径,结果在输入框中显示。放大缩小我也忘了是干嘛的了- 。-,懒得细看,清空可以直接清空当前点
在这里插入图片描述

结语

代码均为刚刚毕业时候写的,写的很丑后面也没精力优化。如今已经毕业,希望自己能给有需要的同学们一些帮助。

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
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]))) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值