「算法」旅行商问题动态规划解法
感谢:https://www.geeksforgeeks.org/travelling-salesman-problem-set-1/
感谢:https://www.youtube.com/watch?v=-JjA4BLQyqE&feature=emb_rel_pause
感谢:Tushar Roy 真大佬
🚌 Travelling Salesman Problem (TSP)
-
给定一组城市以及每对城市之间的距离,找到一条最短的路线,该路线只访问每个城市一次并返回起点。
-
注意哈密顿回路(Hamiltonian Cycle)和TSP之间的差异。
哈密顿图(哈密尔顿图)(英语:Hamiltonian graph,或Traceable graph)是一个无向图,由天文学家哈密顿提出,由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次。
在图论中是指含有哈密顿回路的图,闭合的哈密顿路径称作哈密顿回路(Hamiltonian cycle),含有图中所有顶点的路径称作哈密顿路径(Hamiltonian path)。
例如,考虑下方所示的图形。 图中的TSP路径是1-2-4-3-1。 游览费用为10 + 25 + 30 + 15,即80。
该问题是著名的NP hard。 没有多项式时间已知的解决方案来解决此问题。
- P:算起来很快的问题
- NP:算起来不一定快,但对于任何答案我们都可以快速的验证这个答案对不对
- NP-hard:比所有的NP问题都难的问题
- NP-complete:满足两点:
- 是NP hard的问题
- 是NP问题
以下是旅行商问题的不同解决方案。
📸 Naive Solution 全排列|穷举
- 以城市1为起点和终点。
- 生成所有(n-1)个! 城市的排列。
- 计算每个排列的成本,并记录最小成本排列。
- 以最小的成本返回排列。
- 时间复杂度:Θ(n!)
㊙️ Dynamic Programming
令给定的顶点集为 {1,2,3,4,.... n} 。让我们考虑1作为输出的起点和终点。对于其他所有「不是1」顶点i,我们找到以1为起点,i为终点且所有顶点仅出现一次的最小成本路径。
假设此路径的成本为cost(i),则相应路径的成本为cost(i)+ dis(i, 1),其中dist(i,1)是从i到1的距离。
最后,我们返回所有[cost(i)+ dist(i,1)]值中的最小值。到目前为止,这看起来很简单。现在的问题是如何获得$cost(i)$?
为了用DP计算cost(i),我们需要找到具有递归关系的子问题。
定义C(S, i)为从 1 点出发到 i 点结束并且完全访问S集合里每个节点的最小成本。
我们从所有大小为2的子集开始,并为其中S为子集的所有子集计算C(S,i),然后为大小为3的所有子集S计算C(S,i),依此类推。 请注意,每个子集中必须存在1。
If size of S is 2, then S must be {1, i},
C(S, i) = dist(1, i)
Else if size of S is greater than 2.
C(S, i) = min { C(S-{i}, j) + dis(j, i)} where j belongs to S, j != i and j != 1.
-
对于大小为n的集合,我们考虑n-2个子集,每个子集的大小为n-1,这样所有子集都不包含第n个节点。
-
使用上述递归关系,我们可以编写基于动态编程的解决方案。 最多有$O(n2^n)$个子问题,每个子问题都需要线性时间才能解决。 因此,总运行时间为$O(n^2 2^n)$。 时间复杂度远小于$O(n!)$,但仍然是指数级的。 所需空间也是指数的。 因此,即使顶点数量稍多,此方法也不可行。
🎥 视频讲解

