[Hbfs] lc815. 公交路线(建图+多源bfs+bfs最短路+思维+好题)

12 篇文章 0 订阅

1. 题目来源

链接:815. 公交路线

相似:[最短路] aw920. 最优乘车(单源最短路建图+bfs最短路模型+知识理解+好题)

2. 题目解析

好题,之前做过,但似懂非懂。


问题抽象:

每一个公交线路都是一个环,如果两环有公共点,则这两个环相连。

在这里插入图片描述

本题是求最少经过几个公交车,从起点 S 到达终点 T,即等价于从起点经过最少的环到达终点。

则可以将每个环看成一个点,其公共点就代表两点之间有边相连。在此依据题意,边为无向边。
在这里插入图片描述
故,在此边权为 1,求起点到终点的最短距离,那么就是 bfs 最短路模型了。


建图:

  • 如何建立点边之间的关系是本题建图一大难点,方法也有很多,在此具体实现如下:
    • 将环抽象成点。
    • 环相连,等价于两环中存在公共点,则一个公共点提供一条边。
    • 由于起点可能是多个环的公共点,且从任意一个环出发都是合理的,所以本题是多源 bfs 问题,需要将起点所在环全部入队。
    • bfs 一开始存的为起点所在的环编号,然后遍历起点所在环内的所有点,其中这些点有可能通向其它的环,如果其它的环还未被拓展到的话,那么说明从该环到另一个环的最短路就被找到了。就需要将该点所通到的其他所有环全部加入队列,进行遍历各个环中的所有点。
    • bfs 最短路,第一次遍历到的一定是最短的。可以拿距离判断,也可以拿非法值来判断。即针对 dist 数组有两种初始化方式,dist[i]=-1dist[i]=1e9,前者在更新时仅需判断 dist[y]==-1 即可判断环 y 是否是第一次更新,后者需要判断距离,即 dist[y]>dist[x]+1 这个也是象征性的判断,因为不存在多次更新的情况,bfs 第一次遍历第一次更新一定是最小的
    • 在遍历每个环中的各个点时,顺便判断终点。
    • 在遍历每个环中的各个点时,bfs 操作下,第一次遇到该点就会将它的所有出边全部进行拓展,且第一次遇见该点时即为最短的距离。所以,拓展完该点的信息后,就需要将该点进行删除,避免重复遍历。

注意点:

  • bfs 拓展好处是:第一次遍历到的点,一定是最短的,且由该点进行下一步更新信息时,信息也是完全的。所以该点不应该再被二次遍历,是没有实际意义的,也不可能是最短路的所处路径。故,为了避免重复遍历,可以将遍历过的点直接进行删除。两者性能差距如下:
    在这里插入图片描述
  • 这个建图方式挺需要注意的。将环看成一个个最短路点,环中包含所有的环内节点,环1 走到 环2 时才去更新最短路,路径+1。每次将环内的所有节点拿出来,看看这些节点有没有关联到其他环,关联到的话,就进行距离更新,再将下一个环入队,去看它环内的所有节点。

时间复杂度: O ( n + m ) O(n+m) O(n+m),每个点只会被遍历一次

空间复杂度: O ( n ) O(n) O(n)


标程:

class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        if (source == target) return 0;         // 起点终点相同
        int n = routes.size();
        unordered_map<int, vector<int>> g;
        vector<int> dist(n, 1e9);
        queue<int> q;

        // 建图
        for (int i = 0; i < n; i ++ ) {
            for (auto x : routes[i]) {
                if (x == source) {              // 多源,初始化、入队
                    dist[i] = 1;
                    q.push(i);                  // 若起点在多个环上,则这些环都能当做起点使用,故是多源问题
                }
                g[x].push_back(i);              // 每个点所在的环形公交路线
            }
        }

        // bfs
        while (q.size()) {
            auto t = q.front(); q.pop();

            for (auto x : routes[t]) {
                if (x == target) return dist[t];
                for (auto y : g[x]) {
                    if (dist[y] > dist[t] + 1) {
                        dist[y] = dist[t] + 1;
                        q.push(y);
                    }
                }
                g.erase(x);
            }
        }

        return -1;
    }
};

详细注释版:

class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        if (source == target) return 0;  // 不可少,题目要求

        int n = routes.size();
        unordered_map<int, vector<int>> g;
        queue<int> q;
        vector<int> dist(n, -1);        // 采用 -1 表示未被 bfs 遍历过

        for (int i = 0; i < n; i ++ ) {
            for (auto e : routes[i]) {
                if (e == source) {      // 起点所在环入队,环距离初始化为 1
                    q.push(i);
                    dist[i] = 1;
                }
                g[e].push_back(i);      // 各个点存放其所关联到的所有的环
            }
        }

        while (q.size()) {
            auto t = q.front(); q.pop();
            
            for (auto e : routes[t]) {      // 遍历当前环中所有的点
                if (e == target) return dist[t]; // 如果等于目标点,则找到答案,且由于 bfs 性质,一定为最短路
                for (auto ne : g[e]) {      // 遍历这些点的其他关联的环,更新距离
                    if (dist[ne] == -1) {   // 如果 ne 环没有被遍历过
                        dist[ne] = dist[t] + 1; // 则说明可以从 e 点走到 ne 环,距离加1
                        q.push(ne);         // ne 环入队,下一次遍历它的所有的环中节点
                    }
                }
                // 优化点,可有可无。加上性能提高一倍
                g.erase(e);  // 如果点 e 已经被遍历过,那么点 e 的距离、关联环都已经被更新,信息已经全部获取到了,避免重复遍历,直接从哈希表中删除。
            }
        }
        
        return -1;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值