上篇讲了拓扑排序只适用于有向无环图,那么tarjan算法就是把有向有环图变成一个有向无环图的算法
上述过程也就是缩点,是将原来的一个强连通分量缩成一个点,理由很简单,我只要有了这个强连通分量内的任意一点,我就可以到达强连通分量内的任意一个点,所以把他们一个整体看成一个点,由于环或者说回路全被合并成整体了,剩下的图自然就是有向无环图了!(不明白的自己百度强连通分量定义)
先介绍两个数组,明白一下他们的意义(建议强记,然后结合下面代码来看)
dfn数组,俗称的时间戳,代表此点是第几个被遍历到的
low数组,代表能追溯到的最早的已经被遍历过的点的序号
pan数组,并查集思想,初始化为pan[i]=i
下面直接贴一下tarjan函数
stack<int> q;//建立一个栈
void tarjan(int u)
{
low[u]=dfn[u]=++ant;//点u被遍历到了,所以把low和dfn初始化了,dfn初始化之后的值就不会在改变,但是low是可能改变的,因为此时还没有朝下遍历,不知道是否能回溯到之前被遍历过的点,所以初始化为自己的序号
q.push(u);//u点被遍历了,扔进栈
used[u]=1;//标记进栈
for(int i=head[u];i;i=bian[i].qian)//以u为起点遍历相连的点
{
int v=bian[i].wei;
if(!dfn[v])//dfn我初始化全为0,如果v没有被遍历过
{
tarjan(v);那就递归进去
low[u]=min(low[u],low[v]);u能追溯的最早的点当然就是自己和v点能追溯到的最早的点中最早的一个了,dp的思想
}
else if(used[v])//如果v点已经被遍历过了,并且在栈里
low[u]=min(low[u],low[v]);//那low[u]就和low[u]做对比
//是不是发现了盲点,除了v点被遍历过并且不在栈中,其他时候都要比较low[u]和low[v],当v点已经确定在某一强连通分量时,在比较会让low[u]变小,使结果错误
}
if(low[u]==dfn[u])//如果时间戳和low相等,这意味着什么,意味他能回溯到的最早的点就是他自己,u点向下搜索饶了一圈又回到了自己(或者说无法回溯到自己或者比自己更早的点),说明u就是强连通分量的一个起始搜索点,此时栈里在u点之上的点都属于这个强连通分量
{
while(q.top()!=u)//现在要把栈里面的点提出来
{
used[q.top()]=0;//标记出栈
pan[q.top()]=u;//此时他们的祖宗不在是自己,而是u(并查集思想)
q.pop();//出栈
}
q.pop();//u点自己也是要出的
used[u]=0;
}
}
多解释一下,为什么当已经确定一个点在强连通分量里面(即used[v]=0且dfn[v]不为0的时候不能比较low[u]和low[v]),比如现在有1 2 3三个点,1指向2,2指向1,3指向1,从1开始遍历,显然1 2是一个强连通分量,1 2遍历完后得到,low[1]=dfn[1]=1,low[2]=1,dfn[2]=2,used[2]=0,used[1]=0,从3开始遍历,dfn[3]=3,3显然是一个单独的强连通分量,如果强行比较,会使得正确结果low[3]=dfn[3]=3变成dfn[3]=3,low[3]=1,显然这个答案就错误了
下面是洛谷缩点模板原题
标准的缩点题,先缩点成一个有向无环图,累加权值给强连通分量,然后用拓扑排序dp处理权值和,下面我只贴一份缩点的代码,拓扑排序有兴趣大家可以补充一下交一下这个题:https://www.luogu.com.cn/problem/P3387
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stack>
using namespace std;
struct xing
{
int qian,wei;
}bian[101000];
int n,m,cnt,head[11000],low[11000],dfn[11000],ant,ans,pan[11000],o1,o2,mem[11000];
bool used[11000];
void add(int u,int v)
{
bian[++cnt].qian=head[u];
bian[cnt].wei=v;
head[u]=cnt;
}
stack<int> q;
void tarjan(int u)
{
low[u]=dfn[u]=++ant;
q.push(u);
used[u]=1;
for(int i=head[u];i;i=bian[i].qian)
{
int v=bian[i].wei;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(used[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
ans++;
while(q.top()!=u)
{
used[q.top()]=0;
pan[q.top()]=u;
mem[u]+=mem[q.top()];//权值累加
q.pop();
}
q.pop();
used[u]=0;
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<n+1;i++)
{
pan[i]=i;
cin>>mem[i];//每个点的权值
}
for(int i=1;i<m+1;i++)
{
cin>>o1>>o2;
add(o1,o2);//连边
}
for(int i=1;i<n+1;i++)
{
if(!dfn[i])
tarjan(i);//缩点
}
cout<<ans;//输出强连通分量个数
return 0;
}