运筹系列85:求解大规模tsp问题的julia代码

1. 大规模tsp问题的挑战

数学模型和精确解法见《运筹系列65:TSP问题的精确求解法概述》和《运筹系列80:使用Julia精确求解tsp问题》:

@variable(m, x[1:n,1:n], Bin,Symmetric) # 0-1约束
@objective(model, Min, sum(x.*distmat)/2)
@constraint(model, [i = 1:n], sum(x[i, :]) == 2)
@constraint(model, [j = 1:n], sum(x[:, j]) == 2)
for subset in subsets # 防subtour约束,需要遍历所有的子集合
	@constraint(model,sum(x[subset[i],subset[i]])<=2*length(subset[i])-2) 
end
optimize!(m)
objective_value(model)

主要的挑战包括:

  1. 求解整数规划比较耗时,并且遍历子集难度很大,大规模问题时需要自定义高效的cut和price策略
  2. 变量数量过多,需要使用列生成方法来减少求解变量

2. 使用列生成减少求解变量

关于列生成的列子,可以参考《运筹系列8:Set partitioning问题的列生成方法》。对于tsp问题,我们使用列生成的步骤是:

  1. 首先将问题变量限制在每个点最邻近的k条边上,求解限制主问题
  2. 迭代求解子问题(sub problem)。如果目标函数最优值<0,就将新生成的列yk+1转入基变量,生成新的限制主问题进行求解。如此往复,直至子问题的目标函数≥0。

我们以u159问题为例,原始模型一共有25281个变量:

using TSPLIB, LinearAlgebra, JuMP,HiGHS,PyPlot
#读取数据
tsp = readTSPLIB(:u159)
n = tsp.dimension
distmat = [round(Int64,norm(tsp.nodes[i,:] - tsp.nodes[j,:])) for i in 1:n, j in 1:n];
for i in 1:n;distmat[i,i] = 10^9;end

#完整模型
model = Model(HiGHS.Optimizer)
set_silent(model)
@variable(model, 0<=x[i in 1:n,j in js[i]]<=1)
@info "variable size:",length(x)
@objective(model, Min, sum([x[i,j]*distmat[i,j]/2 for i in 1:n for j in js[i]]))
c = @constraint(model, [i = 1:n], sum([x[i, j] for j in js[i]]) >= 2)
e = @constraint(model, [i = 1:n], sum([x[j, i] for j in js[i]]) >= 2)
optimize!(model)
@info "objective:",objective_value(model)

主问题限制在每个点最近的3条边中,此时只有604个变量,第一轮列生成后为686个变量(此时已经得到最优解了),最终经过7轮列生成迭代,变量个数为716:

# js为初始下标合集,我们将变量下标限制在这个集合内。注意需要保持对称性:
idx = Index(2)
add(idx, tsp.nodes)
c = search(idx,tsp.nodes,4)[2];
js = Vector{Vector{Int}}()
for i in 1:n
   push!(js,c[i,2:end])
end
for i in 1:n
   for j in c[i,2:end]
       union!(js[j],i)
   end
end

求解主问题,然后找到检验数<0的列。令对偶变量 p = C B B − 1 p=C_B B^{-1} p=CBB1, 则检验数 σ = C N − p ∗ N \sigma= C_N-p*N σ=CNpN 。我们将所有检验数小于0的列进基,迭代直至an ==0,此部分完整代码如下:

an = 1
while an >0
    model = Model(HiGHS.Optimizer)
    set_silent(model)
    @variable(model, 0<=x[i in 1:n,j in js[i]]<=1)
    @info "variable size:",length(x)
    @objective(model, Min, sum([x[i,j]*distmat[i,j]/2 for i in 1:n for j in js[i]]))
    c = @constraint(model, [i = 1:n], sum([x[i, j] for j in js[i]]) >= 2)
    e = @constraint(model, [i = 1:n], sum([x[j, i] for j in js[i]]) >= 2)
    optimize!(model)
    @info objective_value(model)
    p1 = [dual(c[i]) for i in 1:n]
    p2 = [dual(e[i]) for i in 1:n];
    an = 0
    for i in 1:n
        for j in setdiff(1:n,js[i])
            sigma = distmat[i,j]/2 - p1[i]-p2[j]
            if sigma < 0;push!(js[i],j);push!(js[j],i);an+=2;end
        end
    end
    @info "add size:",an
end

3. 使用行生成添加约束

我们观察att48使用lp求解后的结果:
在这里插入图片描述

有很多子环,需要添加去除环的约束。
首先添加subtour约束,去除所有的子环约束:

using Graphs
paths = strongly_connected_components(SimpleDiGraph(round.(x_val,digits = 2)))
if length(paths)>1
    for path in paths
        @constraint(model,sum(x[path,path])<=2*length(path)-2)
    end
    optimize!(model)
    x_val = round.(JuMP.value.(x),digits = 2)
    paths = strongly_connected_components(SimpleDiGraph(x_val))
end

迭代求解,并绘制结果如下:
在这里插入图片描述

然后添加comb约束,约束介绍参见《运筹系列65:TSP问题的精确求解法概述》,julia代码参见《运筹系列80: 使用Julia精确求解tsp问题》的4.2节,求解后得到两个comb:
在这里插入图片描述
然后添加进约束,迭代求解:
在这里插入图片描述
迭代得到如下图:
在这里插入图片描述

4. 使用分枝定界求解整数规划问题

我们这里使用queue存储分枝节点,按照最简单的下标顺序,对所有非整数变量进行分枝。完整代码如下:

using TSPLIB,JuMP,DataStructures,GLPK,Graphs
struct Node
    x::Vector{CartesianIndex{2}} 
    b::Vector{Int} # 前面两个用于b&b
    paths::Vector{Vector{Int}} # 用于subtour eliminate
end

# 线性规划求解器
function linPro(node::Node)
    m = Model(GLPK.Optimizer)
    @variable(m, 0<=x[1:n, 1:n]<=1, Symmetric)
    @objective(m, Min, sum(x.*distmat)/2)
    @constraint(m, [i = 1:n], sum(x[i, :]) == 2)
    for i in 1:length(node.x)
        @constraint(m,x[node.x[i]]==node.b[i])
    end
    for path in node.paths
        @constraint(m,sum(x[path,path])<=2*length(path)-2)
    end
    optimize!(m)
    if Int(termination_status(m))==1
        r_x = round.(JuMP.value.(x),digits = 2)
        r_val = objective_value(m)
        return r_x,r_val
    end
    return nothing,nothing
end;

# 简单的分枝定界算法求整数解
function integerPro(paths,t=1.0E-12)
    ub = 10^9
    ub_x = nothing
    PQ = Queue{Node}()
    enqueue!(PQ,Node([],[],paths)) 
    while length(PQ)>0
        v = dequeue!(PQ)
        x,res = linPro(v)
        ind = findfirst(xi->xi>t && xi<1-t ,x)
        if ind == nothing && res < ub
            ub = res
            ub_x = copy(x)
        elseif res < ub
            enqueue!(PQ,Node([v.x;ind],[v.b;0],paths))
            enqueue!(PQ,Node([v.x;ind],[v.b;1],paths))
        end
    end
    return ub_x,ub
end;

tsp = readTSPLIB(:att48) # 使用att48例子
x_val,res = integerPro([])
paths = strongly_connected_components(SimpleDiGraph(round.(x_val,digits = 2)))
paths_list = []
# 行生成算法,不断添加subtour elimination constraints
while length(paths)>1
    @info length(paths)
    paths_list = [paths_list;paths]
    x_val,res = integerPro(paths_list)
    paths = strongly_connected_components(SimpleDiGraph(round.(x_val,digits = 2)))
end

plot_matrix(tsp, x_val)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的使用自适应大领域搜索算法求解 TSP 问题的示例代码(MATLAB版): ```matlab function [path, cost] = tsp_adaptive_search(points) % 计算各个点之间的距离 n = size(points, 1); dist = zeros(n, n); for i = 1:n for j = 1:n dist(i,j) = norm(points(i,:) - points(j,:)); end end % 定义启发式函数(最小生成树) function h = mst_heuristic(node, goal) remaining = setdiff(1:n, node); subgraph = dist(node, remaining); [t, ~] = prim(subgraph); h = sum(t(:)); end % 定义邻居函数(交换两个节点的位置) function neighbors = swap_neighbors(node) neighbors = {}; for i = 1:n-1 for j = i+1:n neighbor = node; neighbor(i) = node(j); neighbor(j) = node(i); neighbors{end+1} = neighbor; end end end % 初始化搜索队列 start = 1:n; queue = {start, 0, {}}; % 初始化已访问集合 visited = {start}; % 初始化当前搜索深度 depth = 0; % 初始化最优路径和代价 path = start; cost = inf; % 开始搜索 while ~isempty(queue) % 获取队列中的下一个节点 node = queue{1}; node_cost = queue{2}; path = node; queue(1) = []; % 检查是否到达目标节点 if isequal(node, start) && node_cost < cost path = [path, start]; cost = node_cost; end % 检查是否达到搜索深度限制 if depth < n-1 % 拓展当前节点的邻居节点 neighbors = swap_neighbors(node); for i = 1:length(neighbors) neighbor = neighbors{i}; % 检查邻居节点是否已经访问过 if ~ismember(neighbor, visited) % 计算邻居节点的启发式函数值 h = mst_heuristic(neighbor, start); % 计算邻居节点的代价(当前节点到邻居节点的距离) neighbor_cost = node_cost + dist(node(end), neighbor(1)) + sum(dist(neighbor(1:end-1), neighbor(2:end))); % 将邻居节点加入队列 queue{end+1} = neighbor; queue{end+1} = neighbor_cost + h; % 将邻居节点标记为已访问 visited{end+1} = neighbor; end end % 如果队列长度超过了搜索宽度限制,则按启发式函数值排序并截断队列 if length(queue) > 10*n [~, idx] = sort(cellfun(@(x) x(2), queue)); queue = queue(idx(1:10*n*3)); end % 如果队列为空,则增加搜索深度 if isempty(queue) depth = depth + 1; end end % 没有找到路径 if cost == inf path = []; cost = -1; end end ``` 在这个示例代码中,我们使用自适应大领域搜索算法求解 TSP 问题。对于 TSP 问题,我们需要定义一个邻居函数,在当前解的基础上产生一些相邻的解。在这个例子中,我们使用交换两个节点的位置来产生邻居解。我们还需要定义一个启发式函数,用来估计当前解到目标解的距离。在这个例子中,我们使用最小生成树算法计算当前解到目标解的最小代价。具体实现中,我们使用 Prim 算法来计算最小生成树,使用 MATLAB 自带的 `prim` 函数即可。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值