网上很多相关文章介绍的时候都缺乏例子,以至于太抽象不够直观。本文用具体例子来介绍动态规划法界tsp,力求让初学者可以轻松理解。伪代码来自《Foundations of Algorithms(written by Neapolitan, Richard)》
TSP
tsp是为了在一个图中求得一个最短的路径,可以实现从一个起点出发,经过所有节点,最终回到起点。
首先理清一些概念
a.对于下图,我们用v1,v2,v3,……v6表示每个节点。n表示节点个数,n = 6。 因为求的是一个回路,所以从任意一点出发都没有区别。所以我们以v1做起点。
b.用二维邻接矩阵W表示各个节点之间的距离。即W[i][j] = vi到vj的距离
c.比较难理解也是比较重点的是用于存储动态规划状态的矩阵D。D的每一行表示一个节点,每一列表示一个子集。这个子集是指包含除起点v1外的所有节点集合V ={v2, v3, v4, v5, v6}(注意,这里的V不是所有点的集合,是除v1外所有点的集合)的任一子集
-------------------------------------------------------------------------------------------------------
{}{v2}{v2,v3}..(一共有2^5个子集,这里不全写了).{v2,v3,v4,v5,v6}
v1
v2
v3
...
v6
--------------------------------------------------------------------------------------------------------
| {Ø} | {v2} | {v2, v3} | 省略,一共是2^5个子集 | {v2, v3, v4, v5, v6} | |
| v1 | …… | ||||
| v2 | …… | ||||
| v3 | |||||
| v4 | |||||
| v5 | |||||
| v6 |
D[v2][{v3}]表示,v2经过v3,最终回到起点v1的路程。一般地,当用A表示V ={v2, v3, v4, v5, v6}的任一子集,对于D[vi][A],
D[vi][A] = 从vi出发,经过A中所有节点,再回到起点v1,所经历的最短路程。
当A为空集,就是指vi直接回v1的距离。也就是D[vi][Ø] = W[i][1]
动态规划的思路
那么,我们要求的最优路径,也就是D[v1][V}]
D[v1][{v2, v3, v4, v5, v6}] = W[v1][vi] + min(D[vi][V - vi])(vi = v2,v3,v4,v5,v6)
动态规划的思想,就是是研究状态转移的规律,通过从最初的状态,转移到另一个状态,逐渐转移到解决问题的状态。下面说说本题的状态
对于D来说,我们已知的就是D[vi][Ø] = W[vi][v1],以及,当A只有一个元素时,D[vi][A]的值。因为它就是vi -> vj(vj∈A) -> v1,也就是W[vi][vj] + W[vj][v1]。
但是当A中的元素多于一个,比如 A = {vj, vk},D[vi][A]的值是vi -> vj -> vk -> v1与 vi -> vk -> vj ->v1中更小的那个值。
比如D[v2][{v3, v4}] = min(9+7+7, 5+7+16) = 23
对于任一A⊆V,我们不能直接穷举它所代表的所有路径,然后求一个最小值,赋给D[vi][A],因为这样就变成了暴力法了。而动态规划的思想是,从A = Ø开始,求得A中包含一个元素时D的所有结果,A中包含两个元素时D的所有结果,一直到A包含了任一n-2个元素的所有D的结果(n是包含起点的所有节点个数,本题n = 6)。
为什么是n-2个呢? 对于D[vi][A]来说,当A包含vi本身的时候,对本题来说是没有意义的,比如,D[v2][{v2}]这个值,没有意义。
所以,当我们知道D[v2][{v3, v4, v5, v6}]的时候,我们也就知道了如果从v1出发,先到v2,所需要的最短回路是多少了。当我们求得了所有的D[vi][V - vi]时,本题就解出来了。
对于任一非空的A,都有D[vi][A] = min(W[vi][vj] + D[vj][A-vj]) 其中vj属于A。
比如,D[v2][{v3}] = W[v2][v3] + D[v3][Ø],又因为D[vi][Ø] = W[vi][1],所以D[v2][{v3} = ]W[v2][v3] + W[v3][v1]
在程序中,我们就逐渐增多A中元素的个数,直到n - 2为止。
当经历第一遍循环,所有D[vi][{vj}]的值就都知道了。然后对于含有两个元素的A,比如D[v2][{v3,v4}],就有
D[v2][{v3,v4}] = min(W[v2][v3] + D[v3][{v4}],W[v2][v4] + D[v4][{v3}])
当执行完n-2次这个循环,D中的所有值,除了D[v1][A]就都知道了。我们最终要求的是D[v1][V],它是
D[v1][{v2,v3,v4,v5,v6}] = min(W[v1][v2] + D[v2][{v3,v4,v5,v6}],
W[v1][v3] + D[v3][{v2,v4,v5,v6}],
W[v1[v4] + D[v4][{v2,v3,v5,v6}]],
W[v1][v5] + D[v5][{v2,v3,v4,v6}],
W[v1][v6] + D[v6[{v2,v3,v4,v5}]])
java代码
其中DP函数用于求解,generateOpttour用于输出路径。getSubsets用于得到节点的所有子集。用hashmap储存子集与该子集在D中的列号。
package project3;
import java.util.ArrayList;
//import java.util.ArrayList;
import java.util.HashMap;
public class TSPDP {
private int[][] graph;
HashMap<Integer, ArrayList<Integer>> idtoset = new HashMap<Integer, ArrayList<Integer>>();
//get subset by id
HashMap< ArrayList<Integer>, Integer> settoid = new HashMap< ArrayList<Integer>, Integer>();
//get id by subset
public TSPDP(int[][] graph){
this.graph = graph;
}
/**
* Solve Traveling Salesperson Probl em by Dynamic Programming
* @return the min length
* */
public int DP(){
int n = graph.length;
int[] vertex = new int[n-1];
int vertexid = 1;
for(int i = 0; i < vertex.length; i++) {
vertex[i] = vertexid;
vertexid++;
}
getsubsets(vertex);
int[][] D = new int[n][settoid.size()];//To record the distance
int[][] P = new int[n][settoid.size()];//To track the path
for(int i = 1; i < n; i++){
D[i][0] = this.graph[i][0];
}
for(int k = 1; k <= n - 2; k++){
for(int id = 0; id < idtoset.size(); id++){
ArrayList<Integer> subset = idtoset.get(id);
if(subset.size() != k)
continue;
for(int i = 1; i < n; i++){
if(subset.contains(i))
continue;
int min = 10000;
int value = 0;
for(int j : subset){
ArrayList<Integer> Aminusj = remove(subset, j);
int idj = settoid.get(Aminusj);
try{
value = this.graph[i][j] + D[j][idj];
}catch(Exception e){
System.out.print("Error!___");
System.out.println("i: " + String.valueOf(i) + " j: "+ String.valueOf(j));
int size = this.graph[i].length;
System.out.println(" graph.length: "+ String.valueOf(size));
System.out.println(" D.length: "+ String.valueOf(D.length));
}
if(value < min && value != 0){
min = value;
P[i][id] = j;
}
}
if(min < 9999);
D[i][id] = min;
}
}
}
ArrayList<Integer> Vminusv0 = new ArrayList<Integer>();
for(int i = 0; i < vertex.length; i++){
Vminusv0.add(vertex[i]);
}
int vminusv0id = settoid.get(Vminusv0);
int min = Integer.MAX_VALUE;
for(int j : Vminusv0){
ArrayList<Integer> Vminusv0vj = remove(Vminusv0,j);
int idj = settoid.get(Vminusv0vj);
int value = (this.graph[0][j]!=0 && D[j][idj]!=0) ? this.graph[0][j] + D[j][idj]:0;
if(value < min && value != 0){
min = value;
P[0][vminusv0id] = j;
}
}
if(min < 99999);
D[0][vminusv0id] = min;
generateOpttour(P, Vminusv0);
return D[0][vminusv0id];
}
private void printDP(int[][] D){
for(int i = 0; i < D.length; i++){
for(int j = 0; j < D[i].length; j++){
System.out.print(D[i][j]);
System.out.print(" ");
}
System.out.println();
}
}
/**
* Generate optimal tour by P, and print it
* @param P
* @param list containing all vertexes except V0
* */
public void generateOpttour(int[][] P, ArrayList<Integer> V){
String path = "1->";
ArrayList<Integer> Set = V;
int start = 0;
while(!Set.isEmpty()){
int id = settoid.get(Set);
String vertex = String.valueOf(P[start][id]+1);
path += vertex + "->";
Set = remove(Set, P[start][id]);
start = P[start][id];
}
path += "1";
System.out.println(path);
}
/**
* Get all subsets of a input set. And number subsets
* All results will be recorded in member variables
* @param set input set
* */
private void getsubsets(int[] set){
idtoset.clear();
settoid.clear();
int max = 1 << set.length; //how many sub sets
int id = 0;
for(int i=0; i<max; i++){
int index = 0;
int k = i;
ArrayList<Integer> s = new ArrayList<Integer>();
while(k > 0){
if((k&1) > 0){
s.add(set[index]);
}
k>>=1;
index++;
}
idtoset.put(id, s);
settoid.put(s, id);
id++;
}
}
/**
* Remove an input value in a list
* @param src source list
* @param n the value to be removed
* @return list after removing n
* */
private ArrayList<Integer> remove(ArrayList<Integer> src, int n){
ArrayList<Integer> dest = new ArrayList<Integer>();
int j = 0;
for(int i = 0; i < src.size(); i++){
int vertex = src.get(i);
if(vertex == n)
continue;
dest.add(vertex);
}
return dest;
}
public static void main(String[] args){
TSPDP test = new TSPDP(new int[][]{{0,16,16,7,13,6},
{16,0,9,5,19,7},
{16,9,0, 7,9,6},
{7,5, 7,0,7,7},
{13,19,9,7,0,13},
{6,7,6,7,13,0}});
int dis = test.DP();
System.out.println(dis);
}
}
本文通过具体例子详细介绍了如何使用动态规划解决旅行商问题(TSP)。从邻接矩阵和状态矩阵D的概念出发,阐述了动态规划的状态转移规律,并提供了求解最优路径的公式。最后,给出了Java代码实现的概述。
&spm=1001.2101.3001.5002&articleId=50920234&d=1&t=3&u=a67fcc16e27949e7be25c52e5dd8e070)
2万+

被折叠的 条评论
为什么被折叠?



