如何选择旅游路线,使得假期旅游路费最少?

简介: 旅行是许多人的热爱,但是在规划一个完美的假期时,找到最经济的路线常常是一个挑战。这里就需要引入一个著名的优化问题——旅行商问题。本文将介绍TSP的基础知识,并使用MTZ消除子环方法优化一个简单的TSP问题的示例。

旅行是许多人的热爱,但是在规划一个完美的假期时,找到最经济的路线常常是一个挑战。这里就需要引入一个著名的优化问题——旅行商问题。本文将介绍TSP的基础知识,并使用MTZ消除子环方法优化一个简单的TSP问题的示例。

旅行商问题简介

TSP,全称为Traveling Salesman Problem,即旅行商问题。它是一个经典的组合优化问题,其目标是找到一条路径,使得旅行商能够访问一组城市,并且总路程最短。

在TSP中,假设有一个旅行商需要访问N个城市,每个城市之间的距离已知。问题的目标是找到一条路径,使得旅行商能够从一个城市出发,最多只能经过所有城市一次(出发城市除外),最终回到出发城市,并且使得总路程最短。

TSP是一个非常重要且具有挑战性的问题,它在实际应用中有广泛的应用,如物流、电路板布线、旅游规划等。然而,由于TSP是一个NP-hard问题,对于大规模的问题,求解最优解是非常困难的,需要使用启发式算法、确切算法等来求解。

解决方案

下面是使用整数线性规划方法的简单描述:

  1. 定义问题:将每个城市视为TSP中的节点,城市之间的距离视为边的权重。
  2. 建立数学模型:使用二进制变量定义节点之间的连接关系,例如x[i,j]表示是否从节点i到节点j的路径。使用p[i,j]表示节点i到节点j的距离。
  3. 构建约束条件:
  • 每个节点只能进入一次:对于每个节点i,约束条件为 ∑ x[i,j] = 1。
  • 每个节点只能离开一次:对于每个节点i,约束条件为 ∑ x[j,i] = 1。
  • 避免子环路:对于每个节点子集S,确保其节点之间的连接关系不构成环路。这可以通过添加额外的约束条件来实现,例如割平面约束或子环路消除约束(本文使用的方法)。
  1. 定义目标函数:目标是最小化总路程。目标函数可以定义为 Minimize ∑(i,j) p[i,j] * x[i,j]。
  2. 使用优化求解器:使用适合您编程语言的优化库或工具(本文使用的是MindOpt 优化求解器和MindOpt APl 建模语言),将模型加载到求解器中,并运行求解器来找到最优解。

一个问题示例

问题描述和数据搜集

小明目前在做一份毕业旅行的规划。打算从城市1出发,分别去如下几个城市:2,3,4,5,6,7,8,9,10,每个城市去一次,然后再回到城市1。

由于经费有限,小明在制定节省大旅游计划,比如住宿便宜青旅、蹭同学寝室、旅游景点门票绕开高峰期,计划里可调整的主要是路费和耗时。

为了简化问题,小明以高铁费用为参考,来计算花费最少的旅行路径。

在12306软件上查询了各个城市之间通行的路费如下:(并登记在一个表格文件 railway_cost.csv里。票价12306会有折扣,因此会出现不一致情况,按感觉的中位数随机选)

上海

杭州

黄山

苏州

南京

千岛湖

景德镇

金华

宁波

舟山

上海

0

73

190

38

135

143

217

147

116

杭州

-

0

110

111

112

62

181

74

71

黄山

-

-

0

230

240

50

71

176.5

苏州

-

-

-

0

94

182

190

182

南京

-

-

-

-

0

190

325

180

201

千岛湖

-

-

-

-

-

0

119

128

景德镇

-

-

-

-

-

-

0

146

金华

-

-

-

-

-

-

-

0

130

宁波

-

-

-

-

-

-

-

-

0

20

舟山

-

-

-

-

-

-

-

-

-

0

查询数据发现,舟山市一个特殊的很多海岛的城市,与外界高铁不方便,最合理的是宁波去再回宁波。

  • 这样会产生一个矛盾:每个城市去一次,对于宁波因为路过会去两次,与去一次冲突。
  • 对于这个问题,解决策略是:舟山必然和宁波相连地去游玩,去掉这个局部小环路。
  • 因此,在建立数学模型的时候,可以将舟山先省略,先排其他的路程。

数学规划建模

下面我们采用数学规划的方法来建模这个旅行商问题。


首先:定义集合,描述数据,方便后面描述时候索引用。

  • 城市集合 image.png
  • 得到两两城市之间有方向的边(线段向量)的集合 image.png ,用 image.png 来表示, image.png 不等于 image.png

