旅行商问题,常被成为旅行推销员问题,是指一名推销员要拜访多个地点,如何找到再拜访每个地点一次后再回到起点的最短路径.
进一步的抽象,可以转化为图论的问题,将每个城市看成图 G(V,E) 中的一个顶点,则旅行商问题可以转化为从一个顶点s出发,找到一条最短的路径从s出发,经过所有的顶点,最后回到s.最简单的解法就是枚举,枚举所有的路径,计算其每条路径的长度,取最短的即可.枚举最有的路径则其有 (n−1)! 条路径,如果n非常大,则解的空间将非常大,我们可以使用一个棵树,那么这棵树将非常庞大.
既然我们可以用一个树表示类似问题的所有可能解,那么现在的问题就变成了从解空间树中找到最短的路径,也就是树的搜索问题,搜索每一条路,找到最短的路径.但是再进一步思考一下,其实很多分枝,我们在没有搜索到叶子节点的时候就可以通过一些已知的条件就可以判断这条路不可能是最短的路.回溯法就是这个思路,按照深度优先的策略搜索解空间树,通过一些约束条件或者边界条件判断是否继续搜索下去,如果不满足约束条件则直接返回.
因此回溯法求解问题的过程大致如下:
>
- 确定问题的解空间(用树表示)
- 确定问题的约束条件和边界条件
- 深度优先搜索解空间幷根据约束条件和边界条件进行裁剪
再一次的回到旅行商问题(TSP),以此为例看一下回溯法的解题过程
确定解空间
假设有四个城市A,B,C,D, 从A出发,求经过所有顶点后回到A的最短路径,我们用树表示所有的解,前面已经说过是一个 (n−1)! 排列树,如下图所示:
d
确定约束条件和边界条件
假设用cl表示从A出发到当前节点的的路径长度,fl表示当前最短的环路长度.k表示路径中第k个节点,$xk 表示第k个点选择的城市编号(A−D编号为1−4),w为邻接矩阵.则当k<n时,如果 x_k 添加到当前路径已经比当前最优路径大,则不用继续搜索,因此约束条件可以表示为: $cl+w[x{k-1},xk] <= fl
上面便是约束条件和边界条件
深度优先搜索和剪枝
搜索的问题就比较简单了,按照DFS的思路搜索就行了,当然特定的问题可以进行一些优化.然后搜索的过程使用约束条件的边界条件进行剪枝和更新最有路径.
下面是伪代码:
用java实现如下所示
/** * 判断第k个数是否不同与前k-1个数 * @param k * @return bool */ private boolean nextValue(int k){ int i = 0; while(i < k){ if(x[k] == x[i]){ return false; } i += 1; } return true; } /** * 第k条路径选择 * @param k */ private void backUp(int k){ if(k==N-1){ for (int j=1;j<=N;j++){ x[k] = Math.floorMod(x[k]+1, N); if(nextValue(k) && cl + weight[x[k-1]][x[k]] + weight[x[k]][0] < fl) {//如果最短路径,更新最优解 fl = cl + weight[x[k - 1]][x[k]] + weight[x[k]][0]; for (int i = 0; i < N; i++) { bestX[i] = x[i]; } } } }else{ for(int j=1; j<=N; j++){ x[k] = Math.floorMod(x[k]+1, N); if(nextValue(k) && cl+weight[x[k-1]][x[k]] <= fl){ //此路可行 cl += weight[x[k-1]][x[k]]; backUp(k+1); cl -= weight[x[k-1]][x[k]]; } } } } public void solve(){ int k = 1; //第0个顶点是固定的,从第一个顶点开始选择 cl = 0; fl = Integer.MAX_VALUE; backUp(k); } |
完整代码下载见:TSP回溯法
/** | |
* 递归求解tsp | |
* Created by devin on 15-11-20. | |
*/ | |
public class BackTSP { | |
private static int N = 4; | |
private int cl; //当前路径的长度 | |
private int fl; //当前只当的最大路径长度 | |
private int weight[][] = { | |
// {0, 30, 6, 5}, | |
// {30, 0, 4, 10}, | |
// {6, 4, 0, 20}, | |
// {5, 10, 20, 0} | |
{0, 13, 8, 9}, | |
{13, 0, 3, 15}, | |
{8, 3, 0, 20}, | |
{9, 15, 20, 0} | |
}; | |
private int x[] = new int[4]; | |
private int bestX[] = new int[4]; | |
public int[] getBestX() { | |
return bestX; | |
} | |
public int getMinLen(){ | |
return fl; | |
} | |
/** | |
* 判断第k个数是否不同与前k-1个数 | |
* @param k | |
* @return bool | |
*/ | |
private boolean nextValue(int k){ | |
int i = 0; | |
while(i < k){ | |
if(x[k] == x[i]){ | |
return false; | |
} | |
i += 1; | |
} | |
return true; | |
} | |
/** | |
* 第k条路径选择 | |
* @param k | |
*/ | |
private void backUp(int k){ | |
if(k==N-1){ | |
for (int j=1;j<=N;j++){ | |
x[k] = Math.floorMod(x[k]+1, N); | |
if(nextValue(k) && cl + weight[x[k-1]][x[k]] + weight[x[k]][0] < fl) {//如果最短路径,更新最优解 | |
fl = cl + weight[x[k - 1]][x[k]] + weight[x[k]][0]; | |
for (int i = 0; i < N; i++) { | |
bestX[i] = x[i]; | |
} | |
} | |
} | |
}else{ | |
for(int j=1; j<=N; j++){ | |
x[k] = Math.floorMod(x[k]+1, N); | |
if(nextValue(k) && cl+weight[x[k-1]][x[k]] <= fl){ | |
//此路可行 | |
cl += weight[x[k-1]][x[k]]; | |
backUp(k+1); | |
cl -= weight[x[k-1]][x[k]]; | |
} | |
} | |
} | |
} | |
public void solve(){ | |
int k = 1; //第0个顶点是固定的,从第一个顶点开始选择 | |
cl = 0; | |
fl = Integer.MAX_VALUE; | |
backUp(k); | |
} | |
} |