您的位置:首页 > 其它

Tarjan算法---强联通分量

2014-08-19 11:37 281 查看
1、基础知识


在有向图G,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。



Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。栈中节点只有在其所属的强连通分量已经全部求出时,才会出栈。如果发现某节点u有边连到搜索树中栈里的节点v,则更新u的low
值为dfn[v](更新为low[v]也可以)。如果一个节点u已经DFS访问结束,而且此时其low值等于dfn值,则说明u可达的所有节点,都不能到达任何在u之前被DFS访问的节点 那么该节点u就是一个强连通分量在DFS搜索树中的根。此时将栈中所有节点弹出,包括u,就找到了一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
Low(u)=MIN{ DFN(u), Low(v),(u,v)为树枝边,u为v的父节点,DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)
}
[code]当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
2、参考代码

/* Tarjan算法:求一个有向图G=(V,E)里极大强连通分量。
  强连通分量是指有向图G里顶点间能互相到达的子图。
  如果一个强连通分量已经没有被其它强通分量完全包含,那这个强连通分量就是极大强连通分量*/
/*low[v]=dfn[v]时,栈里v以及v以上的顶点全部出栈,一个极大强连通分量*/
#include<stdio.h>
#include<stdlib.h>
#define MAXL 50
#define MIN(x,y) ((x) < (y) ? (x) : (y))

//结点定义
typedef struct edge_node{
    int key;
	struct edge_node *next;
}ENode;
typedef struct{
	char vertex;
	ENode *firstedge;
}VNode;
typedef VNode VList[MAXL];
typedef struct{
	VList vlist;
	int n,e;
}ALGraph;
int instack[MAXL];  //用于标记是否在栈中
int vis[MAXL];
int dfn[MAXL],low[MAXL];
int depth;

int ind[MAXL]={0};

int top;
int stack[MAXL];   //【【【要用到】】】
int num_scc;

int count_SCCele;
int scc[MAXL];
ALGraph *ALG=(ALGraph *)malloc(sizeof(ALGraph));

//邻接表生成[有向图]
void creat_ALGraph(ALGraph *ALG)
{
	int i,j,k;
	char ch1,ch2;
	ENode *ep;
	
	scanf("%d,%d",&ALG->n,&ALG->e);
	for(i=0;i<ALG->n;i++) //顶点表
	{
		getchar();
		scanf("%c",&ALG->vlist[i].vertex);
		ALG->vlist[i].firstedge=NULL;
	}
	for(k=0;k<ALG->e;k++)  //边表
	{
		getchar();
		scanf("%c,%c",&ch1,&ch2);
		for(i=0;ALG->vlist[i].vertex!=ch1;i++);
		for(j=0;ALG->vlist[j].vertex!=ch2;j++);

		ep=(ENode*)malloc(sizeof(ENode));
		ep->key=j;
		ep->next=ALG->vlist[i].firstedge;
		ALG->vlist[i].firstedge=ep;
	}
}

//邻接表输出
void print_ALGraph(ALGraph *ALG)
{
	int i;
	ENode *ptr=(ENode*)malloc(sizeof(ENode));
	for(i=0;i<ALG->n;i++)
	{
		printf("%c",ALG->vlist[i].vertex);
		ptr=ALG->vlist[i].firstedge;
		while(ptr!=NULL)  //不能用!ptr
		{
			printf("->%c",ALG->vlist[ptr->key].vertex);
			ptr=ptr->next;
		}
		printf("\n");
	}
}

//计算初始化用于dfnlow()和bicon()
void init_Tarjan(void)
{
	depth=0;
	for(int i=0;i<ALG->n;i++)
	{
		instack[i]=0;
		dfn[i]=low[i]=-1;
		vis[i]=0;
	}

	top=0;  //栈初始化
	for(int j=0;j<ALG->n;j++)
		stack[j]=-1;
	num_scc=0;
}

void init_scc(void)  //scc块初始化
{
	count_SCCele=0;
	for(int i=0;i<ALG->n;i++)
		scc[i]=-1;
}

void SCC_Tarjan(int u)
{
	int son;
	ENode *ptr=(ENode *)malloc(sizeof(ENode));

	dfn[u]=low[u]=depth++;  //访问+访问标记+入栈+入栈标记+遍历
	instack[u]=1;
	vis[u]=1;
	stack[top++]=u;
	ptr=ALG->vlist[u].firstedge;
	while(ptr!=NULL)
	{
		son=ptr->key;
		if(!vis[son])
		{
			SCC_Tarjan(son);
			low[u]=MIN(low[u],low[son]);
		}
		else if(instack[son])  //在栈中
		{
			low[u]=MIN(low[u],dfn[son]);
		}
		ptr=ptr->next;
	}
	if(dfn[u] == low[u])   //若此,以u为根的强连通分量
	{
		num_scc++;
		init_scc();
		do{
			top--;
			scc[count_SCCele++]=stack[top];
			instack[stack[top]]=0;
		}while(stack[top] != u);

		for(int cn=0;cn<count_SCCele;cn++)
			printf("%c ",ALG->vlist[scc[cn]].vertex);
		printf("\n");
	}
}

int main(void)
{
	creat_ALGraph(ALG);
	print_ALGraph(ALG);

	init_Tarjan();
	for(int i=0;i<ALG->n;i++)   //***可以处理不连通的图,如果连通只需要一次即可,即给定一个root,直接bridge_Tarjan(root,-1)***
		if(!vis[i])
			SCC_Tarjan(i);
//    SCC_Tarjan(2);  //root可以自定义

	printf("%d\n",num_scc);

	printf("%s %s %s\n","ver","dfn","low");
	for(int l=0;l<ALG->n;l++)
		printf("%c: %3d %3d\n",ALG->vlist[l].vertex,dfn[l],low[l]);

	return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: