图连通性:
- 无向图若图中任意两点连通,则图连通。
- 有向图若图中任意两点连通则图强连通。若图中忽略方向任意两点连通则图弱连通。
注意:若图弱连通,图中未必任意两点都单侧连通。
连通分量:
- 无向图中极大连通子图为连通分量。
- 有向图的极大连通子图称为强连通分量。
博客参考:https://blog.csdn.net/mengxiang000000/article/details/51672725<----------------详细见这里
https://blog.csdn.net/qq_34374664/article/details/77488976<----------------详细见这里
http://www.cnblogs.com/saltless/archive/2010/11/08/1871430.html<----------------详细见这里
示例图,如下:
下面这段代码就是Tarjan算法,现在来描述一下它的实现过程。
它的实现过程与DFS的过程一样,深度优先遍历,带有回溯。
第一层遍历的时候,dfn的值与low的值是相等的,所以一起赋值。
一:从1号节点开始遍历,所以1号节点的标号为1, 记为:
- 1(1,1)
二:从1号节点遍历到了2号节点,2号节点的标号就是2,记为:
- 2(2,2)
三:从2号节点又遍历到了3号节点,3号节点的标号就是3,记为:
- 3(3,3)
四:从3号节点开始遍历,3号节点能遍历到1号节点,但是1号节点已经遍历过了,所以这个时候就要修改low的值了,3号节点的low值low[3]=min(low[3],low[1])=1,所以这个时候,3号节点就记为:3(3,1),此后,没有点可以继续遍历,就到了回溯的过程。
五:回溯到2号节点,2号节点能遍历到3号节点,但是3号节点已经被标记,所以修改2号节点的low值:low[2]=min(low[2],low[3])=1,2号节点就记为:2(2,1)
六:回溯到1号节点,1号节点能遍历到2号节点,但是1号节点已经被标记,所以修改1号节点的low值:low[1]=min(low[1],low[2])=1,2号节点就记为:1(1,1),此时dfn与low相等,证明1号节点是一个关键点。
七:到1号节点之后,再继续遍历,就到了4号节点,4号节点的标号就是4,记为:
- 4(4,4)
八:到4号节点之后,再继续遍历,就到了5号节点,5号节点的标号就是5,记为:
- 5(5,5)
九:到5号节点之后,不能再继续遍历了(5号节点的dfn与low相等),就又到了回溯的过程。
十:5号节点回溯到4号节点,4号节点被标记,修改4号节点的low值:low[4]=min(low[4],low[5])=4,此时4的dfn与low相等。
十一:再继续回溯就到了1号节点,1号节点被标记,修改1号节点的low值,依旧是1,遍历结束。
回溯的过程就是修改low值的过程,low值代表当前节点能访问到的最早的节点,例如:1号节点——>2号节点——>3号节点——>4号节点(这样形成了一个环,任意节点间都可以到达),2号节点能到达的最早节点不是3号,而是1号,dfn就是来记录节点在搜索树上遍历的顺序,值越小,就证明被遍历的越早。
void Tarjan(int u)
{
vis[u]=1;
low[u]=dfn[u]=cnt++;
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)
{
Tarjan(v);
}
if(vis[v]==1)
low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u])
{
sig++;
}
}
********************************************************************************************************************************************
强连通图还有一个应用是缩点:
上面的图经过缩点之后,就变成了下图:
缩点的代码:
void Tarjan(int u)
{
vis[u]=1;
low[u]=dfn[u]=cnt++;
c[++tt]=u;
// printf("c[%d] = %d\n",tt,u);
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)
Tarjan(v);
if(vis[v]==1)
low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u])
{
sig++;
do
{
low[c[tt]]=sig;
color[c[tt]]=sig;
vis[c[tt]]=-1;
// printf("c[%d]=%d low[%d]=%d color[%d]=%d \n",tt,c[tt],c[tt],sig,c[tt],sig);
}while(c[tt--]!=u);
}
}
同一个强连通分量里的节点的颜色相同,c数组就是来存放节点,color数组就代表节点的颜色,sig代表强连通分量的个数,下面来阐述缩点的过程。这个过程基于以上的搜索过程
一:从1号节点开始c数组从0号节点开始存放
c[0]=1,c[1]=2,c[2]=3,当存放到3号节点的时候,由于这个时候开始了回溯的过程,开始修改low值,回溯结束之后,又开始遍历了4号节点,这个时候,c数组继续存放节点。
二:c[3]=4,c[4]=5;这个时候,节点存放结束
三:当存到c[4]=5,的时候,没有边可以遍历,就不会再进for循环语句,此时dfn与low值是相等的,就执行下面的 if(dfn[u]==low[u]) 语句,
回溯的时候,就到了3号节点,3号节点的dfn与low值也是相等的,因此执行下面的 if(dfn[u]==low[u]) 语句,
继续回溯到了1号节点,所有由1号节点发出去的边均遍历结束了,此时,就输出由1号节点开头的强连通分量元素,由do while()循环输出。
这其实是一个栈操作:
输出各强连通分量各元素以及强连通分量的个数:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <stack>
using namespace std;
const int maxn = 2000 + 7;
const int INF = ~0U >> 1;
vector<int> G[maxn];
int n, m, k = 0, cnt = 0;
int low[maxn], dfn[maxn], in[maxn];
stack<int> s;
void tarjan(int u) {
low[u] = dfn[u] = ++k;
s.push(u);
in[u] = 1;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(in[v]) {
low[u] = min(low[u], dfn[v]);
}
}
int v;
if(dfn[u] == low[u]) {
++cnt;
do {
v = s.top(); s.pop();
printf("%d ",v) ;
in[v] = 0;
} while(u != v);
cout<<endl;
}
}
int main() {
scanf("%d%d", &n, &m);
int u, v;
for(int i = 0; i < m; ++i) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
}
for(int i = 1; i <= n; ++i) {
if(!dfn[i]) tarjan(i);
}
printf("%d\n", cnt);
return 0;
}
带测试样例的代码:
#include<stdio.h>//此代码仅供参考,用于求一个图存在多少个强连通分量
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define maxn 1000000
vector<int >mp[maxn];
int ans[maxn];
int vis[maxn];
int dfn[maxn];
int low[maxn];
int n,m,tt,cnt,sig;
void init()
{
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)mp[i].clear();
}
void Tarjan(int u)
{
vis[u]=1;
low[u]=dfn[u]=cnt++;
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)Tarjan(v);
if(vis[v]==1)low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u])
{
sig++;
}
}
void Slove()
{
tt=-1;cnt=1;sig=0;
for(int i=1;i<=n;i++)
{
if(vis[i]==0)
{
Tarjan(i);
}
}
printf("%d\n",sig);
}
int main()
{
while(~scanf("%d",&n))
{
if(n==0)break;
scanf("%d",&m);
init();
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
mp[x].push_back(y);
}
Slove();
}
}
/*
5 5
1 2
2 3
3 1
1 4
4 5
7 9
1 2
2 3
3 1
2 4
4 7
7 4
4 5
5 6
6 4
8 10
1 2
2 3
3 1
2 4
4 7
7 4
4 5
5 6
6 4
7 8
*/