Tarjan算法求割边——leetcode1192

本文学习自 Tarjan算法:求解图的割点与桥(割边),以leetcode1192题为练习讲述割边的求法。

 

 一、割边的定义

在无向图中,若去掉某条边会导致图中的连通分量增加,则这条边称为割边(或称为桥)。

而连通分量,就是一个图的某个子图,该子图中的所有顶点互相连通(任意两个顶点之间都存在可以互相到达的路径),而这些顶点都与子图之外的其他顶点不连通。

二、Tarjan算法原理

Tarjan算法是基于深度优先搜索的算法。

  • 进行DFS时,若我们从节点U出发,访问到的下一个还未被访问到的节点V,则称U是V的父节点,V是U的子节点。
  • 在访问到节点U之前所有已经访问过的节点,称为U的祖先节点。
  • 通过上面的定义可知,从节点V出发访问到节点U有两种方式,一种是通过U-V之间的边,另一种是V先访问U的祖先节点,再通过U的祖先节点访问到U。(U和其祖先节点之间必有互相连通的路径)
  • 若我们删去U-V之间的边之后,我们再也找不到从V出发访问到U的路径,则说明删去U-V这条边后整个图的连通分量增加了,即U-V为图的割边。即节点V无法访问到节点U的祖先节点

综上,在DFS中若我们能从节点V出发(不通过U)访问到其父节点U的祖先节点则说明U-V这条边不是割边,否则U-V是割边

三、Tarjan算法的实现

实现Tarjan算法需要定义两个重要的数组:

  • dfn数组:数组下标表示节点编号,数组的值表示下标对应的节点的遍历顺序(也可以看作时间戳),表明节点被访问的顺序。如设定初始时间戳为1,然后每访问到一个未被访问过的节点,就把当前时间戳赋值给表示该节点的dfn数组,再令当前时间戳+1。因此,子节点的dfn值一定比其父节点(祖先节点)的dfn值大一个节点被访问后其dfn值即被确定下来,不再改变
  • low数组:数组下标表示节点编号,数组的值表示下标对应的节点 不通过其父节点 就能访问到的 最早被访问到的节点的时间戳。我们每次只根据节点的子节点更新该值,所以有时候low的值未必是我们以全局的视角去看图时得到的最早被访问的节点的时间戳。
  • 初始时每个点的dfn值和low值都应该一样。
  • 在同一个连通分量中,若我们从V出发访问到的下一个节点已经访问过,且该节点不是V的父节点U,那么该节点必定是U的祖先节点(连通的特点)。设该节点为W,若dfn[W]
  • 当从节点U的子节点V回溯到U时,我们比较low[U]和low[V],若low[V]小于low[U]则我们需要更新low[U]为low[V],因为根据low数组的定义,节点U能不通过其父节点(而通过其子节点V)访问到U的祖先节点。此时,因为我们不会再遍历到U-V这条边了,所以我们检查,如果low[V]大于dfn[U],则说明节点V不通过节点U能访问到的最早的节点 的被访问时间比节点U的晚,进一步说明节点V无法访问到节点U的祖先节点,因此边U-V是割边。 
  • 有时候我们可以根据需要决定要不要设置一个parent数组来记录节点与其对应的父节点,如parent[v]=u表示节点V的父节点为U。

Tarjan算法虽然涉及到回溯,但其保证了每个节点实际上仅被“完全”访问一次,因此时间复杂度为O(N)【若此处有错请各位大佬指出】

题目实战:Leetcode-1192.查找集群内的[关键连接]

题目大意是给你一个无向图,两点之间没有重复的边,要求找出图的所有割边。

class Solution {
public:
    vector> ans;
    //定义Tarjan算法需要的数据
    vector dfn,low,vis,parent;
    int time=1; //Tarjan需要的时间戳
    //定义邻接矩阵
    vector> graph;
    
    vector> criticalConnections(int n, vector>& connections) {
        //定义数据结构大小
        dfn.assign(n,0),low.assign(n,0),vis.assign(n,0),parent.assign(n,-1);
        graph.resize(n);
        //构造邻接矩阵
        for(vector connection:connections){
            int u=connection[0],v=connection[1];
            graph[u].push_back(v),graph[v].push_back(u);
        }
        //遍历图的所有节点
        for(int i=0;idfn[u])   //判断是否为割边
                    ans.push_back({u,v});
            }
            else{   //访问到下一个节点不是父节点,但又是已经被访问过的,所以该节点的时间戳肯定小于当前节点,该节点也必是当前节点的父节点的祖先
                if(dfn[v]

 

你可能感兴趣的:(leetcode,图论)