1. 辅助函数
Node算子用来存储搜索树的状态。其中level等于path的长度,path是当前节点已经访问过的vertex清单,bound则是当前的lb。
这里的bound函数是一种启发式方法,等于当前路径的总长度,再加上往后走两步的最小值。
struct Node
level::Int
path::Vector{Int64}
bound::Int
end
function totaldist(adj_mat::Array{Int64,2},t::Vector{Int64} )
n = length(t)
sum([adj_mat[t[i],t[i+1]] for i in 1:n-1])+adj_mat[t[n],t[1]]
end
function bound(adj_mat::Array{Int64,2}, path::Vector{Int64} )
_bound = 0
n = size(adj_mat)[1]
determined, last = path[1:end-1], path[end]
remain = setdiff(1:n,path)
for i in 1:length(path)-1;_bound += adj_mat[path[i],path[i + 1]];end
_bound += minimum([adj_mat[last,i] for i in remain])
p = [path[1];remain]
for r in remain
_bound+=minimum([adj_mat[r,i] for i in setdiff(p,r)])
end
return _bound
end;
2. 分枝定界代码
这里用priorityQueue存储节点,用Queue也是一样的。
分枝条件为bound<ub,往下搜索所有没有探访过的节点,使用函数setdiff(1:n,v.path)。当然这里可以尝试将搜索范围缩小,比如仅搜索最近的一些节点,不过就不保证最优性了。
当搜索到level==n-1时,获得一个可行解,并且停止往下探索。此时如果路径长度比ub还短,则更新ub。
function solve(adj_mat::Array{Int64,2},ub::Int64 = 10^9)
optimal_tour = Vector{Int64}()
optimal_length = 0
n = size(adj_mat)[1]
PQ = PriorityQueue{Node,Int}()
path = Vector{Int64}([1])
v = Node(1,path,bound(adj_mat,path))
enqueue!(PQ,v,v.bound)
while length(PQ)>0
v = dequeue!(PQ)
if v.bound<ub
level = v.level+1
b = 0
for i in setdiff(1:n,v.path)
path = [v.path;i]
if level==n-1 #终止条件
push!(path,setdiff(1:n,path)[1])
_len = totaldist(adj_mat,path)
if _len < ub
ub = _len
optimal_length = _len
optimal_tour = path
end
else # 进行分叉
b = bound(adj_mat,path)
if b < ub # 分枝条件
enqueue!(PQ,Node(level,path,b),b)
end
end
end
end
end
optimal_tour,optimal_length
end
solve([0 14 4 10 20;14 0 7 8 7;4 5 0 7 16;11 7 9 0 2;18 7 17 4 0])
输出([1, 4, 5, 2, 3], 30)。
TSP时一个NPhard问题,当点数增多时,使用b&b的算法性能会急速下降。
3. 另一种b&b算法
参考1980年Ton VOLGENANT and Roy JONKER的《A branch and bound algorithm for the symmetric traveling salesman probli m based on the 1-tree relaxation》,基于minimum-1-tree进行分枝定界。
首先是基于mst的ascent算法:
其中z(T)是mst的长度。我们的目标是迭代
π
\pi
π得到最优的lowerbound,即这里的
w
(
π
)
w(\pi)
w(π)。
Held and Karp首先使用了ascent method,并且在Held, Wolfe and Crowder论文中进行了优化。Sraith and
Thompson开发了变种算法。令L为lower bound,U为upper bound,迭代算法为:
Held, Wolfe and Crowder证明了,当U等于optimal长度,并且p足够小时,上面的计算是收敛的。
由于上面的收敛速度比较慢,因此常用的迭代公式为:
令R为必须在结果中的边,F为不可以出现在结果中的边,则分枝策略为:
找到degree大于2的点p,然后选择其中的两条边e1和e2进行分枝:
至于upper bound,这里使用Christofides的简化版本:
紧接着使用Lin algorithm,等价于insertion+2opt算法。
测试结果如下: