「算法」旅行商问题动态规划解法

感谢:https://www.geeksforgeeks.org/travelling-salesman-problem-set-1/

感谢:https://www.youtube.com/watch?v=-JjA4BLQyqE&feature=emb_rel_pause

感谢: https://github.com/mission-peace/interview/blob/master/src/com/interview/graph/TravelingSalesmanHeldKarp.java

感谢: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. 以城市1为起点和终点。
  2. 生成所有(n-1)个! 城市的排列。
  3. 计算每个排列的成本,并记录最小成本排列。
  4. 以最小的成本返回排列。
  • 时间复杂度:Θ(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;
    }
}
posted @ 2020-03-12 14:33  antonGo  阅读(2713)  评论(0)    收藏  举报