有向图的强连通分量:在有向图G中,如果两个顶点vi,vj间(vi!=vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量。如图所示:
{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别为两个强连通分量。
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出:
Low(u)=Min{ DFN(u), Low(v),(u,v)为树枝边,u为v的父节点 DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)}, 当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。算法伪代码如下:
tarjan(u)
{
DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值
Stack.push(u) // 将节点u压入栈中
for each (u, v) in E // 枚举每一条边
if (v is not visted) // 如果节点v未被访问过
tarjan(v) // 继续向下找
Low[u] = min(Low[u], Low[v])
else if (v in S) // 如果节点v还在栈内
Low[u] = min(Low[u], DFN[v])
if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根
repeat
v = S.pop // 将v退栈,为该强连通分量中一个顶点
print v
until (u== v)
}
题1:POJ 1236(Network of Schools),题目意思不懂的直接百度。强连通分量的裸题。已经在代码上做了提示:
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
const int N=5000; //最大顶点数
const int M=100010; //最大边数
#define min(a,b) (a)<(b)?(a):(b)
#define max(a,b) (a)>(b)?(a):(b)
#define CLR(arr,val) memset(arr,val,sizeof(arr))
stack<int> s;
int n,m,t,sum; //n为顶点个数,m为边数,t为时间戳,sum为强连通分量个数
int DFN[N]; //保存顶点i搜索的次序(时间戳)编号
int Low[N]; //保存顶点i或i的子树最早的次序编号
int flag[N]; //记录点是否被保存在堆栈中
int belong[N]; //用来缩点的数组
struct ArcNode{
void Add(int u,int v)
{ next[num]=Prior[u];
data[num]=v;
Prior[u]=num++;
}
void Init()
{ CLR(Prior,-1);
num=0;
}
int Prior[N],next[M],data[M],num;
}A;
void Tarjan(int u) //从顶点u进行DFS
{ DFN[u]=Low[u]=++t;
s.push(u);
flag[u]=1; //标记顶点u已经被访问
for(int i=A.Prior[u];i!=-1;i=A.next[i])
{ int v=A.data[i];
if(DFN[v]==0) //顶点v没有被访问过
{ Tarjan(v);
Low[u]=min(Low[u],Low[v]);
}
else if(flag[v]) Low[u]=min(Low[u],Low[v]);
}
if(DFN[u]==Low[u]) //顶点u是强连通分量的根
{ int temp=-1;
sum++;
while(temp!=u) //缩点
{ temp=s.top();
s.pop();
belong[temp]=sum;
flag[temp]=0;
}
}
}
void Slove()
{ CLR(DFN,0);
for(int i=1;i<=n;i++)
if(DFN[i]==0) Tarjan(i);
}
void Count() //统计出度为0和入度为0的强连通分量的个数
{ int Inum=0,Onum=0;
int In[N],Out[N];
CLR(In,0);
CLR(Out,0);
for(int i=1;i<=n;i++)
for(int j=A.Prior[i];j!=-1;j=A.next[j])
if(belong[i]!=belong[A.data[j]]) //如果顶点i和顶点A.data[j]不在同一个强连通分量中
Out[belong[i]]=1,In[belong[A.data[j]]]=1;
for(int i=1;i<=sum;i++)
{ if(!In[i]) Inum++;
if(!Out[i]) Onum++;
}
cout<<Inum<<endl; //最少需要选择多少个顶点,使得从这些点出发能遍历完整个图
if(sum==1) cout<<0<<endl; //最少需要添加多少条有向边,使得整个图为强连通图
else cout<<(max(Inum,Onum))<<endl;
}
int main()
{ cin>>n;
sum=0;
t=0;
A.Init();
while(!s.empty()) s.pop();
for(int i=1;i<=n;i++)
{ int u;
while(cin>>u,u)
A.Add(i,u);
}
Slove();
Count();
return 0;
}
然后拿着这个代码在main里面添加个测试组数去把NYOJ 120(校园网络)过了吧。