Tarjan 算法
一.算法简介
Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度。
我们定义:
如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
例如:在上图中,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 三个区域可以相互连通,称为这个图的强连通分量。
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
再Tarjan算法中,有如下定义。
DFN[ i ] : 在DFS中该节点被搜索的次序(时间戳)
LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号
当DFN[ i ]==LOW[ i ]时,为i或i的子树可以构成一个强连通分量。
二.算法图示
以1为Tarjan 算法的起始点,如图
顺次DFS搜到节点6
回溯时发现LOW[ 5 ]==DFN[ 5 ] , LOW[ 6 ]==DFN[ 6 ] ,则{ 5 } , { 6 } 为两个强连通分量。回溯至3节点,拓展节点4.
拓展节点1 , 发现1再栈中更新LOW[ 4 ],LOW[ 3 ] 的值为1
回溯节点1,拓展节点2
自此,Tarjan Algorithm 结束,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 为图中的三个强连通分量。
不难发现,Tarjan Algorithm 的时间复杂度为O(E+V).
三.算法模板
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;
typedef long long ll;
const double pi = acos(-1.0);
const int mod = 1e9 + 7;
const int maxn = 1e5 + 5;
const int V = 150, E = 100500;
struct Edge {
int to, next;
} edge[E];
int head[V], num; //记录树的结构
int idx; // 记录时间戳
int top, S[V]; //记录堆栈
int indeg[V], outdeg[V]; //记录入度、出度
int low[V], dfn[V]; //low记录能追溯到最前面的点,dfn记录dfs序
int belong[V], scc; //Belong[i] = a; 表示i这个点属于第a个连通分量 scc为连通分量个数
bool vis[V]; //标记是否进栈
int n;
int addedge(int u, int v)
{
edge[num].to = v;
edge[num].next = head[u];
head[u] = num++;
}
void tarjan(int u)
{
int v;
dfn[u] = low[u] = ++idx;
S[top++] = u;
vis[u] = 1;
for (int i = head[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if (dfn[v] == 0) //v还未遍历
{
tarjan(v);
low[u] = min(low[u], low[v]); //确保low[u]最小
}
else if (vis[v]) //如果在堆栈中
{
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) //表示找完一个连通分量
{
++scc;
do
{
v = S[--top];
vis[v] = 0;
belong[v] = scc;
} while (u != v);
}
}
int solve()
{
scc = top = idx = 0;
ms(dfn, 0);
ms(vis, 0);
for (int u = 1; u <= n; u++)
{
if (dfn[u] == 0)
{
tarjan(u);
}
}
return scc;
}
void count_deg()
{
ms(indeg, 0);
ms(outdeg, 0);
for (int u = 1; u <= n; u++)
{
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (belong[u] != belong[v])
{
indeg[belong[v]]++; //v所在的连通块入度++
outdeg[belong[u]]++; //u所在的连通块出度++
}
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
while (~scanf("%d", &n))
{
num=0;
ms(head, -1);
for (int u = 1; u <= n; u++)
{
int v;
while (~scanf("%d", &v) && v)
{
addedge(u, v);
}
}
solve();
if (scc == 1)
{
printf("1
0
");
}
else
{
count_deg();
int num_in = 0, num_out = 0;
for (int i = 1; i <= scc; i++)
{
if (indeg[i] == 0)
num_in++;
if (outdeg[i] == 0)
num_out++;
}
printf("%d
%d
", num_in, max(num_in, num_out));
}
}
return 0;
}
转自:http://www.cnblogs.com/shadowland/p/5872257.html