有向图的强连通分量:在有向图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(校园网络)过了吧。