图论:Tarjan算法的使用,找连通分量、割点、桥

感谢B站:"邋遢大哥233"的教学视频

Tarjan算法

Tarjan算法是处理有关图的连通性问题的算法。下面是有关图的连通性的一些基本概念:

图的连通性

连通图(Connected Graph):无向图中任意两个顶点都是连通的,即存在一条路径连接它们.
强连通图(Strongly Connected Graph):有向图中任意两个顶点都是强连通的,即存在从一个顶点到另一个顶点的路径,反之亦然.
连通分量(Connected Component):无向图中的最大连通子图.
强连通分量(Strongly Connected Component):有向图中的最大强连通子图.
割点(Cut Vertex):删除后会使图的连通性降低的顶点.如果去除某个顶点及与其相关的边后,连通分量的数量增加,则该点为割点。
桥(Bridge):删除后会使图的连通性降低的边.如果去除某条边后,连通分量的数量增加,则该点为桥。
在有向图中tarjan算法可以求连通分量,而在无向图中它可以求割点和桥。

个人对算法的理解

tarjan算法通过找强连通分量中的环的方法来求强连通分量。它通过深度遍历按遍历的顺序给每个顶点标上一个时间戳。如果某个顶点U是一个强连通分量中的顶点,那么深度遍历它的相邻顶点。在这次遍历的顶点中,必然有一些顶点它们的邻接顶点的时间戳要小于等于U的时间戳。因为在强连通分量中,从U出发必然会回到U。
所以,用两个数组dfn和low分别记录当前顶点的时间和从给点出发所能达到的最早的点的时间。如果dfn[u] == low[u] 说明不能回到更早的顶点;如果dfn[u] < low[u] 说明可以回到更早的顶点,存在强连通分量。
所以算法的关键在于时间戳,通过判断是否能回到过去,来判断是否存在环。把空间问题转化为了时间问题,很巧妙的设计。
顺便说一下,算法的时间复杂度与图的表示有关,如果用邻接矩阵,时间是:O(V^2);如果用邻接表,时间是:O(V+E)。V是顶点数,E是边数.

代码

本题以leetcode1192 查找集群内的关键连接 为背景,原题目是求无向图的桥。

求有向图强连通分量

cpp 复制代码
class Solution {
    vector<int> dfn, low, inStack; // 记录当前时间,可以达到的最早时间,和标记是否已遍历
    stack<int> S;  // 记录节点
    int step = 1;  // 记录时间
    vector<vector<int>> graph, sccMap; // 分别邻接表, 记录连通分量
    void tarjan(int u){
        dfn[u] = low[u] = step ++;
        S.push(u);
        inStack[u] = 1; // 标记时间和入栈

        for(int v : graph[u]){
            if(dfn[v] == 0) tarjan(v); // 深度遍历为遍历的点
            if(inStack[v]) low[u] = min(low[u], low[v]);  // 记录可以到达的最早时间
        }

        if(dfn[u] == low[u]){ // 不能到达更早的点,说明是一个强连通分量的根
            int v;
            vector<int> scc; // 记录连通分量
            do{
                v = S.top();
                S.pop();
                //scc.push_back(v);
            }while(v != u);
            sccMap.push_back(scc);
        }
    }

public:
    vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
        dfn.resize(n, 0);
        low.resize(n, 0);
        inStack.resize(n, 0);
        graph.resize(n);

        for(const auto &edge: connections){// 假设这是有向图
            graph[edge[0]].push_back(edge[1]);
        }

        tarjan(0);
        return sccMap;
    }
};

求无向图的割点

如果当节点不为根,且子节点不能到达比当前顶点更早的节点,去掉前期节点,图必有早于当前节点和晚于当前节点的两部分。
如果当前节点有两个子节点去掉当前节点,两个子节点不连通
cpp 复制代码
class Solution {
    vector<int> dfn, low, inStack, cuts; // 记录当前时间,可以达到的最早时间,标记是否已遍历和割点
    stack<int> S;
    int step = 1;  // 时间
    vector<vector<int>> graph; // 邻接表
    void tarjan(int u, int f){
        dfn[u] = low[u] = step ++;
        S.push(u);
        inStack[u] = 1; // 标记时间和入栈
		
		int coutSon = 0;
        for(int v : graph[u]){
            if(v == f) continue; // 避免回到父节点
            if(dfn[v] == 0){
            	tarjan(v, u);
            	countSon ++
            }
            if(inStack[v]) low[u] = min(low[u], low[v]);
        }
        if(countSon > 1){
        	cuts.push_back(u);
        	return;
        }
        if(dfn[u] == 1) return;
        for(int v : graph[u]){
        	if(dfn[u] <= low[v]) cuts.push_back(v);
        }
    }

public:
    vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
        dfn.resize(n, 0);
        low.resize(n, 0);
        inStack.resize(n, 0);
        graph.resize(n);

        for(const auto &edge: connections){
            graph[edge[0]].push_back(edge[1]);
            graph[edge[1]].push_back(edge[0]);
        }

        tarjan(0, -1);
        return cuts;
    }
};

求无向图的桥

当边的下一个节点无法到达,比边的上一个更早的节点,该边为桥。
cpp 复制代码
class Solution {
    vector<int> dfn, low, inStack;
    stack<int> S;
    int step = 1;
    vector<vector<int>> ans, graph;
    void tarjan(int u, int f){
        dfn[u] = low[u] = step ++;
        S.push(u);
        inStack[u] = 1;

        for(int v : graph[u]){
            if(v == f) continue;
            if(dfn[v] == 0) tarjan(v, u);
            if(inStack[v]) low[u] = min(low[u], low[v]);
            if(dfn[u] < low[v]) ans.push_back({u, v});
        }
    }

public:
    vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
        dfn.resize(n, 0);
        low.resize(n, 0);
        inStack.resize(n, 0);
        graph.resize(n);

        for(const auto &edge: connections){
            graph[edge[0]].push_back(edge[1]);
            graph[edge[1]].push_back(edge[0]);
        }

        tarjan(0, -1);
        return ans;
    }
};
相关推荐
Felix_12151 小时前
2025 西电软工数据结构机考 Tip (By Felix)
算法
飞yu流星2 小时前
C++ 函数 模板
开发语言·c++·算法
pursuit_csdn2 小时前
力扣 74. 搜索二维矩阵
算法·leetcode·矩阵
labuladuo5202 小时前
洛谷 P8703 [蓝桥杯 2019 国 B] 最优包含(dp)
算法·蓝桥杯·动态规划
Milk夜雨3 小时前
C语言冒泡排序教程简介
数据结构·算法·排序算法
委婉待续4 小时前
redis的学习(三)
数据结构·算法
一直学习永不止步4 小时前
LeetCode题练习与总结:随机翻转矩阵--519
java·数学·算法·leetcode·哈希表·水塘抽样·随机化
xiao--xin4 小时前
LeetCode100之组合总和(39)--Java
java·开发语言·算法·leetcode·回溯
IT猿手5 小时前
部落竞争与成员合作算法(CTCM)求解5个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
深度学习·算法·机器学习·matlab·无人机·无人机、
GISer_Jing5 小时前
React中 Reconciliation算法详解
前端·算法·react.js