【OD机试】校车路线规划

题目描述

阿甘是一名乡下学校的校车司机,每天放学都需要送学生回家,送完所有学生后,还需要把车开回学校。
由于是乡下,因此学校建在了乡村的中心位置,可以保证每个学生家都有路线直达学校。但是不同学生的家之间不一定有直达路线。
现在你已经统计好了每个学生家直达学校的路线距离,以及有直达路线关联的两个学生家之间的距离。
请你找出一条最短行车路线,这条路线起点和终点都要求是学校,并且中间需要经过所有学生的家。(假设学校的编号为0,学生的编号为1~x)。

输入描述

第一行输入学生数量 x。学生数量不会高于校车核载规定的15人。
第二行输入 x 个正整数,第 i 个正整数表示编号 i 的学生家到学校直达路线的距离,该距离不大于100000米,以空格分割。i 从 1 开始编号。
第三行输入多个三元组,三元组含义是:学生编号a,学生编号b,学生a家到学生b家的距离c。三元组内以逗号分隔,三元组之间以空格分隔。

输出描述

输出一个最短行车路线,编号之间以 "->" 分隔

用例

输入3
100 300 500
1,2,50 2,3,1000
输出0->1->2->1->0->3->0
说明

最短路线长度为1300。

题目解析

本题需要我们帮助司机找到一条最短路径,该最短路径需要满足:

  • 从学校(编号0)出发,最终返回学校
  • 经过所有学生家位置,每个学生家位置可以经过不止一次

首先,这题需要我们经过所有学生家位置,因此我们对 1~x 学生编号求解全排列,每一个全排列即代表一种策略路径,比如x=3,则有以下策略路径:

  • 1->2->3
  • 1->3->2
  • 2->1->3
  • 2->3->1
  • 3->1->2
  • 3->2->1

我们在这些全排列首尾加上0,即可得所有策略路径:

  • 0->1->2->3->0
  • 0->1->3->2->0
  • 0->2->1->3->0
  • 0->2->3->1->0
  • 0->3->1->2->0
  • 0->3->2->1->0

由于本题的 1 ≤ x ≤ 15 ,因此全排列求解不会超时。

需要注意的是:上面策略路径中,比如:0->2->3->1->0,只是关键点路径 keyPath,并非实际路线 fullPath,比如 3->1 并非 3 直达 1,而是可能需要 3 经过一些中转点后到 1,才能实现最短路。

求解图中任意两点间的最短距离,最佳策略是使用floyd算法。

floyd算法:最短路径算法全套(floyed+dijstra+Bellman+SPFA)_哔哩哔哩_bilibili

JAVA实现

import java.util.*;
 
public class Main {
    static int x;
    static int[][] dist;
    static int[][] path;
 
    static int minDis = Integer.MAX_VALUE; // minDis记录经过所有点后回到出发点的最短距离
    static ArrayList<Integer> keyPath = new ArrayList<>();
 
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
 
        x = sc.nextInt();
 
        // floyd算法需要基于dist和path矩阵求解
        // dist[i][j] 用于记录点 i->j 的最短距离,初始时等价于邻接矩阵
        dist = new int[x + 1][x + 1];
        // path[i][j] 用于记录点 i->j 最短距离情况下需要经过的中转点,初始时默认任意两点间无中转点,即默认path[i][j] = -1
        path = new int[x + 1][x + 1];
 
        for (int i = 0; i < x + 1; i++) {
            for (int j = 0; j < x + 1; j++) {
                // 初始时默认i,j不相连,即i,j之间距离无穷大
                if (i != j) {
                    dist[i][j] = Integer.MAX_VALUE;
                }
                path[i][j] = -1;
            }
        }
 
        for (int i = 1; i <= x; i++) {
            dist[0][i] = sc.nextInt();
            dist[i][0] = dist[0][i];
        }
 
        while (sc.hasNext()) {
            int[] tmp = Arrays.stream(sc.next().split(",")).mapToInt(Integer::parseInt).toArray();
            int a = tmp[0];
            int b = tmp[1];
            int c = tmp[2];
            dist[a][b] = c;
            dist[b][a] = c;
        }
 
        // floyd算法调用
        floyd();
 
        // 全排列模拟经过所有点的路径
        dfs(0, 0, new boolean[x + 1], new LinkedList<>());
 
        keyPath.add(0, 0);
        keyPath.add(0);
 
        StringJoiner fullPath = new StringJoiner("->");
 
        for (int i = 1; i < keyPath.size(); i++) {
            int start = keyPath.get(i - 1);
            int end = keyPath.get(i);
 
            while (true) {
                fullPath.add(start + "");
                if (path[start][end] == -1) break;
                start = path[start][end];
            }
        }
 
        fullPath.add("0");
 
        System.out.println(fullPath);
    }
 
    // floyd算法求解图中任意两点之间的最短路径
    public static void floyd() {
        for (int k = 0; k < x + 1; k++) {
            for (int i = 0; i < x + 1; i++) {
                for (int j = 0; j < x + 1; j++) {
                    // newDist是经过k后,i->j的距离
                    int newDist = dist[i][k] + dist[k][j];
                    // 如果newDist是i->j的更短路径
                    if (newDist < dist[i][j]) {
                        // 则更新i->j的最短距离
                        dist[i][j] = newDist;
                        // 且此更短距离需要经过k, path[i][j]即记录 i->j 最短距离下需要经过点 k
                        path[i][j] = k;
                    }
                }
            }
        }
    }
 
    /**
     * 找一条经过所有点的最短路径,我们可以求解所有点形成的全排列,每一个全排列都对应一条经过所有点的路径,只是经过点的先后顺序不同 //
     * 求某个全排列过程中,可以通过dist数组,累计上一个点i到下一个点j的最短路径dist[i][j]
     *
     * @param pre         上一个点, 初始为0,表示从快递站出发
     * @param sum         当前全排列路径累计的路径权重
     * @param used        全排列used数组,用于标记哪些点已使用过
     * @param permutation 1~x的全排列
     */
    public static void dfs(int pre, int sum, boolean[] used, LinkedList<Integer> permutation) {
        if (permutation.size() == x) {
            // 此时pre是最后一个学生编号,送完最后一个学生后,司机需要回到学校,因此最终累计路径权重为 sum + dist[pre][0]
            // 我们保留最小权重路径
            int dis = sum + dist[pre][0];
            if (dis < minDis) {
                minDis = dis;
                keyPath = new ArrayList<>(permutation);
            }
            return;
        }
 
        for (int i = 1; i <= x; i++) {
            if (used[i]) continue;
 
            used[i] = true;
            permutation.add(i);
 
            dfs(i, sum + dist[pre][i], used, permutation);
 
            permutation.removeLast();
            used[i] = false;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值