高铁票价作为参数:

  • 两地点之间车票价格 image.png

然后:我们定义变量来解决这个问题。

设置变量是数学建模的关键一步,要思考我们可以操控改变的决策,能怎么用数学量化符号来表示。

比如这里我们想要找路径,那么直接可以设置0-1变量记录两两城市之间的路径是否有生效。即

  • 0-1的二进制决策变量 image.png 。如果经过这个路线,则 image.png ;如果没有经过这个点,则 image.png

然后:根据设置的变量,我们来描述结果评估好坏,比如本TSP问题我们用花费来评价,

  • 则我们设置目标函数:最小化总旅行成本: image.png

然后:开始设置约束,来限定变量的取值逻辑,使得符合要求又能找到可行解。

这里比较容易想到的约束是:

  • 保证每个地点有两条路线相连接,其中一条为进入路线,一条为出去路线:
  • 对于任意城市 image.png image.png ,且 image.png

子环问题

这里我们思考这个问题,或者编程尝试运行后会发现:仅有上面这条约束,会导致多个子圈(子回路)的情况,即算出来多个环,如下图示意。我们需要加限制是的只有一个圈环。这个过程称为消除子回路(subtour elimination)。(用户可自行尝试修改后文代码仅有这两条约束时运行结果,了解子环。) image.png

消除子环的约束形式有多种方式,比如MTZ模型(Miller-Tucker-Zemlin subtour elimination constraints)和DFJ模型(Dantzig-Fulkerson-Johnson subtour elimination constraints)。

本文主要介绍MTZ的方法,它的设计很巧妙。

MTZ模型:

首先,我们引入自由变量 image.png 来代表i是路径中的第几个点。

然后引入一个 Big-M,大M法思路,引入一个足够大的数,联合 0-1变量 image.png 来构造一个不等式,如下:

  • image.png ,其中i
  • image.png 时,表示i->j是路径中的一个有向线段,此时 image.png image.png 是路径上相继的两个点,因此 image.png ,上述约束成立;
  • image.png ,右边的值 image.png 足够大,上述公式也成立。

为了更好地估计,这里我们的Big-M取值最好接近理论上界:

  • 由于u_i和u_j的差异最大是城市数量 image.png 减去1,即 image.png
  • 即不等式左边是 image.png , 可取Big-M为 image.png
  • 再将变量都挪到等式左边,整理得到 image.png

此外,需要注意的是,此 image.png
记录的是 image.png 个城市的顺序。去环后是个单向顺序。而实际的我们本例中的TSP问题,是从起点城市 image.png 出发后,最后需要回到起点 image.png

  • 因此,除了城市集合C,我们还另外拓了个城市,序号 image.png ,此序号就是起点城市,其路费的费用与起点城市相同。
  • 与之对应的,前面的进、出城市只有一条的约束,也会因为这个增加的节点,有变化:
  • 城市间线段的结合E增加了N个城市到终点城市的向量
  • 1-N 的城市都只有一条出去的线路
  • 2-N+1的城市都只有一条回来的线路

整理后得到数学公式如下:

image.png

选择工具编程和清理数据

这里我们采用达摩院自研的代数建模语言 MindOpt APL(MAPL)进行编程。方便码代码和更换求解器测试。

为了编程的时候方便,也为了数据能统一表达,

  • 我们将上述的城市进行编号1-9。整理在data/city_list.csv
  • 对于无高铁的,临时用大数值(10000)来代替其价格防止被选,并且将之前的半三角的价格,转置粘贴再相加后,得到价格如文件data/price.csv
  • MAPL目前对于文件形式的二维表格的读入理解支持不好,比较适合复制进代码直接改,或者拉成k-v对的长表更利于读取(文件data/price_longlist.csv)。下文代码给了两种示例。

最后撰写MAPL的代码如下:

