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]}
// 根据下界建立的小顶堆
queue
top <- {[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;
}
}