【DFS/BFS】1466. 重新规划路径

本文探讨如何在已有的n个城市间路线网络中,通过最少的线路方向调整,确保所有城市都能通过现有路径访问城市0。涉及深度优先搜索和广度优先搜索的解决方案及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

(中等)n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线。因此,要想在两座不同城市之间旅行只有唯一一条路线可供选择(路线网形成一颗树)。去年,交通运输部决定重新规划路线,以改变交通拥堵的状况。
路线用 connections 表示,其中 connections[i] = [a, b] 表示从城市 a 到 b 的一条有向路线。
今年,城市 0 将会举办一场大型比赛,很多游客都想前往城市 0 。
请你帮助重新规划路线方向,使每个城市都可以访问城市 0 。返回需要变更方向的最小路线数。
题目数据保证每个城市在重新规划路线方向后都能到达城市 0 。

示例:
示例图片

输入:n = 6, connections = [[0,1],[1,3],[2,3],[4,0],[4,5]]
输出:3
解释:更改以红色显示的路线的方向,使每个城市都可以到达城市 0

解题思路

最重要的是理解题意,尤其这种经过长篇幅文字包装的题,更要静下心来掌握题意!
容易分析得到以下结论:

  1. n个结点,n - 1条边且所有结点均能到达 ‘0’ 结点,即每个结点的出度(图论中的知识点)为1, ‘0’ 结点除外;
  2. 进一步分析,若将题目中的有向图视为无向图,则此图一定是以 ‘0’ 结点为根节点的树结构
  3. 重新规划路径方向,即结点间的连结关系不改,只改连接方向
  4. 最后,以 ‘0’ 结点为起点,依循连接关系遍历所有结点,对错误方向计数即可。

代码实现

先看深度优先(DFS):

class Solution {
public:
    int minReorder(int n, vector<vector<int>>& connections) {
    	//统计记录入度的邻接链表
        vector<vector<int>> in(n);
        //统计记录出度的邻接链表
        vector<vector<int>> out(n);
        //邻接链表初始化
        for(auto conn : connections){
            in[conn[1]].push_back(conn[0]);
            out[conn[0]].push_back(conn[1]);
        }
        //访问数组,避免边重复访问:对于一条边上的两个顶点,A是入边,B是出边。导致重复访问使结果大于正确值。
        vector<bool> visited(n, false);
        //初始化规划次数
        int ans = 0;
        //以‘0’结点为起点
        dfs(0, ans, in, out, visited);
        return ans;
    }
    
    //深度优先搜索
    void dfs(int node, int& ans, vector<vector<int>>& in, vector<vector<int>>& out, vector<bool>& visited){
    	//访问过的结点不进行下一步递归
        if(visited[node]) return;
        visited[node] = true;
        //遍历当前结点入边的弧头结点
        for(int i : in[node]){
            dfs(i, ans, in, out, visited);
        }
        //遍历当前结点入边的弧尾结点,此时必为错误方向,计数+ 1!!!
        for(int i : out[node]){
            if(!visited[i]) ans++;
            dfs(i, ans, in, out, visited);
        }
    }
};

运行结果:

运行结果

深度优先的另一种实现思路:

class Solution {
public:
    int minReorder(int n, vector<vector<int>>& connections) {
        //有向图邻接矩阵
        vector<vector<int>> withDire(n);
        //无向图邻接矩阵
        vector<vector<int>> withOutDire(n);
        for(auto conn : connections){
            withDire[conn[0]].push_back(conn[1]);
            withOutDire[conn[0]].push_back(conn[1]);
            withOutDire[conn[1]].push_back(conn[0]);
        }
        vector<bool> visited(n, false);
        int ans = 0;
        dfs(withDire, withOutDire, visited, 0, ans);
        return ans;
    }

    void dfs(vector<vector<int>>& withDire, vector<vector<int>> &withOutDire, vector<bool>& visited, int node, int& ans){
        if(visited[node]) return;
        visited[node] = true;
        for(int i : withOutDire[node]){
            if(visited[i]) continue;
            //无需分出、入度考虑,代码逻辑统一
            if(find(withDire[node].begin(), withDire[node].end(), i) != withDire[node].end()) ans++;
            dfs(withDire, withOutDire, visited, i, ans);
        }           
    }
};

再看广度优先(BFS):
通过建立边的索引目录,并且相较于上述DFS,以边为中心,极大减少了时间、内存开销。

class Solution {
public:
    int minReorder(int n, vector<vector<int>>& connections) {
        int ans = 0;
        //创建顶点--边的关系的索引
        vector<vector<int>> edgeIndex(n);
        //初始化索引
        for(int i = 0; i < connections.size(); i++){
            edgeIndex[connections[i][0]].push_back(i);
            edgeIndex[connections[i][1]].push_back(i);
        }
        //以边为核心,创建访问数组
        vector<bool> visited(n - 1);
        //广度优先
        queue<int> que;
        que.push(0);
        while(!que.empty()){
            int node = que.front();
            que.pop();
            for(int i : edgeIndex[node]){
            	//重复访问边,直接跳过当前循环
                if(visited[i]) continue;
                visited[i] = true;
                //当前结点为边的弧头,必为错误方向!
                if(node == connections[i][0]){
                    ans++;
                    que.push(connections[i][1]);
                }else{
                    que.push(connections[i][0]);
                }
            }
        }
        return ans;
    }
};

运行结果
广度优先的代码里,创建索引是亮点,也是性能提升的关键。避免了像深度优先开辟额外空间创建各种类型的邻接矩阵,且避免在递归中使用std::find()这种时间复杂度为O(n)的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值