回溯法-旅行商问题

问题:

某售货员要到若干城市去推销商品,已知各城市之间的路程。他要选定一条从驻地出发,经过每个城市一次,最后回到驻地的路线,使总的路程(或总旅费)最小。

问题分析:

现在我们从城市A出发,要去B,C,D,E共四个城市,按上面的顺序给城市编号1~5,每个城市用一个节点表示,可以直接到达的城市有连线,连线上的数字代表两个城市之间的路程(旅费),那么要去的城市地图就转换成了一个无向带权图。如图

回溯法旅行商问题java 回溯法旅行商问题画图_最短路径


在无向带权图G(V,E)中,节点代表城市,连线上的数字代表城市之间的路径长度,我们从1号节点出发,先走哪些景点,后走哪些城市呢?只要是可以直接到达的,即有边相连的,都是可以走的,问题就是要找出从出发地开始的一个城市排列,按照这个顺序旅行,不重复地走遍所以城市回到出发地,所经过的路径长度是最短的。

因此,问题的解空间是一颗排列树。显然,对于任何给定的一个无向带权图,存在某两个城市之间没有直接路径的情况。也就是说,并不是任何一个城市排列搜索一条可行路径(问题的可行解),因此需要设置约束条件,判断排列中相连的两个城市之间是否有边相连,有边的则可以走通;反之,是不可行路径,另外,在所有的可行路径中,要求找出一条最短路径,因此需要设置限界条件。

算法设计

(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。如图

回溯法旅行商问题java 回溯法旅行商问题画图_搜索_02

(3)搜索解空间

约束条件

用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]不等于∞表示城市i和城市j有边相连,能走通。

限界条件

cl<bestl,cl的初始值为0,bestl的初始值为+∞。

cl:当前已经走过的城市所用的路径长度

bestl:表示当前找到的最短路径的路径长度。

搜索过程

扩展节点沿着某个分支扩展时需要判断约束条件和限界条件,如果满足,则进入深一层继续搜索;如果不满足,则剪掉该分支。搜索到叶子节点时,即找到当前最优解。搜索直到全部的活结点变成死结点为止。

回溯法旅行商问题java 回溯法旅行商问题画图_搜索_03

详细过程请看陈小玉老师的《趣学算法》

代码

import java.util.Scanner;
public class Main {
    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();
    }
}

运行示例

回溯法旅行商问题java 回溯法旅行商问题画图_System_04