文字描述
连通分量的定义:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
用深度优先搜索求有向图的强连通分量的方法如下并假设有向图的存储结构为十字链表。
1 在有向图G上,从某个定点出发沿以该顶点为尾的弧进行深度优先遍历;并按其所有邻接点的搜索都完成(即退出DFS函数)的顺序将顶点排列起来。此时需对之前的深度优先遍历算法(见图遍历)作两处修改: (a)在进入DSFTraverse函数时首先进行计数变量的初始化,即在入口处加上count=0;(b)在退出DFS函数之前将完成搜索的顶点号记录在另一个辅助数组finished[vexnum]中,即在DFS函数结束之前加上finished[count++]=v;
2 在有向图G上,从最后完成搜索的顶点(即finished[count-1])出发,沿着以该顶点为头的弧作逆向的深度优先搜索遍历,若遍历不能访问到有向图中所有顶点,则从余下的顶点中最后完搜索的那个顶点出发,继续作逆向的深度优先搜索便利。依次类推。
由此,每一次调用DFS作逆向深度优先遍历所访问到的顶点集便是有向图G中一个强连通分量的顶点集。
示意图
见本章后面的代码运行Example01, Example02, Example03, Example04。
算法分析
求有向图的强连通分量的算法时间复杂度和深度优先搜索遍历相同。
代码实现
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #define MAX_VERTEX_NUM 20
5 #define DEBUG
6 #ifdef DEBUG
7 #include <stdarg.h>
8 #define LOG(args...) _log_(__FILE__, __FUNCTION__, __LINE__, ##args);
9 void _log_(const char *file, const char *function, int line, const char * format, ...)
10 {
11 char buf[1024] = {0};
12 va_list list;
13 va_start(list, format);
14 sprintf(buf, "[%s,%s,%d]", file, function, line);
15 vsprintf(buf+strlen(buf), format, list);
16 sprintf(buf+strlen(buf), "\n");
17 va_end(list);
18 printf(buf);
19 }
20 #else
21 #define LOG
22 #endif // DEBUG
23
24 //////////////////////////////////////////////////////////////
25 // 十字链表作为图的存储结构
26 //////////////////////////////////////////////////////////////
27 typedef enum {DG, DN, UDG, UDN} GraphKind;
28 typedef char InfoType;
29 typedef char VertexType;
30 typedef struct ArcBox{
31 int tailvex, headvex;
32 struct ArcBox *hlink, *tlink;
33 InfoType *info;
34 }ArcBox;
35 typedef struct VexNode{
36 VertexType data;
37 ArcBox *firstin, *firstout;
38 }VexNode;
39 typedef struct{
40 VexNode xlist[MAX_VERTEX_NUM];
41 int vexnum, arcnum;
42 GraphKind kind;
43 }OLGraph;
44
45 //////////////////////////////////////////////////////////////
46 // 若G中存在顶点u,则返回该顶点在图中位置;否则返回-1。
47 //////////////////////////////////////////////////////////////
48 int LocateVex(OLGraph G, VertexType v)
49 {
50 int i = 0;
51 for(i=0; i<G.vexnum; i++)
52 {
53 if(G.xlist[i].data == v)
54 return i;
55 }
56 return -1;
57 }
58
59 //////////////////////////////////////////////////////////////
60 // 若G中存在顶点位置loc存在,则返回其顶点名称
61 //////////////////////////////////////////////////////////////
62 VertexType LocateVInfo(OLGraph G, int loc)
63 {
64 return G.xlist[loc].data;
65 }
66
67
68 //////////////////////////////////////////////////////////////
69 // 以十字链表作为图的存储结构创建有向图
70 //////////////////////////////////////////////////////////////
71 int CreateDG(OLGraph *G)
72 {
73 printf("\n以十字链表作为图的存储结构创建有向图:\n");
74 int i = 0, j = 0, k = 0, IncInfo = 0;
75 int v1 = 0, v2 = 0;
76 char tmp[10] = {0};
77 ArcBox *p = NULL;
78 printf("输入顶点数,弧数,其他信息标志位: ");
79 scanf("%d,%d,%d", &G->vexnum, &G->arcnum, &IncInfo);
80 for(i=0; i<G->vexnum; i++)
81 {
82 //输入顶点值
83 printf("输入第%d个顶点: ", i+1);
84 memset(tmp, 0, sizeof(tmp));
85 scanf("%s", tmp);
86 //初始化指针
87 G->xlist[i].data = tmp[0];
88 G->xlist[i].firstin = NULL;
89 G->xlist[i].firstout = NULL;
90 }
91 //输入各弧并构造十字链表
92 for(k=0; k<G->arcnum; k++)
93 {
94 printf("输入第%d条弧(顶点1, 顶点2): ", k+1);
95 memset(tmp, 0, sizeof(tmp));
96 scanf("%s", tmp);
97 sscanf(tmp, "%c,%c", &v1, &v2);
98 i = LocateVex(*G, v1);
99 j = LocateVex(*G, v2);
100 //对弧结点赋值
101 p = (ArcBox *) malloc(sizeof(ArcBox));
102 p->tailvex = i;
103 p->headvex = j;
104 p->hlink = G->xlist[j].firstin;
105 p->tlink = G->xlist[i].firstout;
106 p->info = NULL;
107 //完成在入弧和出弧链头的插入
108 G->xlist[j].firstin = p;
109 G->xlist[i].firstout = p;
110 //若弧有相关的信息,则输入
111 if(IncInfo)
112 {
113 //Input(p->info);
114 }
115 }
116 return 0;
117 }
118
119 int CreateGraph(OLGraph *G)
120 {
121 printf("输入图类型: +有向图(0), -有向网(1), -无向图(2), -无向网(3): ");
122 scanf("%d", &G->kind);
123 switch(G->kind){
124 case DG:
125 return CreateDG(G);
126 case DN:
127 case UDN:
128 case UDG:
129 default:
130 printf("not support!\n");
131 return -1;
132 }
133 return 0;
134 }
135
136 //////////////////////////////////////////////////////////////
137 // 打印图结点
138 //////////////////////////////////////////////////////////////
139 void printG(OLGraph G)
140 {
141 printf("\n打印图结点\n");
142 if(G.kind == DG){
143 printf("类型:有向图;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
144 }else if(G.kind == DN){
145 printf("类型:有向网;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
146 }else if(G.kind == UDG){
147 printf("类型:无向图;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
148 }else if(G.kind == UDN){
149 printf("类型:无向网;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
150 }
151 int i = 0;
152 ArcBox *fi = NULL;
153 ArcBox *fo = NULL;
154 for(i=0; i<G.vexnum; i++)
155 {
156 printf("%c: ", G.xlist[i].data);
157 fi = G.xlist[i].firstin;
158 fo = G.xlist[i].firstout;
159 printf("{hlink=");
160 while(fi)
161 {
162 printf("(%d,%c)->(%d,%c); ", fi->tailvex, LocateVInfo(G, fi->tailvex), fi->headvex, LocateVInfo(G, fi->headvex));
163 fi = fi->hlink;
164 }
165 printf("} {tlink=");
166 while(fo)
167 {
168 printf("(%d,%c)->(%d,%c); ", fo->tailvex, LocateVInfo(G, fo->tailvex), fo->headvex, LocateVInfo(G, fo->headvex));
169 fo = fo->tlink;
170 }
171 printf("}\n");
172 }
173 return ;
174 }
175
176
177
178 //////////////////////////////////////////////////////////////
179 // 用双向遍历的方法求有向图的强连通分量
180 //////////////////////////////////////////////////////////////
181 int GVisited[MAX_VERTEX_NUM] = {0};
182 void (*visitfun)(OLGraph G, int v);
183
184 //记录退出DFS函数之前完成搜索的顶点号
185 int GFinished[MAX_VERTEX_NUM] = {0};
186 int GCount = 0;
187
188 #define D_OUT 0
189 #define D_IN 1
190 typedef enum DIRECTION
191 {
192 IN,
193 OUT
194 }DIRECTION;
195
196 //////////////////////////////////////////////////////////////
197 // 打印结点在图中的位置及结点信息
198 //////////////////////////////////////////////////////////////
199 void printvex(OLGraph G, int v)
200 {
201 printf("[%d]:%c\t", v, G.xlist[v].data);
202 return ;
203 }
204
205 //////////////////////////////////////////////////////////////
206 // direction is IN: 返回图G中以顶点位置为V的顶点为入度的第一个结点
207 // direction is OUT: 返回图G中以顶点位置为V的顶点为出度的第一个结点
208 //////////////////////////////////////////////////////////////
209 int FirstAdjVex(OLGraph G, int v, int direction)
210 {
211 if(direction == IN){
212 return (G.xlist[v].firstin)?G.xlist[v].firstin->tailvex:-1;
213 }else if(direction == OUT){
214 return (G.xlist[v].firstout)?G.xlist[v].firstout->headvex:-1;
215 }else{
216 return -1;
217 }
218 }
219
220 //////////////////////////////////////////////////////////////
221 // direction is IN: G中位置w是以v为入度的结点, 返回下一个以v为入度的结点
222 // direction is OUT: G中位置w是以v为出度的结点, 返回下一个以v为出度的结点
223 //////////////////////////////////////////////////////////////
224 int NextAdjVex(OLGraph G, int v, int w, int direction)
225 {
226 int ret = -1;
227 if(direction == IN){
228 ArcBox *fi = NULL;
229 fi = G.xlist[v].firstin;
230 while(fi)
231 {
232 if(fi->tailvex == w)
233 break;
234 fi = fi->hlink;
235 }
236 if(fi && (fi=fi->hlink)){
237 ret = fi->tailvex;
238 }else{
239 ret = -1;
240 }
241 }else if(direction == OUT){
242 ArcBox *fo = NULL;
243 fo = G.xlist[v].firstout;
244 while(fo)
245 {
246 if(fo->headvex == w)
247 break;
248 fo = fo->tlink;
249 }
250 if(fo && (fo=fo->tlink)){
251 ret = fo->headvex;
252 }else{
253 ret = -1;
254 }
255 }
256 return ret;
257 }
258 //////////////////////////////////////////////////////////////
259 // direction is IN :从第v个顶点出发, 沿着以v为入度的顶点的方式, 递归地深度优先遍历图G
260 // direction is OUT :从第v个顶点出发, 沿着以v为出度的顶点的方式, 递归地深度优先遍历图G
261 //////////////////////////////////////////////////////////////
262 void DFS(OLGraph G, int v, int direction)
263 {
264 GVisited[v] = 1;
265 printvex(G, v);
266 int w = 0;
267 for(w=FirstAdjVex(G,v,direction); w>=0; w=NextAdjVex(G, v, w, direction))
268 {
269 if(!GVisited[w]){
270 DFS(G, w, direction);
271 }
272 }
273 GFinished[GCount++] = v;
274 }
275
276 //////////////////////////////////////////////////////////////
277 // 1 从第一个出发,对图G作深度优先遍历并记录下退出DFS函数的顺序
278 // 2 从最后一个退出DFS函数的顶点出发, 反方向对图G作深度优先遍历并求出图的强连通分量
279 //////////////////////////////////////////////////////////////
280 void DFSTraverse(OLGraph G, void (*visitfun)(OLGraph G, int v))
281 {
282 visitfun = printvex;
283 int v = 0;
284 int index = 0;
285
286 printf("\n深度优先搜索(以第一个顶点为尾的弧进行深度优先搜索遍历):\n");
287 //访问标志数组初始化
288 for(v=0; v<G.vexnum; v++)
289 {
290 GVisited[v] = 0;
291 GFinished[v] = 0;
292 }
293 GCount = 0;
294
295 for(v=0; v<G.vexnum; v++)
296 {
297 if(!GVisited[v]){
298 DFS(G, v, OUT);
299 }
300 }
301
302 printf("\n退出DFS函数的次序location和顶点位置index为:\n");
303 for(v=0; v<GCount; v++)
304 {
305 printf("[location:%d, index:%d] ", v, GFinished[v]);
306 }
307 printf("\n");
308
309 printf("深度优先搜索(从最后完成搜索的顶点出发,沿着以该顶点为头的弧作逆向的深度优先搜索遍历):");
310 //访问标志数组初始化
311 for(v=0; v<G.vexnum; v++)
312 {
313 GVisited[v] = 0;
314 }
315 for(v=0; v<GCount; v++)
316 {
317 index = GFinished[GCount-1-v];
318 if(!GVisited[index]){
319 printf("\n强连通分量:");
320 DFS(G, index, IN);
321 }
322 }
323 printf("\n");
324 }
325
326
327 int main(int argc, char *argv[])
328 {
329 OLGraph G;
330 if(CreateGraph(&G) > -1){
331 printG(G);
332 }
333 DFSTraverse(G, printvex);
334 }
有向图(十字链表存储)的强连通分量
代码运行
Example01
[jennifer@localhost Data.Structure]$ ./a.out
输入图类型: +有向图(0), -有向网(1), -无向图(2), -无向网(3): 0
以十字链表作为图的存储结构创建有向图:
输入顶点数,弧数,其他信息标志位: 4,7,0
输入第1个顶点: a
输入第2个顶点: b
输入第3个顶点: c
输入第4个顶点: d
输入第1条弧(顶点1, 顶点2): a,b
输入第2条弧(顶点1, 顶点2): a,c
输入第3条弧(顶点1, 顶点2): c,a
输入第4条弧(顶点1, 顶点2): c,d
输入第5条弧(顶点1, 顶点2): d,c
输入第6条弧(顶点1, 顶点2): d,a
输入第7条弧(顶点1, 顶点2): d,b
打印图结点
类型:有向图;顶点数 4, 弧数 7
a: {hlink=(3,d)->(0,a); (2,c)->(0,a); } {tlink=(0,a)->(2,c); (0,a)->(1,b); }
b: {hlink=(3,d)->(1,b); (0,a)->(1,b); } {tlink=}
c: {hlink=(3,d)->(2,c); (0,a)->(2,c); } {tlink=(2,c)->(3,d); (2,c)->(0,a); }
d: {hlink=(2,c)->(3,d); } {tlink=(3,d)->(1,b); (3,d)->(0,a); (3,d)->(2,c); }
深度优先搜索(以第一个顶点为尾的弧进行深度优先搜索遍历):
[0]:a [2]:c [3]:d [1]:b
退出DFS函数的次序location和顶点位置index为:
[location:0, index:1] [location:1, index:3] [location:2, index:2] [location:3, index:0]
深度优先搜索(从最后完成搜索的顶点出发,沿着以该顶点为头的弧作逆向的深度优先搜索遍历):
强连通分量:[0]:a [3]:d [2]:c
强连通分量:[1]:b
[jennifer@localhost Data.Structure]$
Example01
Example02
[jennifer@localhost blogs]$ ./a.out
输入图类型: +有向图(0), -有向网(1), -无向图(2), -无向网(3): 0
以十字链表作为图的存储结构创建有向图:
输入顶点数,弧数,其他信息标志位: 6,8,0
输入第1个顶点: 1
输入第2个顶点: 2
输入第3个顶点: 3
输入第4个顶点: 4
输入第5个顶点: 5
输入第6个顶点: 6
输入第1条弧(顶点1, 顶点2): 1,2
输入第2条弧(顶点1, 顶点2): 1,3
输入第3条弧(顶点1, 顶点2): 2,4
输入第4条弧(顶点1, 顶点2): 3,4
输入第5条弧(顶点1, 顶点2): 3,5
输入第6条弧(顶点1, 顶点2): 4,1
输入第7条弧(顶点1, 顶点2): 4,7
输入第8条弧(顶点1, 顶点2): 5,6
打印图结点
类型:有向图;顶点数 6, 弧数 8
1: {hlink=(3,4)->(0,1); } {tlink=(0,1)->(2,3); (0,1)->(1,2); }
2: {hlink=(0,1)->(1,2); } {tlink=(1,2)->(3,4); }
3: {hlink=(0,1)->(2,3); } {tlink=(2,3)->(4,5); (2,3)->(3,4); }
4: {hlink=(2,3)->(3,4); (1,2)->(3,4); } {tlink=(3,4)->(-1,<); (3,4)->(0,1); }
5: {hlink=(2,3)->(4,5); } {tlink=(4,5)->(5,6); }
6: {hlink=(4,5)->(5,6); } {tlink=}
深度优先搜索(以第一个顶点为尾的弧进行深度优先搜索遍历):
[0]:1 [2]:3 [4]:5 [5]:6 [3]:4 [1]:2
退出DFS函数的次序location和顶点位置index为:
[location:0, index:5] [location:1, index:4] [location:2, index:3] [location:3, index:2] [location:4, index:1] [location:5, index:0]
深度优先搜索(从最后完成搜索的顶点出发,沿着以该顶点为头的弧作逆向的深度优先搜索遍历):
强连通分量:[0]:1 [3]:4 [2]:3 [1]:2
强连通分量:[4]:5
强连通分量:[5]:6
[jennifer@localhost blogs]$
Example02
Example03
[jennifer@localhost blogs]$ ./a.out
输入图类型: +有向图(0), -有向网(1), -无向图(2), -无向网(3): 0
以十字链表作为图的存储结构创建有向图:
输入顶点数,弧数,其他信息标志位: 8,13,0
输入第1个顶点: a
输入第2个顶点: b
输入第3个顶点: c
输入第4个顶点: d
输入第5个顶点: e
输入第6个顶点: f
输入第7个顶点: g
输入第8个顶点: h
输入第1条弧(顶点1, 顶点2): a,b
输入第2条弧(顶点1, 顶点2): b,c
输入第3条弧(顶点1, 顶点2): d,c
输入第4条弧(顶点1, 顶点2): c,d
输入第5条弧(顶点1, 顶点2): e,a
输入第6条弧(顶点1, 顶点2): b,e
输入第7条弧(顶点1, 顶点2): b,f
输入第8条弧(顶点1, 顶点2): c,g
输入第9条弧(顶点1, 顶点2): h,d
输入第10条弧(顶点1, 顶点2): e,f
输入第11条弧(顶点1, 顶点2): g,f
输入第12条弧(顶点1, 顶点2): f,g
输入第13条弧(顶点1, 顶点2): h,g
打印图结点
类型:有向图;顶点数 8, 弧数 13
a: {hlink=(4,e)->(0,a); } {tlink=(0,a)->(1,b); }
b: {hlink=(0,a)->(1,b); } {tlink=(1,b)->(5,f); (1,b)->(4,e); (1,b)->(2,c); }
c: {hlink=(3,d)->(2,c); (1,b)->(2,c); } {tlink=(2,c)->(6,g); (2,c)->(3,d); }
d: {hlink=(7,h)->(3,d); (2,c)->(3,d); } {tlink=(3,d)->(2,c); }
e: {hlink=(1,b)->(4,e); } {tlink=(4,e)->(5,f); (4,e)->(0,a); }
f: {hlink=(6,g)->(5,f); (4,e)->(5,f); (1,b)->(5,f); } {tlink=(5,f)->(6,g); }
g: {hlink=(7,h)->(6,g); (5,f)->(6,g); (2,c)->(6,g); } {tlink=(6,g)->(5,f); }
h: {hlink=} {tlink=(7,h)->(6,g); (7,h)->(3,d); }
深度优先搜索(以第一个顶点为尾的弧进行深度优先搜索遍历):
[0]:a [1]:b [5]:f [6]:g [4]:e [2]:c [3]:d [7]:h
退出DFS函数的次序location和顶点位置index为:
[location:0, index:6] [location:1, index:5] [location:2, index:4] [location:3, index:3] [location:4, index:2] [location:5, index:1] [location:6, index:0] [location:7, index:7]
深度优先搜索(从最后完成搜索的顶点出发,沿着以该顶点为头的弧作逆向的深度优先搜索遍历):
强连通分量:[7]:h
强连通分量:[0]:a [4]:e [1]:b
强连通分量:[2]:c [3]:d
强连通分量:[5]:f [6]:g
[jennifer@localhost blogs]$
Example03
Example04
[jennifer@localhost blogs]$ ./a.out
输入图类型: +有向图(0), -有向网(1), -无向图(2), -无向网(3): 0
以十字链表作为图的存储结构创建有向图:
输入顶点数,弧数,其他信息标志位: 10,15
输入第1个顶点: ^C
[jennifer@localhost blogs]$ ./a.out
输入图类型: +有向图(0), -有向网(1), -无向图(2), -无向网(3): 0
以十字链表作为图的存储结构创建有向图:
输入顶点数,弧数,其他信息标志位: 10,15,0
输入第1个顶点: 0
输入第2个顶点: 1
输入第3个顶点: 2
输入第4个顶点: 3
输入第5个顶点: 4
输入第6个顶点: 5
输入第7个顶点: 6
输入第8个顶点: 7
输入第9个顶点: 8
输入第10个顶点: 9
输入第1条弧(顶点1, 顶点2): 9,2
输入第2条弧(顶点1, 顶点2): 7,9
输入第3条弧(顶点1, 顶点2): 2,7
输入第4条弧(顶点1, 顶点2): 2,1
输入第5条弧(顶点1, 顶点2): 2,4
输入第6条弧(顶点1, 顶点2): 7,4
输入第7条弧(顶点1, 顶点2): 1,8
输入第8条弧(顶点1, 顶点2): 0,1
输入第9条弧(顶点1, 顶点2): 1,0
输入第10条弧(顶点1, 顶点2): 0,4
输入第11条弧(顶点1, 顶点2): 8,5
输入第12条弧(顶点1, 顶点2): 5,0
输入第13条弧(顶点1, 顶点2): 4,3
输入第14条弧(顶点1, 顶点2): 3,4
输入第15条弧(顶点1, 顶点2): 5,6
打印图结点
类型:有向图;顶点数 10, 弧数 15
0: {hlink=(5,5)->(0,0); (1,1)->(0,0); } {tlink=(0,0)->(4,4); (0,0)->(1,1); }
1: {hlink=(0,0)->(1,1); (2,2)->(1,1); } {tlink=(1,1)->(0,0); (1,1)->(8,8); }
2: {hlink=(9,9)->(2,2); } {tlink=(2,2)->(4,4); (2,2)->(1,1); (2,2)->(7,7); }
3: {hlink=(4,4)->(3,3); } {tlink=(3,3)->(4,4); }
4: {hlink=(3,3)->(4,4); (0,0)->(4,4); (7,7)->(4,4); (2,2)->(4,4); } {tlink=(4,4)->(3,3); }
5: {hlink=(8,8)->(5,5); } {tlink=(5,5)->(6,6); (5,5)->(0,0); }
6: {hlink=(5,5)->(6,6); } {tlink=}
7: {hlink=(2,2)->(7,7); } {tlink=(7,7)->(4,4); (7,7)->(9,9); }
8: {hlink=(1,1)->(8,8); } {tlink=(8,8)->(5,5); }
9: {hlink=(7,7)->(9,9); } {tlink=(9,9)->(2,2); }
深度优先搜索(以第一个顶点为尾的弧进行深度优先搜索遍历):
[0]:0 [4]:4 [3]:3 [1]:1 [8]:8 [5]:5 [6]:6 [2]:2 [7]:7 [9]:9
退出DFS函数的次序location和顶点位置index为:
[location:0, index:3] [location:1, index:4] [location:2, index:6] [location:3, index:5] [location:4, index:8] [location:5, index:1] [location:6, index:0] [location:7, index:9] [location:8, index:7] [location:9, index:2]
深度优先搜索(从最后完成搜索的顶点出发,沿着以该顶点为头的弧作逆向的深度优先搜索遍历):
强连通分量:[2]:2 [9]:9 [7]:7
强连通分量:[0]:0 [5]:5 [8]:8 [1]:1
强连通分量:[6]:6
强连通分量:[4]:4 [3]:3
[jennifer@localhost blogs]$
Example04