clear model;
option modelname TSP_01; #定义存储文件名
# ----------建模--------Start----
# TSP_01.mapl
print "导入数据和参数-------";
## 导入数据------------
# 城市集合---
param fileDir = "data/";
set City = {read fileDir+"city_list.csv" as "<1n>" skip 1}; # 读取城市序号
param CityName[City] = read fileDir+"city_list.csv" as "<1n> 2s" skip 1;  #读取城市名称
param cityNum = card(City);
# 路费参数---
# 输入方式1
param price[City*City] = read fileDir+"price_longlist.csv" as "<1n,2n> 5n" skip 1;  #读取高铁费用
#print price;
#输入方式2
#param price[City*City] =
#    |1,      2,      3,      4,      5,      6,      7,      8,      9|
#|1|  0,    73,   190,  38,   135,  143,  217,  147,  116|
#|2| 73,    0,    110,  111,    112,  62,   181,  74,   71|
#|3|190,    110,  0,    230,  240,  50,   71,   176.5,  10000|
#|4| 38,    111,  230,  0,    94,   182,  10000,  190,  182|
#|5|135,    112,  240,  94,   0,    190,  325,  180,  201|
#|6|143,    62,   50,   182,  190,  0,    119,  128,  10000|
#|7|217,    181,  71,   10000,  325,  119,  0,    146,  10000|
#|8|147,    74,   176.5,  190,  180,  128,  146,  0,    130|
#|9|116,    71,   10000,  182,  201,  10000,  10000,  130,  0|;
#print price;
# 设定起始城市----
param startCity = 1;  #选择对应数字序号的城市为起始城市(startCity),此处可根据需要修改成自己的出发城市
# 其他的城市
set City_internal = City - {startCity}; 
# 引入虚拟城市,实际就是起始城市,对虚拟城市endCity给予ID赋值,
# 比如路径 1->3->4->2->1,最后一个1是虚拟引入的
# 注意:引入虚拟城市,是为了消除TSP中所有的环,否则引入MTZ约束后,问题会不可行。(因为MTZ约束实际上不允许环存在,这也包括了TSP问题的可行解)
param endCity = cityNum + 1; 
#生成边的集合----
# 城市之间的两两互通(有方向) + 除了起始城市外的所有城市去结束城市
set Edge = {<i,j> in City*City with i!=j:<i,j>} + {k in City_internal:<k, endCity>}; 
#print Edge;
print "开始建模-------";
## 定义变量-----------
var u[City] >= 0 ; # MTZ模型需要,代表各个城市是路径中的第几个点
var x[Edge] binary; #这个边是否有
## 目标函数------------
minimize totalCost : 
    sum{<i,j> in Edge with j != endCity} price[i,j] * x[i,j] + 
    sum{k in City_internal} price[k,startCity]*x[k,endCity]; 
# 注:结束节点endCity的路费,用startCity的price来替
## 约束函数------------
# 每个城市离开1次,除结束城市外
subto leaveCityEquals1_: 
    forall {c in City} 
        sum{ <c,j> in Edge} x[c,j] == 1;
# 每个城市进入1次,除起始城市外
subto enterCityEquals1_: 
    forall {c in (City_internal + {endCity})} 
        sum{ <i,c> in Edge} x[i,c] == 1;
# 消除子环
# MTZ模型:
    # 引入自由变量u_i>= 0来代表i是路径中的第几个点。
    # 然后引入一个Big-M,构造 (u_i - u_j) +1 <= M (1- x_ij):
    #  - 当x_ij=1时,表示i->j是路径中的一个有向线段,此时u_i与u_j是路径上相继的两个点,因此 u_i - u_j = -1,上述约束成立;
    #  - 当x_ij =0,右边的值足够大,上述公式也成立。
    # 为了更好地估计,这里我们的Big-M取值最好接近理论上界。 由于u_i和u_j的差异最大是cityNum-1,可取Big-M为cityNum。
    # 整理得到 (u_i - u_j) + cityNum * x_ij <= cityNum -1
subto MTZ_: 
    forall {<i,j> in Edge with j!=endCity } 
        u[i] - u[j] + cityNum * x[i,j] <= cityNum-1;
print "开始求解-------";
## 求解-----
option solver mindopt;     # (可选)指定求解用的求解器,默认是MindOpt
option mindopt_options 'print=0'; #设置求解器输出级别,减少过程打印
#option mindopt_options 'iisfind=1'; # 设置IIS
solve;# 求解
## 打印约束--------在调试时候求解不出时候打印
#print "IIS info:"; 
# 如果想避免人工列举所有约束,也可以使用MAPL的内置关键字_con与_ncons来一次性  遍历全部约束,代码如下:
#for i in 1.._ncons with _con[i].iis > 0: print "{}{}: iisType={}" % _con[i].name, _con[i].index, _con[i].iis;
print "打印结果-------";
#display; #结果太多,省略,需要打印的时候打印
forall {<i,j> in Edge with x[i,j]>=0.5 and j != endCity}
    print "{}号城市{} --> {}号城市{}:{},价格{}"%i,CityName[i],j,CityName[j],x[i,j],price[i,j];
#用起始城市替换结束城市名称
forall {<i,endCity> in Edge with x[i,endCity]>=0.5 } 
    print "{}号城市{} --> {}号城市{}:{},价格{}" % i, CityName[i], startCity, CityName[startCity], x[i,endCity], price[i,startCity];