- 一共 $2n$个子集,每个节点都需要被检查$n2$ ,所以时间复杂度是
⌨️ Java实现
package graph;
import java.util.*;
/**
* Help Karp method of finding tour of traveling salesman.
* <p>
* Time complexity - O(2^n * n^2)
* Space complexity - O(2^n)
* <p>
* https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm
*/
public class TravelingSalesmanHeldKarp {
private static int INFINITY = 100000000;
private static class Index {
int currentVertex;
Set<Integer> vertexSet;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Index index = (Index) o;
if (currentVertex != index.currentVertex) return false;
return !(vertexSet != null ? !vertexSet.equals(index.vertexSet) : index.vertexSet != null);
}
@Override
public int hashCode() {
int result = currentVertex;
result = 31 * result + (vertexSet != null ? vertexSet.hashCode() : 0);
return result;
}
private static Index createIndex(int vertex, Set<Integer> vertexSet) {
Index i = new Index();
i.currentVertex = vertex;
i.vertexSet = vertexSet;
return i;
}
}
private static class SetSizeComparator implements Comparator<Set<Integer>> {
@Override
public int compare(Set<Integer> o1, Set<Integer> o2) {
return o1.size() - o2.size();
}
}
public static void main(String[] args) {
int[][] distance = {{0, 3, 6, 7},
{5, 0, 2, 3},
{6, 4, 0, 2},
{3, 7, 5, 0}};
TravelingSalesmanHeldKarp t = new TravelingSalesmanHeldKarp();
System.out.println(t.minCost(distance));
}
public int minCost(int[][] distance) {
//1. 两个map用来存储中间生成的值
Map<Index, Integer> minCostDP = new HashMap<>();
Map<Index, Integer> parent = new HashMap<>();
//2. 生成所有组合 比如 {},{1},{2},{3},{1,2},{1,3},{2,3}
List<Set<Integer>> allSets = generateCombination(distance.length - 1);
for (Set<Integer> set : allSets) {
// 3. 计算当前集合到每个节点的距离,节点不在当前集合内。
for (int currentVertex = 1; currentVertex < distance.length; currentVertex++) {
if (set.contains(currentVertex)) {
continue;
}
Index index = Index.createIndex(currentVertex, set);
int minCost = INFINITY;
int minPrevVertex = 0;
//to avoid ConcurrentModificationException copy set into another set while iterating
Set<Integer> copySet = new HashSet<>(set);
for (int prevVertex : set) {
int cost = distance[prevVertex][currentVertex] + getCost(copySet, prevVertex, minCostDP);
if (cost < minCost) {
minCost = cost;
minPrevVertex = prevVertex;
}
}
//空集合的特殊处理
if (set.size() == 0) {
minCost = distance[0][currentVertex];
}
minCostDP.put(index, minCost);
parent.put(index, minPrevVertex);
}
}
Set<Integer> set = new HashSet<>();
for (int i = 1; i < distance.length; i++) {
set.add(i);
}
int min = Integer.MAX_VALUE;
int prevVertex = -1;
//to avoid ConcurrentModificationException copy set into another set while iterating
Set<Integer> copySet = new HashSet<>(set);
// 4. 计算 [0, {1, 2, 3}]
for (int k : set) {
int cost = distance[k][0] + getCost(copySet, k, minCostDP);
if (cost < min) {
min = cost;
prevVertex = k;
}
}
parent.put(Index.createIndex(0, set), prevVertex);
printTour(parent, distance.length);
return min;
}
private void printTour(Map<Index, Integer> parent, int totalVertices) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < totalVertices; i++) {
set.add(i);
}
Integer start = 0;
Deque<Integer> stack = new LinkedList<>();
while (true) {
stack.push(start);
set.remove(start);
start = parent.get(Index.createIndex(start, set));
if (start == null) {
break;
}
}
StringJoiner joiner = new StringJoiner("->");
stack.forEach(v -> joiner.add(String.valueOf(v)));
System.out.println("\nTSP tour");
System.out.println(joiner.toString());
}
private int getCost(Set<Integer> set, int prevVertex, Map<Index, Integer> minCostDP) {
set.remove(prevVertex);
Index index = Index.createIndex(prevVertex, set);
int cost = minCostDP.get(index);
set.add(prevVertex);
return cost;
}
private List<Set<Integer>> generateCombination(int n) {
int input[] = new int[n];
for (int i = 0; i < input.length; i++) {
input[i] = i + 1;
}
List<Set<Integer>> allSets = new ArrayList<>();
int result[] = new int[input.length];
generateCombination(input, 0, 0, allSets, result);
Collections.sort(allSets, new SetSizeComparator());
return allSets;
}
private void generateCombination(int input[], int start, int pos, List<Set<Integer>> allSets, int result[]) {
if (pos == input.length) {
return;
}
Set<Integer> set = createSet(result, pos);
allSets.add(set);
for (int i = start; i < input.length; i++) {
result[pos] = input[i];
generateCombination(input, i + 1, pos + 1, allSets, result);
}
}
private static Set<Integer> createSet(int input[], int pos) {
if (pos == 0) {
return new HashSet<>();
}
Set<Integer> set = new HashSet<>();
for (int i = 0; i < pos; i++) {
set.add(input[i]);
}
return set;
}
}

浙公网安备 33010602011771号