回溯法-详解旅行商问题(java)

回溯法-旅行商问题

问题:

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

问题分析:

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

(3)搜索解空间
约束条件
用二维数组g[][]存储无向带权图的邻接矩阵,如果g[i][j]不等于∞表示城市i和城市j有边相连,能走通。
限界条件
cl<bestl,cl的初始值为0,bestl的初始值为+∞。
cl:当前已经走过的城市所用的路径长度
bestl:表示当前找到的最短路径的路径长度。
搜索过程
扩展节点沿着某个分支扩展时需要判断约束条件和限界条件,如果满足,则进入深一层继续搜索;如果不满足,则剪掉该分支。搜索到叶子节点时,即找到当前最优解。搜索直到全部的活结点变成死结点为止。
搜索过程

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

代码

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();
    }
}

运行示例

在这里插入图片描述

回溯法(Backtracking)是一种暴力搜索算法,它尝试在所有可能的解中寻找正确的解。回溯法通常用于组合问题,其中我们尝试从一组可能的解中选择一个最佳解。 地图着色问题是指在地图上给不同的区域着不同的颜色,使得相邻的区域颜色不同。这是一个经典的组合问题,可以使用回溯法解决。 下面是 Java 代码实现: ```java public class MapColoring { // 地图邻接矩阵 private int[][] map; // 区域颜色 private int[] color; // 区域数量 private int n; public MapColoring(int[][] map, int n) { this.map = map; this.n = n; this.color = new int[n]; } public void colorMap() { if (backtrack(0)) { printSolution(); } else { System.out.println("No solution exists."); } } private boolean backtrack(int area) { // 如果所有区域都已经着色,返回 true if (area == n) { return true; } // 尝试给当前区域着色 for (int c = 1; c <= 4; c++) { if (isSafe(area, c)) { color[area] = c; // 递归着色下一个区域 if (backtrack(area + 1)) { return true; } // 如果不能着色,则回溯 color[area] = 0; } } return false; } private boolean isSafe(int area, int c) { // 检查相邻区域是否有着相同的颜色 for (int i = 0; i < n; i++) { if (map[area][i] == 1 && c == color[i]) { return false; } } return true; } private void printSolution() { System.out.println("Solution exists:"); for (int i = 0; i < n; i++) { System.out.println("Area " + (i + 1) + " is colored with " + color[i]); } } } ``` 在上述代码中,`backtrack()` 方法用于尝试给当前区域着色,并递归着色下一个区域,如果不能着色,则回溯。`isSafe()` 方法用于检查相邻区域是否有着相同的颜色。`printSolution()` 方法用于输出结果。 如果地图可以被正确着色,则 `colorMap()` 方法将打印结果。否则,它将打印“无解”。 下面是一个例子: ```java public static void main(String[] args) { int[][] map = { {0, 1, 1, 1}, {1, 0, 1, 0}, {1, 1, 0, 1}, {1, 0, 1, 0} }; MapColoring mc = new MapColoring(map, 4); mc.colorMap(); } ``` 输出结果为: ``` Solution exists: Area 1 is colored with 1 Area 2 is colored with 2 Area 3 is colored with 3 Area 4 is colored with 2 ``` 在这个例子中,地图可以被正确着色,其中每个区域都着有不同的颜色。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值