print "整理后访问的路径是:---------";
forall { y in 1..9 }
    forall {<i> in City with u[i] == y-1 }
        print "第{}个城市是{}号{}" % y, i, if CityName[i] == "宁波" then CityName[i] + ", 之后往返舟山" else CityName[i] end;
        
print "最后回到起始城市是{}号{}"%endCity,CityName[startCity];
forall {<i,j> in Edge with x[i,j]>=0.5 and j != endCity}
    print "{}->{}"% (if CityName[i] == "宁波" then CityName[i] + "->舟山
舟山->宁波
宁波" else CityName[i] end), CityName[j];
forall {<i,endCity> in Edge with x[i,endCity]>=0.5 } 
    print "{}->{}"% (if CityName[i] == "宁波" then CityName[i] + "->舟山
舟山->宁波
宁波" else CityName[i] end), CityName[startCity];
print "总花费是 = {}"% (sum{<i,j> in Edge with j != endCity} price[i,j] * x[i,j] + sum{k in City_internal} price[k,startCity]*x[k,endCity]) + 2 * 20;
# 验证MTZ约束
#forall {<i,j> in City * City with i < j and j >= 2} 
#   print "u[{}]_{} - u[{}]_{} + (cityNum -1) * x[i,j]_{} = {}" % i,u[i],j,u[j],x[i,j],(u[i] - u[j] + (cityNum -1) * x[i,j]);

运行上述代码结果如下:

导入数据和参数-------
开始建模-------
开始求解-------
Running mindoptampl
wantsol=1
print=0
MindOpt Version 1.0.1 (Build date: 20231114)
Copyright (c) 2020-2023 Alibaba Cloud.
Start license validation (current time : 04-FEB-2024 15:05:26).
License validation terminated. Time : 0.005s
OPTIMAL; objective 819.00
Completed.
打印结果-------
1号城市上海 --> 4号城市苏州:1,价格38
2号城市杭州 --> 6号城市千岛湖:1,价格62
3号城市黄山 --> 7号城市景德镇:1,价格71
4号城市苏州 --> 5号城市南京:1,价格94
5号城市南京 --> 2号城市杭州:1,价格112
6号城市千岛湖 --> 3号城市黄山:1,价格50
7号城市景德镇 --> 8号城市金华:1,价格146
8号城市金华 --> 9号城市宁波:1,价格130
9号城市宁波 --> 1号城市上海:1,价格116
整理后访问的路径是:---------
第1个城市是1号上海
第2个城市是4号苏州
第3个城市是5号南京
第4个城市是2号杭州
第5个城市是6号千岛湖
第6个城市是3号黄山
第7个城市是7号景德镇
第8个城市是8号金华
第9个城市是9号宁波, 之后往返舟山
最后回到起始城市是10号上海
上海->苏州
杭州->千岛湖
黄山->景德镇
苏州->南京
南京->杭州
千岛湖->黄山
景德镇->金华
金华->宁波
宁波->舟山
舟山->宁波
宁波->上海
总花费是 = 859

最后如果大家想基于这个问题进行修改,做更多的尝试,可以扫描下方的二维码或者点击这个链接,进入MindOpt Studio 云上建模求解平台中获取(无需下载软件)。

image.png

相关文章
|
达摩院 开发者 容器
「达摩院MindOpt」优化形状切割问题(MILP)
在制造业,高效地利用材料不仅是节约成本的重要环节,也是可持续发展的关键因素。无论是在金属加工、家具制造还是纺织品生产中,原材料的有效利用都直接影响了整体效率和环境影响。
「达摩院MindOpt」优化形状切割问题(MILP)
|
人工智能 达摩院 开发工具
MindOpt不联网License,可直接在阿里云线上购买了
在很多场景里,由于智能决策运行环境不允许联网、网络不稳定、或者需要毫秒级计算决策方案需要节省联网耗时等场景,多用户反馈需要【不联网】的License。
MindOpt不联网License,可直接在阿里云线上购买了
|
达摩院 供应链 安全
光储荷经济性调度问题【数学规划的应用(含代码)】阿里达摩院MindOpt
本文介绍使用MindOpt工具优化光储荷经济性调度的数学规划问题。光储荷经济性调度技术旨在最大化能源利用率和经济效益,应用场景包括分布式光伏微网、家庭能源管理系统、商业及工业用电、电力市场参与者等。文章详细阐述了如何通过数学规划方法解决虚拟电厂中的不确定性与多目标优化难题,并借助MindOpt云建模平台、MindOpt APL建模语言及MindOpt优化求解器实现问题建模与求解。最终案例展示了如何通过合理充放电策略减少37%的电费支出,实现经济与环保双重效益。读者可通过提供的链接获取完整源代码。
|
达摩院 供应链 JavaScript
网络流问题--仓储物流调度【数学规划的应用(含代码)】阿里达摩院MindOpt
本文通过使用MindOpt工具优化仓储物流调度问题,旨在提高物流效率并降低成本。首先,通过考虑供需匹配、运输时间与距离、车辆容量、仓库储存能力等因素构建案例场景。接着,利用数学规划方法,包括线性规划和网络流问题,来建立模型。在网络流问题中,通过定义节点(资源)和边(资源间的关系),确保流量守恒和容量限制条件下找到最优解。文中还详细介绍了MindOpt Studio云建模平台和MindOpt APL建模语言的应用,并通过实例展示了如何声明集合、参数、变量、目标函数及约束条件,并最终解析了求解结果。通过这些步骤,实现了在满足各仓库需求的同时最小化运输成本的目标。
|
达摩院 BI 索引
切割问题【数学规划的应用(含代码)】阿里达摩院MindOpt
本文主要讲述了使用MindOpt工具对切割问题进行优化的过程与实践。切割问题是指从一维原材料(如木材、钢材等)中切割出特定长度的零件以满足不同需求,同时尽可能减少浪费的成本。文章通过实例详细介绍了如何使用MindOpt云上建模求解平台及其配套的MindOpt APL建模语言来解决此类问题,包括数学建模、代码实现、求解过程及结果分析等内容。此外,还讨论了一维切割问题的应用场景,并对其进行了扩展,探讨了更复杂的二维和三维切割问题。通过本文的学习,读者能够掌握利用MindOpt工具解决实际切割问题的方法和技术。
|
达摩院 安全 调度
网络流问题--交通调度【数学规划的应用(含代码)】阿里达摩院MindOpt
本文探讨了如何利用数学规划工具MindOpt解决交通调度问题。交通调度涉及网络流分析,考虑道路容量、车辆限制、路径选择等因素,以实现高效运行。通过建立数学模型,利用MindOpt云平台和建模语言MAPL,设定流量最大化目标并确保流量守恒,解决实际的调度问题。案例展示了如何分配车辆从起点到终点,同时满足道路容量约束。MindOpt Studio提供在线开发环境,支持模型构建和求解,帮助优化大规模交通调度。
|
达摩院 算法 Java
选择优化求解器的关键因素:以MindOpt为例
选择一款适合自己业务需求的求解器我们一般需要考量什么呢?可求解的问题类型?问题规模?本文将介绍一些需要考虑的重要因素,并且介绍阿里达摩院MindOpt优化求解器在这些因素下的表现。
|
存储 达摩院 供应链
排产排程问题【数学规划的应用(含代码)】阿里达摩院MindOpt
**文章摘要:** 本文探讨了使用阿里巴巴达摩院的MindOpt优化求解器解决制造业中的排产排程问题。排产排程涉及物料流动、工序安排、设备调度等多个方面,通常通过数学规划方法建模。MindOpt支持线性规划、整数规划等,能有效处理大规模数据。案例以香皂制造工厂为例,考虑了多种油脂的购买、存储和生产计划,以及价格变化和存储成本。问题通过数学建模转化为MindOpt APL代码,求解器自动寻找最优解,以最大化利润。文章还提供了代码解析,展示了解决方案的细节,包括目标函数(利润最大化)、约束条件(如生产效率、库存管理)以及结果分析。
|
达摩院 算法 决策智能
解决背包问题:组合优化的应用与建模方法
组合优化是数学优化的一支,专注于从有限集合中选取元素的最优化问题。它涉及将一组对象组合在一起,以满足特定条件并优化某个目标函数,即在所有可能的组合中找到最有利的一个。 本文将以一个简化的背包问题为例,来讲解采用数学规划的方法来解决背包这个组合优化问题。
|
达摩院 IDE 开发工具
阿里达摩院MindOpt优化求解器-月刊(2024年5月)
阿里达摩院MindOpt优化求解器-月刊(2024年5月版),新增了两个案例,如何使用LLM和MindOpt更准确地回答数学问题、如何使用MindOpt优化云计算集群虚拟机资源配置提高机器利用率,和如何利用IIS冲突分析指导不可解的问题解决方案。MindOpt的求解器已经可以在阿里云线上购买不联网版本。租户版也正式上线,可体验更多功能。新增QQ交流群。
282 4