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)为指向栈中节点的后向边(非横叉边) }
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
2、参考代码
1 /* Tarjan算法:求一个有向图G=(V,E)里极大强连通分量。 2 强连通分量是指有向图G里顶点间能互相到达的子图。 3 如果一个强连通分量已经没有被其它强通分量完全包含,那这个强连通分量就是极大强连通分量*/ 4 /*low[v]=dfn[v]时,栈里v以及v以上的顶点全部出栈,一个极大强连通分量*/ 5 #include<stdio.h> 6 #include<stdlib.h> 7 #define MAXL 50 8 #define MIN(x,y) ((x) < (y) ? (x) : (y)) 9 10 11 //结点定义 12 typedef struct edge_node{ 13 int key; 14 struct edge_node *next; 15 }ENode; 16 typedef struct{ 17 char vertex; 18 ENode *firstedge; 19 }VNode; 20 typedef VNode VList[MAXL]; 21 typedef struct{ 22 VList vlist; 23 int n,e; 24 }ALGraph; 25 int instack[MAXL]; //用于标记是否在栈中 26 int vis[MAXL]; 27 int dfn[MAXL],low[MAXL]; 28 int depth; 29 30 int ind[MAXL]={0}; 31 32 int top; 33 int stack[MAXL]; //【【【要用到】】】 34 int num_scc; 35 36 int count_SCCele; 37 int scc[MAXL]; 38 ALGraph *ALG=(ALGraph *)malloc(sizeof(ALGraph)); 39 40 //邻接表生成[有向图] 41 void creat_ALGraph(ALGraph *ALG) 42 { 43 int i,j,k; 44 char ch1,ch2; 45 ENode *ep; 46 47 scanf("%d,%d",&ALG->n,&ALG->e); 48 for(i=0;i<ALG->n;i++) //顶点表 49 { 50 getchar(); 51 scanf("%c",&ALG->vlist[i].vertex); 52 ALG->vlist[i].firstedge=NULL; 53 } 54 for(k=0;k<ALG->e;k++) //边表 55 { 56 getchar(); 57 scanf("%c,%c",&ch1,&ch2); 58 for(i=0;ALG->vlist[i].vertex!=ch1;i++); 59 for(j=0;ALG->vlist[j].vertex!=ch2;j++); 60 61 ep=(ENode*)malloc(sizeof(ENode)); 62 ep->key=j; 63 ep->next=ALG->vlist[i].firstedge; 64 ALG->vlist[i].firstedge=ep; 65 } 66 } 67 68 //邻接表输出 69 void print_ALGraph(ALGraph *ALG) 70 { 71 int i; 72 ENode *ptr=(ENode*)malloc(sizeof(ENode)); 73 for(i=0;i<ALG->n;i++) 74 { 75 printf("%c",ALG->vlist[i].vertex); 76 ptr=ALG->vlist[i].firstedge; 77 while(ptr!=NULL) //不能用!ptr 78 { 79 printf("->%c",ALG->vlist[ptr->key].vertex); 80 ptr=ptr->next; 81 } 82 printf(" "); 83 } 84 } 85 86 //计算初始化用于dfnlow()和bicon() 87 void init_Tarjan(void) 88 { 89 depth=0; 90 for(int i=0;i<ALG->n;i++) 91 { 92 instack[i]=0; 93 dfn[i]=low[i]=-1; 94 vis[i]=0; 95 } 96 97 top=0; //栈初始化 98 for(int j=0;j<ALG->n;j++) 99 stack[j]=-1; 100 num_scc=0; 101 } 102 103 void init_scc(void) //scc块初始化 104 { 105 count_SCCele=0; 106 for(int i=0;i<ALG->n;i++) 107 scc[i]=-1; 108 } 109 110 void SCC_Tarjan(int u) 111 { 112 int son; 113 ENode *ptr=(ENode *)malloc(sizeof(ENode)); 114 115 dfn[u]=low[u]=depth++; //访问+访问标记+入栈+入栈标记+遍历 116 instack[u]=1; 117 vis[u]=1; 118 stack[top++]=u; 119 ptr=ALG->vlist[u].firstedge; 120 while(ptr!=NULL) 121 { 122 son=ptr->key; 123 if(!vis[son]) 124 { 125 SCC_Tarjan(son); 126 low[u]=MIN(low[u],low[son]); 127 } 128 else if(instack[son]) //在栈中 129 { 130 low[u]=MIN(low[u],dfn[son]); 131 } 132 ptr=ptr->next; 133 } 134 if(dfn[u] == low[u]) //若此,以u为根的强连通分量 135 { 136 num_scc++; 137 init_scc(); 138 do{ 139 top--; 140 scc[count_SCCele++]=stack[top]; 141 instack[stack[top]]=0; 142 }while(stack[top] != u); 143 144 for(int cn=0;cn<count_SCCele;cn++) 145 printf("%c ",ALG->vlist[scc[cn]].vertex); 146 printf(" "); 147 } 148 } 149 150 int main(void) 151 { 152 creat_ALGraph(ALG); 153 print_ALGraph(ALG); 154 155 init_Tarjan(); 156 for(int i=0;i<ALG->n;i++) //***可以处理不连通的图,如果连通只需要一次即可,即给定一个root,直接bridge_Tarjan(root,-1)*** 157 if(!vis[i]) 158 SCC_Tarjan(i); 159 // SCC_Tarjan(2); //root可以自定义 160 161 printf("%d ",num_scc); 162 163 printf("%s %s %s ","ver","dfn","low"); 164 for(int l=0;l<ALG->n;l++) 165 printf("%c: %3d %3d ",ALG->vlist[l].vertex,dfn[l],low[l]); 166 167 return 0; 168 }