强连通分量总结 //个人总结,有不正确地方欢迎指出

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LMengi000/article/details/82152938

图连通性:

  1. 无向图若图中任意两点连通,则图连通。
  2. 有向图若图中任意两点连通则图强连通。若图中忽略方向任意两点连通则图弱连通。

注意:若图弱连通,图中未必任意两点都单侧连通。

连通分量:

  1. 无向图中极大连通子图为连通分量。
  2. 有向图的极大连通子图称为强连通分量。

博客参考: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)

二:从1号节点遍历到了2号节点,2号节点的标号就是2,记为:

  1. 2(2,2)

三:从2号节点又遍历到了3号节点,3号节点的标号就是3,记为:

  1. 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,记为:

  1. 4(4,4)

八:到4号节点之后,再继续遍历,就到了5号节点,5号节点的标号就是5,记为:

  1. 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
*/

猜你喜欢

转载自blog.csdn.net/LMengi000/article/details/82152938