定义 c ( s , k ) c(s,k) c(s,k)为当前在 k k k,待访问点的集合 s s s,最后返回城市0的最短路径,那么Bellman方程为:
c ( s , k ) = min i ∈ s { c ( s − { i } , i ) + d i , k } c(s,k)=\min_{i \in s}\{c(s-\{i\},i)+d_{i,k}\} c(s,k)=mini∈s{c(s−{i},i)+di,k}
为了使用方便,这里使用一个trick,即使用二进制来表达,用位运算符来计算,称作set bits:
我们有初始状态 c ( { 0 } , k ) = ( d 0 , k , 0 ) c(\{0\},k)=(d_{0,k},0) c({0},k)=(d0,k,0)。
逐步扩大set的尺寸,遍历所有可能的subset,使用bellman方程迭代计算。我们以n=5为例进行计算,初始状态为:
第三轮结束后,状态清单为:
using Combinatorics
function held_karp(dists::Array{Int64, 2})
n = size(dists,1)
C = Dict{Tuple{Int64, Int64},Tuple{Int64, Int64}}()
for k in 1:n-1
C[(1 << k, k)] = (dists[n,k], n)
end
for subset_size in 2:n-1
for subset in combinations(1:n-1,subset_size)
bits = 0
for bit in subset;bits |= 1 << bit;end
for k in subset
prev = bits & ~(1 << k)
res = Array{Tuple{Int64, Int64},1}()
for m in subset
if m == k;continue;end
push!(res,(C[(prev,m)][1]+dists[m,k],m))
end
C[(bits,k)] = minimum(res)
end
end
end
bits = 1<
import itertools
import random
import sys
def generate_distances(n):
dists = [[0] * n for i in range(n)]
for i in range(n):
for j in range(i+1, n):
dists[i][j] = dists[j][i] = random.randint(1, 99)
return dists
def held_karp(dists):
"""
Implementation of Held-Karp, an algorithm that solves the Traveling
Salesman Problem using dynamic programming with memoization.
Parameters:
dists: distance matrix
Returns:
A tuple, (cost, path).
"""
n = len(dists)
# Maps each subset of the nodes to the cost to reach that subset, as well
# as what node it passed before reaching this subset.
# Node subsets are represented as set bits.
C = {}
# Set transition cost from initial state
for k in range(1, n):
C[(1 << k, k)] = (dists[0][k], 0)
# Iterate subsets of increasing length and store intermediate results
# in classic dynamic programming manner
for subset_size in range(2, n):
for subset in itertools.combinations(range(1, n), subset_size):
# Set bits for all nodes in this subset
bits = 0
for bit in subset:
bits |= 1 << bit
# Find the lowest cost to get to this subset
for k in subset:
prev = bits & ~(1 << k)
res = []
for m in subset:
if m == 0 or m == k: # 不允许回到0,不允许为当前点
continue
res.append((C[(prev, m)][0] + dists[m][k], m))
C[(bits, k)] = min(res)
# We're interested in all bits but the least significant (the start state)
bits = (2**n - 1) - 1
# Calculate optimal cost
res = []
for k in range(1, n):
res.append((C[(bits, k)][0] + dists[k][0], k))
opt, parent = min(res)
# Backtrack to find full path
path = []
for i in range(n - 1):
path.append(parent)
new_bits = bits & ~(1 << parent)
_, parent = C[(bits, parent)]
bits = new_bits
# Add implicit start state
path.append(0)
return opt, list(reversed(path))
def held_karp2(dists):
n = len(dists)
C = {}
for k in range(1, n):
C[(1, k)] = (dists[0][k], 0)
for subset_size in range(1, n):
for m in range(1, n):
for subset in itertools.combinations(list(range(1,m))+list(range(m+1,n)), subset_size):
bits = 1
for bit in subset:
bits |= 1 << bit
res = []
for k in subset: # 决策变量
next = bits & ~(1 << k)
res.append((C[(next, k)][0] + dists[k][m], k))
C[(bits, m)] = min(res)
# We're interested in all bits but the least significant (the start state)
bits = 2**n - 1
# Calculate optimal cost
res = []
for k in range(1, n):
res.append((C[(bits & ~(1 << k), k)][0] + dists[k][0], k))
opt, parent = min(res)
# Backtrack to find full path
path = []
for i in range(n-1):
path.append(parent)
bits = bits & ~(1 << parent)
_, parent = C[(bits, parent)]
# Add implicit start state
path.append(0)
return opt, list(reversed(path))
def held_karp3(dists):
n = len(dists)
C = {}
for k in range(1, n):
C[(frozenset([k]), k)] = (dists[0][k], 0)
for subset_size in range(2, n):
for subset in itertools.combinations(range(1, n), subset_size):
for k in subset:
prev = frozenset(set(subset) - {k})
res = []
for m in subset:
if m == 0 or m == k:
continue
res.append((C[(prev, m)][0] + dists[m][k], m))
C[(frozenset(subset), k)] = min(res)
# We're interested in all bits but the least significant (the start state)
bits = set(list(range(1,n)))
# Calculate optimal cost
res = []
for k in range(1, n):
res.append((C[(frozenset(bits), k)][0] + dists[k][0], k))
opt, parent = min(res)
# Backtrack to find full path
path = []
for i in range(n - 1):
path.append(parent)
new_bits = bits - {parent}
_, parent = C[(frozenset(bits), parent)]
bits = new_bits
# Add implicit start state
path.append(0)
return opt, list(reversed(path))
简化版如下:
# memoization for top down recursion
memo = [[-1]*(1 << (n+1)) for _ in range(n+1)]
def fun(i, mask):
# base case
# if only ith bit and 1st bit is set in our mask,
# it implies we have visited all other nodes already
if mask == ((1 << i) | 3):
return dist[1][i]
# memoization
if memo[i][mask] != -1:
return memo[i][mask]
res = 10**9 # result of this sub-problem
# we have to travel all nodes j in mask and end the path at ith node
# so for every node j in mask, recursively calculate cost of
# travelling all nodes in mask
# except i and then travel back from node j to node i taking
# the shortest path take the minimum of all possible j nodes
for j in range(1, n+1):
if (mask & (1 << j)) != 0 and j != i and j != 1:
res = min(res, fun(j, mask & (~(1 << i))) + dist[j][i])
memo[i][mask] = res # storing the minimum value
return res
# Driver program to test above logic
ans = 10**9
for i in range(1, n+1):
# try to go from node 1 visiting all nodes in between to i
# then return from i taking the shortest route to 1
ans = min(ans, fun(i, (1 << (n+1))-1) + dist[i][1])
print("The cost of most efficient tour = " + str(ans))