Tarjan算法可以用来求一个图中的强连通分量,不仅如此,还可以求图的割边、割点
接下来上一些定义
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
[点连通度与边连通度]
在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。一个图的点连通度的定义为,最小割点集合中的顶点数。
类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。一个图的边连通度的定义为,最小割边集合中的边数。
[求割点与桥]
该算法是R.Tarjan发明的。对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。根据定义,则有:
Low(u)=Min { DFS(u) DFS(v) (u,v)为后向边(返祖边) 等价于 DFS(v)<DFS(u)且v不为u的父亲节点 Low(v) (u,v)为树枝边(父子边) }
一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。 (2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)。
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
以上十分专业的语言皆为转载,如果对于思路还是不大清楚的安利填坑时看的博客
https://www.byvoid.com/zhs/blog/scc-tarjan
这个是求强连通分量的
https://www.byvoid.com/zhs/blog/biconnect
这个是求割点割边还有一些其他的
#include<iostream> #include<cstdlib> #include<cstdio> #include<algorithm> #define N 3010 #define M 200010 using namespace std; int n,m,cnt,g[N],DFN,low[M],dfn[M],cut[N],sz[N]; struct nee{ int nxt,to; }e[M]; inline void tarjan(int x,int f){ low[x]=dfn[x]=++DFN;sz[x]=0; for(int i=g[x];i;i=e[i].nxt){ ++sz[x]; if(e[i].to==f) continue; if(dfn[e[i].to]) low[u]=min(low[u],dfn[e[i].to]); else{ tarjan(e[i].to,x); low[x]=min(low[x],low[e[i].to]); if(x==1){ if(sz[x]>=2) cut[x]=1; } else if(low[e[i].to]>dfn[x]) cut[x]=1; } } } inline void addedge(int x,int y){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y; } inline void Jimmy(){ scanf("%d%d",&n,&m); for(int i=1,u,v;i<=m;i++){ scanf("%d%d",&u,&v); addedge(u,v); } tarjan(1,0); for(int i=1;i<=n;i++) if(cut[i]) printf("%d ",i); } int main(){ Jimmy(); return 0; }
#include<iostream> #include<cstdlib> #include<cstdio> #include<algorithm> #define N 3010 #define M 200010 using namespace std; int n,m,g[M],cnt,DFN,dfn[M],low[M],cutb; struct nee{ int nxt,to,u,v; }e[M]; inline addedge(int x,int y){ e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y;e[cnt].u=x;e[cnt].v=y; } inline void tarjan(int x,int f){ low[x]=dfn[x]=++DFN; for(int i=g[x];i;i=e[i].nxt){ if(e[i].to==f) continue; if(dfn[e[i].to]) low[x]=min(low[x],dfn[e[i].to]); else { tarjan(e[i].to,x); low[x]=min(low[x],low[e[i].to]); } } } inline void Jimmy(){ scanf("%d%d",&n,&m); for(int i=1,u,v;i<=m;i++){ scanf("%d%d",&u,&v); addedge(u,v); } tarjan(1,0); for(int i=1;i<=m;i++){ if(dfn[e[i].u]>dfn[e[i].v]) swap(e[i].u,e[i].v); if(low[e[i].v]>dfn[e[i].u]) ++cutb; } printf("%d ",cutb); } int main(){ Jimmy(); return 0; }
P.S.:为什么以前博客折叠代码没有标题?因为我今天才发现有这个设置的选项。。。