一、深度优先生成树
在对无向图或有向图G进行从顶点v出发的深度优先搜索时,由v引向未被访问(标记)的顶点的边,构成以v为根的一棵树,这棵树被称为深度优先生成树(DFST)。
始点v称为树根、树上的每条边称为树边。
对G的完整地深度优先搜索,可能把G的顶点分划在若干棵DFST上,这若干棵DFST合在一起称为图G的深度优先生成森林(DFST)。
二、有向图的边分类
1.祖先点和子孙点
在G的DFST中,若顶点x在从根导顶点y的路上,则称x是y的祖先,y是x的子孙;根是树中所有其它顶点的祖先;当x¹y时,称x是y的真祖先,y是x的真子孙。
2.树边――在G的DFST上的边。
3.后向边――若G的一条非树边e放到其DFST上后,它是从某个顶点指向该顶点的祖先的连边,则把它称为后向边。
4.前向边――从某顶点指向该顶点的子孙点的连边。
5.横跨边(横向边)――若边的两顶点无祖先、子孙关系,则把它称为横跨边。
三、深度优先数与后序数
1、深度优先数:顶点v的深度优先数dfnumber(v)为:在DFS中,顶点v被访问的次序计数值。
应用:
1)w是v的子孙Ûdfnumber(v)<dfnumber(w)≤dfnumber(v)+v的子孙数。
2)xy是后向边Ûdfnumber(x)>dfnumber(y),且y是x的祖先。
3)xy是前向边Ûdfnumber(x)<dfnumber(y),且y是x的子孙。
4)xy是横向边Ûdfnumber(x)>dfnumber(y),且x、y没有祖先、子孙关系。
2、后序数:对G做DFS时,各顶点退出DFS(由该点返退到其祖先点时)的次序计数。
四、反图
将有向图G的所有边反向后得到的图。记为Gr。
五、有向图的连通性
在G的顶点集V上定义一个关系R:
六、Kosaraju算法的基本思想
首先明确:图G的反图Gr与图G的连通分量是一样的。
从图中某个顶点开始进行遍历得到的DFS树可能包含整个图,即可能将所有SCC混在一起。就是说,在未知G的连通性的前提下,DFS的始点选取,对找其强分支是有制约的。能否把始点定下来,从此点开始,通过DFS把G的强分支都找出来呢?
设图G为SC-->SC1,其中SC、SC1为连通分量,SC到SC1有一条边相连。
分析知:不管是选SC1上的点还是选SC2上的点作为始点进行DFS,最后退出DFS的点(后序数最大)一定在SC1上。若选最后退出DFS的点为始点并对Gr也进行一次DFS,则可以将Gr的强分支找到,从而也就确定了G的强分支。
由此得到以下算法:
对有向图G做DFS,求出每个顶点的后序数
2.求G的反图Gr
3.选其后序数最大的顶点作为起始点对Gr进行DFS;当该始点的选取不能访到Gr的所有顶点时,再在剩余的顶点中选其后序数较大的顶点为始点,继续对Gr进行DFS,直到访问完Gr的所有顶点为止。
4.把Gr的每一棵DFST上的顶点及其在G中的关联边作为G的一个强分支。
#define maxn 505 vector<int>G[maxn],G2[maxn];//G的逆图保存在G2中。 vector<int>S; int vis[maxn],sccno[maxn],scc_cnt; void dfs1(int u){ if(vis[u]) return; vis[u]=1; for(int i=0;i<G[u].size();++i) dfs1(G[u][i]); S.push_back(u);//按后序数由小到大存放顶点。 } void dfs2(int u){ if(sccno[u]) return; sccno[u]=scc_cnt; for(int i=0;i<G2[u].size();++i) dfs2(G2[u][i]); } void find_scc(int n){ scc_cnt=0; S.clear(); memset(sccno,0,sizeof(sccno)); memset(vis,0,sizeof(vis)); for(int i=0;i<n;++i) dfs1(i); for(int i=n-1;i>=0;--i) if(!sccno[S[i]]) {++scc_cnt;dfs2(S[i]);} }
算法时间复杂度以及正确性:略。