tarjan算法各种应用

Robert Tarjan,一个很牛逼的计算机科学家。

tarjan算法真的是一个神奇的算法,一个简单的dfs却可以解决连通性的问题以及求最近公共祖先

1.求强连通分量
首先介绍一下什么是强连通分量。

强连通(Strongly Connected)是指一个有向图(Directed Graph)中任意两点v1、v2间存在v1到v2的路径(path)及v2到v1的路径。
强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

当我们知道了强连通分量是什么了之后,我们来学习一下tarjan算法是如何求强连通分量的。
其实tarjan算法写起来很简单,就是一遍dfs,但是理解起来确实有点麻烦。

首先,
dfs就是我们通常的dfs,每个点需要标记有没有访问过,如果没有就往下dfs。
dfs出来的其实是一个搜索树。

每个节点我们需要保存两个信息
1.dfn[x],表示x是第几个访问的,就相当于有个cnt,每调用一层dfs,就使得cnt+1,然后赋值给当前访问的节点。
2.low[x],这个就不太好理解了,指的是,从这个点往下继续搜索所能搜索到的反回边所指向的已经搜过的节点的最小编号。

这个low[x]其实是在dfs的过程中递归求出来的一个结果,对于u->v分两种情况
(0.初始时low[u]=dfn[u],如果这个点处理完low[u]仍然不能改变的话,说明无法搜索到已访问过的点)
1.v是没有访问过的节点,dfs(v),回溯时low[u]=min(low[u],low[v]);
2.v是已经访问过的节点,如果该点还没有被划分到某一个强连通分量里,low[u]=min(low[u],dfn[v]);

第2种情况可以理解为维护low的递归边界,因为找到了已访问过的节点,否则就递归下去并用low[v]去更新low[u];

其次,
我们在dfs的时候还需要维护一个栈,每访问到一个点就将其进栈,这个栈其实就是用来存在同一个强连通分量里的点。

那么什么时候出栈呢?
当一个点处理完后,low[u]==dfn[u]的时候。之前说过了,low[u]==dfn[u]表明这个点无法搜索到已经访问过的点,那么当前栈内,从这个点往上的所有点就都属于这个强连通分量,并将它们出栈。

怎么理解呢?
如果low[u]==dfn[u],那么说明从u点往后搜索的点没有能搜到在u点之前访问的点,那么他们一定不会属于更往前的点组成的强连通分量。有人又问了,这样也未必属于u所在的强连通分量啊。可是如果这个点不属于u所在的强连通分量,也不属于u之前访问的点的强连通分量,那么它一定已经和自己所在的强连通分量的所有点一起出栈了,所以栈中u往上的所有点就都是u所在的强连通分量。

代码实现

vector <int> a[maxn];
int index=0,cnt=0;
int low[maxn],dfn[maxn],vis[maxn],id[maxn];
stack <int> s;
void tarjan(int x)
{
    index++;
    low[x]=dfn[x]=index;
    s.push(x);
    vis[x]=1;
    int l=a[x].size();
    for(int i=0;iint t=a[x][i];
        if(dfn[t]==0)
        {
            tarjan(t);
            low[x]=min(low[x],low[t]);
        }
        else
        if(id[t]!=0)
            low[x]=min(low[x],dfn[t]);
    }
    if(low[x]==dfn[x])
    {
        cnt++;
        while(!s.empty())
        {
            int t=s.top();
            s.pop();
            id[t]=cnt;
            if(t==x)
                break;
        }
    }
}

2.tarjan求割点
理解了dfn和low的意思之后,tarjan求割点就很好理解了。
先来看割点的定义:

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。
如果某个割点集合只含有一个顶点X(也即{X}是一个割点集合),那么X称为一个割点。

换句话说就是在一个无向连通图中,如果某一个点被去掉之后使得剩下的图不再连通,那么这个点就是割点,它把图分割了。

求割点就是在上面所展示的过程中,找到某个点u,如果dfn[u]<=low[u],那么这个节点u就是割点。
为什么?
dfn[u]<=low[u]表示u点往下搜索无法搜索到更往前访问过的点。
如果无法搜索到之前的点,那么就表示把这个点拿掉,一个连通图就被分成了两个或多个连通图。
我们可以通过下图直观地感受一下。
如图所示是割点最基本的表现形式。
tarjan算法各种应用_第1张图片
可以看出
图中红色部分与蓝色部分与顶点t组成了一个连通图,而两部分要想到达彼此,必须经过t点。所以当我们把t点去掉时,一个连通图就变成了两个,即红色部分与蓝色部分。
所以t点就是一个割点。
我们会发现,无论我们搜索顺序是 红->t->蓝 还是 蓝->t->红,我们都无法从t点往后搜索到t点往前的点,因为必然要两次经过t点,而一个点我们只走一次,所以dfn[u]<=low[u].

3.tarjan求桥

tarjan求桥和求割点是差不多的,在dfs的时候,对于任意一条边u->v,如果dfn[u]

你可能感兴趣的:(ACM-图论)