TSP旅行商问题

TSP是啥呢?通俗地讲,这个问题可以描述为一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短。所以就是在一个图中从一个点出发,找一个回路,能够回到这个点,而且总花销还最小。那我们怎么做呢?这篇文章我们用动态规划来写。

首先,请看一下bilibili上关于动态规划过程的描述,这是我们实现算法的基础:

 

 

 

 我认为这两张图就比较通俗易懂了。我们从城市0出发,经过城市1,2,3最后回到城市0所花费的代价,是三个小代价的最小值,这三个小代价是什么呢?是城市0到城市1的距离,加上从城市1出发,经过城市2,3,最后回到城市0的距离,...后面以此类推,递推式已经写在上面了。因此,这个递推式就是我们实现递归的核心,我们需要不断更新d这个城市的集合(说白了,就是不断把它里面的城市从它里面剔除,一次剔除一个),这样,递归到最后,城市集合为空。这时候怎么办呢?别急,上面的式子里面也写了。递归到最后,我就只需要算集合外面那个城市(定义的是d(city,collection),这里集合外面的城市就指的是d的city)到城市0的距离即可,也就是说,直接把这个距离返回。这样,我们写代码的思路就出来了。然后再在前面做一些初始化的工作就可以了。

下面贴代码:

第一步,我们定义几个变量:

1 public int[][] dis;
2 public ArrayList<Integer> cities;
3 public int source;

第一行的二维数组dis存储的是距离,比如dis[i][j]表示的是从城市i到城市j的距离;

第二行的是集合,存储的是城市。由于城市我们以数字编号,因此使用了一个Integer类型的ArrayList集合;

第三行是源点,即城市0.

然后我们在构造器里来个初始化:

1 public Traveling(int n) {
2         cities = new ArrayList<>();
3         for (int i = 0; i < n; ++i) {
4             cities.add(i);
5         }
6         source = 0;
7 }

在cities集合里添加全部的城市,这里城市编号从0开始。

然后我们写一个init方法,用来初始化dis数组,以什么样的标准呢?城市自己到自己的距离当然就是0啦,除此之外,全部设为正无穷。

1 public void init() {
2         int n = dis.length;
3         for (int i = 0; i < n; ++i) {
4             for (int j = 0; j < n; ++j) {
5                 dis[i][j] = ((i == j) ? 0 : Integer.MAX_VALUE);
6             }
7         }
8 }

然后,为了实现后面递归代码里面城市集合几种不同的情况,我们写一个复制集合的方法(否则会炸,因为java不允许边迭代集合的时候还边对它里面的元素进行修改):

1 private ArrayList<Integer> copy(ArrayList<Integer> a) {
2         ArrayList<Integer> list = new ArrayList<>();
3         for (Integer i : a) {
4             list.add(i);
5         }
6         return list;
7 }

最后,我们写一个distance函数,其实也就是tsp动态规划的过程:

 1 private int distance(int start, ArrayList<Integer> list) {
 2         if (list.size() == 0) {
 3             return dis[start][source];
 4         } 
 5         int temp = Integer.MAX_VALUE;
 6         for (Integer i : list) {
 7             ArrayList<Integer> copied = copy(list);
 8             copied.remove(i);
 9             int res = distance(i, copied) + dis[start][i];
10             temp = temp < res ? temp : res;
11         }
12         return temp;
13 }

首先,我们要定义递归函数的退出条件,即:城市集合为空时,我们应直接返回距离。

其次,遍历城市集合里面所有的城市,分别把它们挑出来,计算哪种花费的代价最小。我们就选那个,最后,把这个最小代价返回。

最后我们给外界提供一个方法:

1 public int tsp() {
2         return distance(source, cities);
3 }

来一波完整代码:

 1 package com.hw.others;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Scanner;
 5 
 6 public class Traveling {
 7     public int[][] dis;
 8     public ArrayList<Integer> cities;
 9     public int source;
10 
11     public Traveling(int n) {
12         cities = new ArrayList<>();
13         for (int i = 0; i < n; ++i) {
14             cities.add(i);
15         }
16         source = 0;
17     }
18 
19     public void init() {
20         int n = dis.length;
21         for (int i = 0; i < n; ++i) {
22             for (int j = 0; j < n; ++j) {
23                 dis[i][j] = ((i == j) ? 0 : Integer.MAX_VALUE);
24             }
25         }
26     }
27 
28     private ArrayList<Integer> copy(ArrayList<Integer> a) {
29         ArrayList<Integer> list = new ArrayList<>();
30         for (Integer i : a) {
31             list.add(i);
32         }
33         return list;
34     }
35 
36     private int distance(int start, ArrayList<Integer> list) {
37         if (list.size() == 0) {
38             return dis[start][source];
39         } 
40         int temp = Integer.MAX_VALUE;
41         for (Integer i : list) {
42             ArrayList<Integer> copied = copy(list);
43             copied.remove(i);
44             int res = distance(i, copied) + dis[start][i];
45             temp = temp < res ? temp : res;
46         }
47         return temp;
48     }
49 
50     public int tsp() {
51         return distance(source, cities);
52     }
53 
54     public static void main(String[] args) {
55         Scanner s = new Scanner(System.in);
56         System.out.println("输入城市个数:");
57         int n = s.nextInt();
58         Traveling traveler = new Traveling(n);
59         traveler.dis = new int[n+1][n+1];
60         traveler.init();
61         System.out.println("输入边的数目:");
62         int edge = s.nextInt();
63         System.out.println("现在输入城市与城市间的距离(城市编号从0开始):");
64         for (int i = 0; i < edge; ++i) {
65             int start = s.nextInt();
66             int end = s.nextInt();
67             int weight = s.nextInt();
68             traveler.dis[start][end] = weight;
69             traveler.dis[end][start] = weight;
70         }
71         int length = traveler.tsp();
72         System.out.println("最短路径代价为:" + length);
73         s.close();
74     }
75 }

 

posted @ 2021-11-08 23:56  EvanTheBoy  阅读(284)  评论(0)    收藏  举报