Tarjan 学习笔记

1.前置知识

  • 强联通:若两个点可以互相通达,则称这两个点强联通。

  • 强连通图:若图中每两个点都强联通,则称这个图为强连通图。

  • 强联通分量:若图中有一个子图中每两个点都强连通,则称这个子图为强连通分量。

如图中 ({1,2,3,4})({5})({6}) 即为强连通分量

  • 链式前向星

可以阅读https://blog.csdn.net/sugarbliss/article/details/86495945

2.Tarjan

Tarjan是一种用来求强连通分量的算法。

我们用一个数组 (dfn) 来存当前点的编号,用一个数组 (low) 来存当前点能到达的点的最小编号,用一个数组 (vis) 来表示当前点是否被访问过。

然后进行 ( exttt{DFS})。每遍历到一个点就让它入栈,并且把 (vis_{i}) 赋为 ( exttt{true}),表示当前点被遍历过且在栈内。

如果下一个要遍历的点没被遍历过,那么先遍历下一个点,然后把当前的 (low_{i}) 更新为 (min(low_{i},low_{r[i]}))。((r_{i}) 表示下一个要遍历的点)

如果下一个要遍历的点被遍历过且还在栈内,那么把当前的 (low_{i}) 更新为 (min(low_{i},dfn_{r[i]}))

举个例子,如图。

(1)开始 ( exttt{DFS}),将 (1) 入栈,(dfn_{1}=1,low_{1}=1)

( exttt{DFS}) (3),未被遍历过,将 (3) 入栈,(dfn_{3}=2,low_{3}=2)

(3) 不能继续 ( exttt{DFS}),将 (3) 出栈。

( exttt{DFS}) (4),未被遍历过,将 (4) 入栈,(dfn_{4}=3,low_{4}=3)

( exttt{DFS}) (2),未被遍历过,将 (2) 入栈,(dfn_{2}=4,low_{2}=4)

( exttt{DFS}) (1),已被遍历,(low_{2}=min(low_{2},dfn_{1})=1)

返回 (4)(low_{4}=1)

返回 (1)

( exttt{DFS}) (8),未被遍历过,将 (8) 入栈,(dfn_{8}=5,low_{8}=5)

( exttt{DFS}) (6),未被遍历过,将 (6) 入栈,(dfn_{6}=6,low_{6}=6)

( exttt{DFS}) (5),未被遍历过,将 (5) 入栈,(dfn_{5}=7,low_{5}=7)

( exttt{DFS}) (9),未被遍历过,将 (9) 入栈,(dfn_{9}=8,low_{9}=8)

( exttt{DFS}) (6),已被遍历,(low_{9}=min(low_{9},dfn_{6})=6)

返回 (5)

返回 (6)(low_{6}=5)

返回 (8)

(9) 出栈。

(5) 出栈。

(6) 出栈。

(8) 出栈。

(2) 出栈。

(4) 出栈。

(1) 出栈。

( exttt{DFS}) (7),未被遍历过,将 (7) 入栈,(dfn_{7}=9,low_{7}=9)

(7) 出栈。

( exttt{tarjan}) 结束。

如果不懂,可以结合以下动图食用。

3.总结

例题:P2661。

题意:图中的每一个点都连有一条有向边 ((i,t_{i})),求图中最小环。

解答:考虑在 ( exttt{tarjan}) 过程中求最小环,如果当前是一个环,则打擂台找最小值,注意排除自环。

#include<bits/stdc++.h>
using namespace std;
int n,ans=0x3ffffff;
int a;
int sta[200002],top;//查询数组
int dfn[200002],low[200002],tot;//tarjan数组
int head[200002],Next[200002],to[200002],cnt;//链式前向星数组
int f[200002];//栈
void addedge(int x,int y)//链式前向星
{
    cnt++;
    Next[cnt]=head[x];
    head[x]=cnt;
    to[cnt]=y;
}
void tarjan(int u)
{
    f[u]=1;//标记为已入栈
    dfn[u]=low[u]=++tot;//记录dfn和low
    sta[++top]=u;//记录,便于查询
    for(int i=head[u];i;i=Next[i])
        if(!dfn[to[i]]) {tarjan(to[i]);low[u]=min(low[u],low[to[i]]);}//如果该点未被访问,则对此点进行tarjan
            else if(f[to[i]]) low[u]=min(low[u],dfn[to[i]]);//否则更新low
    if(low[u]==dfn[u])//如果找到环
    {
        int res=0;
        do
        {
            f[sta[top]]=0;//出栈
            res++;
        }while(sta[top--]!=u);//找出当前环的点数
        if(res!=1) ans=min(ans,res);//寻找最小值
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) {cin>>a;addedge(i,a);}//存图
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);//如果该点未被访问,则对此点进行tarjan
    cout<<ans;
    return 0;
}

图论基础。

参考资料:

图论——(color{black}Ocolor{red}RzyzRO)

https://www.luogu.com.cn/blog/hicc0305/solution-p2661

https://www.jianshu.com/p/346e01244080

原文地址:https://www.cnblogs.com/wuzhenyu/p/14701295.html