使用回溯法解决如下问题
问题:
某售货员要到若干城市去推销商品,已知各城市之间的路程。他要选定一条从驻地出发,经过每个城市一次,最后回到驻地的路线,使总的路程(或总旅费)最小。城市数量小于5个。
用回溯法解决问题时,首先要考虑如下三个问题。
(1)定义合适的解空间
因为解空间的大小对搜索效率有很大的影响,因此使用回溯法首先要定义合适的解空间,确定解空间包括解的组织形式和显约束。
解的组织形式:解的组织形式都规划为一个n元组{x1,x2,…xn},只是具体问题表达的含义不同而已。
显约束:显约束是对解分量的取值范围的限定,显约束可以控制解空间的大小。
(2)确定解空间的组织结构
解空间的组织结构通常用解空间树形象的表达,根据解空间树的不同,解空间分为子集树,排列树,m叉树等。
(3)搜索解空间
回溯法是按照深度优先搜索策略,根据隐约束(约束函数和限界函数),在解空间中搜索问题的可行解或最优解。当发现当前节点不满足求解条件时,就回溯,尝试其他路径。能进则进,进不了则换,换不了则退。
如果问题只是要求可行解,则只需要设定约束函数即可;如果要求最优解,则需要设定约束函数和限界函数。
解空间的大小和剪支函数的好坏是影响搜索效率的关键。
显约束可以控制解空间的大小,剪支函数决定剪支的效率。
所以回溯法解题的关键是设计有效的显约束和隐约束。
算法设计
(1)定义问题的解空间
旅行商问题解的形式为n元组:{x1,x2,…xi…xn},分量xi表示第i个要去的城市编号,城市的集合为S={1,2,3…n}。因为城市不可重复走,因此在确定xi时,前面走过的城市{x1,x2…xi-1}不可能再走,xi的取值为S-{x1,x2…xi-1},i=1,2,3…,n。
(2)解空间的组织结构
问题解空间是一颗排列树,树的深度为n=5。
3)搜索解空间
约束条件
用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]不等于∞表示城市i和城市j有边相连,能走通。
限界条件
cl<bestl,cl的初始值为0,bestl的初始值为+∞。
cl:当前已经走过的城市所用的路径长度
bestl:表示当前找到的最短路径的路径长度。
搜索过程
扩展节点沿着某个分支扩展时需要判断约束条件和限界条件,如果满足,则进入深一层继续搜索;如果不满足,则剪掉该分支。搜索到叶子节点时,即找到当前最优解。搜索直到全部的活结点变成死结点为止。
代码
import java.util.Scanner;
public class text8 {
static int INF=(int)1e7;//设置无穷大的值为10的七次方
static int N=100;
static int [][]g=new int[N][N];//地图的无向带权邻接矩阵
static int []x=new int[N];//记录当前路径
static int []bestx=new int[N];//记录当前最优路径
static int cl;//当前路径长度
static int bestl;//当前最短路径长度
static int n,m;//城市个数n,边数m
static void swap(int array[], int a, int b) {//交换函数
int temp;
temp = array[a];
array[a] = array[b];
array[b] = temp;
}
static void Traveling(int t) {
if (t > n) {//到达叶子节点
/*
推销货物的最后一个城市与住地城市有边相连并且路径长度比当前最优值小,说明找到了一条更好的路径,记录相关信息
*/
if (g[x[n]][1] != INF && (cl + g[x[n]][1] < bestl)) {
for (int j = 1; j <= n; j++) {
bestx[j] = x[j];
}
bestl = cl + g[x[n]][1];
}
} else {//没有到达叶子节点
for (int j = t; j <= n; j++) {//搜索扩展节点的所有分支
if (g[x[t - 1]][x[j]] != INF && (cl + g[x[t - 1]][x[j]] < bestl)) {//如果第t-1个城市与第t个城市有边相连并且有可能得到更短的路线
swap(x, t, j);//交换两个元素的值
cl = cl + g[x[t - 1]][x[t]];
Traveling(t+1);//从第t+1层的扩展结点继续搜索
//第t+1层搜索完毕,回溯到第t层
cl=cl-g[x[t-1]][x[t]];
swap(x,t,j);
}
}
}
}
//初始化函数
static void init() {
bestl = INF;
cl = 0;
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
g[i][j] = g[j][i] = INF;
for (int i = 0; i <= n; i++) {
x[i] = i;
bestx[i] = 0;
}
}
static void print(){
System.out.print("最短路径:");
for (int i=1;i<=n;i++){
System.out.print(bestx[i]+"---->");
}
System.out.println("1");
System.out.print("最短路径长度:"+bestl);
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int u,v,w;//u,v代表城市,w代表城市u,v之间的距离。
System.out.println("请输入城市数n:");
n=sc.nextInt();
init();
System.out.println("请输入城市之间的连线数:");
m=sc.nextInt();
System.out.println("请依次输入两个城市u,v以及之间的距离w:");
for (int i=1;i<=m;i++){
u=sc.nextInt();
v=sc.nextInt();
w=sc.nextInt();
g[u][v]=g[v][u]=w;
}
Traveling(2);//根据排列树的图,从第二层开始
print();
}
}
输入
5
9
1 2 3
1 4 8
1 5 9
2 3 3
2 4 10
2 5 5
3 4 4
3 5 3
4 5 20