文字描述
有向圖強連通分量的定義:在有向圖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]$
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]$
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]$
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]$