7.1 题目描述
实现旅行销售商问题的分支限界算法。
7.2 程序使用说明
- 输入城市数n;
2. 输入各城市间的距离map[1…n, 1…n]。
7.3 分析和设计
- 思路
使用分支限界问题解决旅行商问题重点就是限界条件。从一个城市进入下一个城市有多种走法,而不同的选择会产生不同的下界,为了找到最短路径,需要在所有选择中找到下界最小的方案进行下一步的处理,这里可以用优先队列快速确定最小下界的方案。最短路径的下界可以通过计算每一个城市i(1<=i<=n)到最近的两个城市的距离之和si(最快经过城市i),然后把这n个数字求和除以2(同一条路径即作为了出城的路,又作为了入城的路)得到,当然,如果已经走过一些城市,那么对应的一些路径就被固定,此时Si中就必须包含已确定路径。
2. 伪代码FindPath(map[1...n][1...n])// 输入:各城市的距离map// 输出:最短路径// 结点记录的是已走过的城市、下界、未走过的城市bestNode <- {[1], sum(map), [2,...,n]}// 根据下界建立的小顶堆queuetop <- {[1], sum(map), [2,...,n]}// 计算下界top.lb <- getLb(map, top)while top != null and top.lb < bestNode.lb if top.leave.length = 0 // 所有城市都走过,回到起点 top.path.add(1) top.lb <- getLb(map, top) if top.lb < bestNode.lb bestNode <- top else for i <- 0 to top.leave.length do path <- top.path path.add(top.leave[i]) leave <- top.leave leave.remove(top.leave[i]) node <- {path, top.lb, leave} node.lb <- getLb(map, node) queue.add(node) top = queue.poll()return bestNode
- 时间复杂度
在分支限界的解空间树中,一个结点需要从它未走过的城市选择下一个城市,有多少个剩余城市就会生成多少个结点,所以它的空间树的最大结点数为(n-1)(n-2)…*1,所以时间复杂度为O(nn)。
7.4 测试用例
| | | | | |
| —- | —- | —- | —- | —- |
| 1 | n = 2
Goods = [[0, 1, 1],
[1, 0, 2],
[1, 2, 0]] | 路径:a b c a
最短路径:4 | 路径:a b c a
最短路径:4 | 通过 |
| 2 | n = 5
Goods = [[0, 3, 1, 5, 8],
[3, 0, 6, 7, 9],
[1, 6, 0, 4, 2],
[5, 7, 4, 0, 3],
[8, 9, 2, 3, 0]] | 路径:a c e d b a
总路径:16 | 路径:a c e d b a
总路径:16 | 通过 |
7.5 源代码(含注释)
import java.util.*;public class Travel { public static void main(String[] args) { int[][] map = {{0, 3, 1, 5, 8}, {3, 0, 6, 7, 9}, {1, 6, 0, 4, 2}, {5, 7, 4, 0, 3}, {8, 9, 2, 3, 0}}; System.out.println("各城市距离如下:"); for (int[] paths : map) { for (int path : paths) { System.out.print(path + "\t"); } System.out.println(); } Node bestNode = findPath(map); System.out.print("路径:"); for (Integer location : bestNode.path) { System.out.print((char) (location + 'a') + " "); } System.out.println("\n总路径:" + bestNode.lb); System.out.print("请输入城市数:"); Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); System.out.println("请输入城市间的距离(n*n的二维数组):"); map = new int[n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { map[i][j] = scanner.nextInt(); } } bestNode = findPath(map); System.out.print("路径:"); for (Integer location : bestNode.path) { System.out.print((char) (location + 'a') + " "); } System.out.println("\n总路径:" + bestNode.lb); } /** * 通过分支限界找最短路径,每到一个城市就对应生成一个结点, * 在已生成的结点中选择lb(=s/2上取整)最小的结点进行下一步的决策(限界条件), * s_i为从城市i经过已确定路径或者最短路径到达的两个城市的路径之和,s=s_1+s_2+...+s_n * 所有城市都走过时就得到了一条可行的路径 * * @param map int[0...n-1][0...n-1]各城市间的距离 * @return */ public static Node findPath(int[][] map) { Node bestNode = new Node(new ArrayList<>(), Integer.MAX_VALUE, null); // 小顶堆,始终先处理下界最小的结点 PriorityQueue<Node> queue = new PriorityQueue<>(1, ((o1, o2) -> o1.lb - o2.lb)); // 生成最初的结点 List<Integer> path = new ArrayList<>(); path.add(0); List<Integer> leaveLocation = new ArrayList<>(); for (int i = 1; i < map.length; i++) { leaveLocation.add(i); } Node top = new Node(path, Integer.MAX_VALUE, leaveLocation); top.lb = getLb(map, top); // 保证优先队列的顶部结点不为空,并且下界小于当前最优,这才有处理的价值 while (top != null && top.lb < bestNode.lb) { if (top.leave.size() == 0) { // 所有城市都走过了,回到起点 top.path.add(0); top.lb = getLb(map, top); if (top.lb < bestNode.lb) { bestNode = top; } } else { // 把当前结点的所有下一步决策加入优先队列中 for (Integer location : top.leave) { path = new ArrayList<>(top.path); path.add(location); leaveLocation = new ArrayList<>(top.leave); leaveLocation.remove(new Integer(location)); Node node = new Node(path, Integer.MAX_VALUE, leaveLocation); node.lb = getLb(map, node); queue.add(node); } } top = queue.poll(); } return bestNode; } /** * 计算当前结点的lb,即最优的进城和出城路径,对于已经过的路径不可避免,剩余的需要是最短路径 * * @param map int[1...n][1...n]各城市间的距离 * @param node 当前结点状态 * @return */ public static int getLb(int[][] map, Node node) { int[][] tempMap = new int[map.length][map[0].length]; // 深度拷贝 for (int i = 0; i < map.length; i++) { tempMap[i] = Arrays.copyOf(map[i], map[i].length); } // 标记已经过的路径 int[][] flag = new int[tempMap.length][tempMap[0].length]; for (int i = 0; i < node.path.size() - 1; i++) { // 标记a->b和b->a flag[node.path.get(i)][node.path.get(i + 1)] = 1; flag[node.path.get(i + 1)][node.path.get(i)] = 1; } int s = 0; // 依次查找两个合适路径,表示进城与出城,如果城市数小于等于2这个循环就有问题 for (int last = 0; last < 2; last++) { for (int i = 0; i < tempMap.length; i++) { // 记录第i个城市到第j个城市的最短路径 int min = Integer.MAX_VALUE; int index = -1; // 记录本次查找的是否是已经过的路径 boolean isPass = false; for (int j = 0; j < tempMap.length; j++) { if (i == j) { continue; } if (flag[i][j] == 1) { // 如果此路径已经过 s += tempMap[i][j]; // 下次查找需要排除 flag[i][j] = 0; tempMap[i][j] = Integer.MAX_VALUE; isPass = true; break; } else if (min > tempMap[i][j]) { min = tempMap[i][j]; index = j; } } if (!isPass) { s += tempMap[i][index]; // 下次查找可排除 tempMap[i][index] = Integer.MAX_VALUE; } } } return (int) Math.ceil(s * 0.5); }}import java.util.ArrayList;import java.util.List;public class Node { // 已经走过的城市 List<Integer> path; // 计算下界 int lb; // 剩余城市 List<Integer> leave; public Node() { path = new ArrayList<>(); lb = Integer.MAX_VALUE; leave = new ArrayList<>(); } public Node(List<Integer> path, int lb, List<Integer> leave) { this.path = path; this.lb = lb; this.leave = leave; }}