一个有向图称为半连通(Semi-Connected),满足:对于图中任两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。 若满足,则称G’是G的一个导出子图。
若G’是G的导出子图,且G’半连通,则称G’为G的半连通子图。若G’是G所有半连通子图中包含节点数最多的,则称G’是G的最大半连通子图。
判断一个图是不是半连通图
求解:<1>Kosarsju算法: [1] 新图DFS [2] 方法2
<2>Tarjan算法:[1] 新图DFS
【1】新图DFS
1 void init_judge(void) 2 { 3 for(int i=1;i<=num_scc;i++) 4 { 5 vis_scc[i]=0; 6 } 7 root=0; 8 is_halfSCC=0; 9 } 10 void judge_halfSCC(int u,int depth) 11 { 12 ENode *ptr=(ENode *)malloc(sizeof(ENode)); 13 int son; 14 15 if(depth == num_scc) 16 is_halfSCC = 1; 17 else 18 { 19 ptr=rebuild_ALG->vlist[u].firstedge; 20 while(ptr!=NULL) 21 { 22 son=ptr->key; 23 if(!vis_scc[son]) 24 { 25 vis_scc[son]=1; 26 judge_halfSCC(son,depth+1); 27 vis_scc[son]=0; //回溯时要用到 28 } 29 ptr=ptr->next; 30 } 31 } 32 } 33 init_judge(); //【3】half_SCC判定 【主函数程序段】 34 for(int cn=1;cn<=num_scc;cn++) 35 if(in_d[cn] == 0) 36 root = cn; //找到入度为0的点,做起点dfs 37 vis_scc[root]=1; 38 judge_halfSCC(root,1); 39 40 if(is_halfSCC) printf("Yes\n"); 41 else printf("No\n");
【2】方法2(仅适应于kosaraju算法)
求出缩点后所有顶点的入度ind[]。思考:如果原图G要是半连通的,那么缩点后的图mat必须要连通,这是基础的前提,不然原图都是不连通的,这时只要判断mat中顶点是否只有一个入度为0的点,如果当前的 DAG 有不止一个入度为 0 的点,那么这些点之间是不可到达的,导致图G不是半连通的。此外,mat就是一棵树,入度为0的顶点就是根,如果这个树不是一条链,那么图G也不是半连通的,不是链就说明有分叉,两个分叉之间是不能到达的,那么如何判断是否有分叉呢?答案是拓扑排序,如果排序到某个节点后,剩下的顺序不能确定,就说明出现了分叉。
其实程序是判断树的高度是否==num_scc。
1 void judge_half_SCC(void) 2 { 3 int i; 4 ENode *ptr=(ENode *)malloc(sizeof(ENode)); 5 6 num_indegree_0=0; 7 for(i=1;i<=num_scc;i++) 8 { 9 if(in_degree[i]==0) 10 num_indegree_0++; 11 } 12 13 if(num_indegree_0 > 1) //优先判断重构图是否连通 14 printf("No\n"); 15 else 16 { 17 depth=0; //按层处理 18 while(1) 19 { 20 num_indegree_0=0; 21 for(i=1;i<=num_scc;i++) 22 if(in_degree[i] == 0) 23 { 24 root=i; 25 num_indegree_0++; 26 } 27 if(num_indegree_0>1 || num_indegree_0==0) 28 break; 29 30 in_degree[root]=-1; //标记+下层遍历 31 depth++; 32 ptr=rebuild_ALG->vlist[root].firstedge; 33 while(ptr!=NULL) 34 { 35 in_degree[ptr->key]--; 36 ptr=ptr->next; 37 } 38 } 39 if(depth==num_scc) //若相等 40 printf("Yes\n"); 41 else printf("No\n"); 42 } 43 }
求出有向图的最大半连通子图
tarjan或kosarju缩点之后变为DAG,最大节点数即为“最长链”,一条链的长度定义为所有节点的权值之和,每个scc的权值为它的节点个数。一个注意的地方就是tarjan之后重构图的时候会加入重边,要消除重边影响。一个SCC里所有点之间都是半连通的。如果两个强连通之间有边,那么这两个强连通中的任意点也是半连通的。
[1].找出入度为0的点做DFS,并统计count权值
[2].count是把父亲节点的num向孩子节点加;
[3].找出count数组中的最大值max_count即可;
[4].计算出与max_count相等的个数,即max_halfSCC个数
在程序中,首先要记录下每个SCC包含的顶点个数num[i]。
1 /*深度优先搜索寻找最大权值*/
2 void init_find(void)
3 {
4 for(int i=1;i<=num_scc;i++)
5 {
6 vis_scc[i]=0;
7 count[i]=num[i];
8 }
9 root=0;
10 }
11 void Find_max_halfSCC(int u)
12 {
13 ENode *ptr=(ENode *)malloc(sizeof(ENode));
14 int son;
15
16 ptr=rebuild_ALG->vlist[u].firstedge;
17 while(ptr!=NULL)
18 {
19 son=ptr->key;
20 if(!vis_scc[son])
21 {
22 vis_scc[son]=1;
23 count[son]+=count[u];
24 Find_max_halfSCC(son);
25 vis_scc[son]=0; //回溯时要用到
26 }
27 ptr=ptr->next;
28 }
29 }
30 init_find();
31 for(int cnt=1;cnt<=num_scc;cnt++) //【3.判定】
32 if(in_degree[cnt] == 0)
33 {
34 root=cnt; //找到入度为0的点,做起点DFS
35
36 vis_scc[root]=1; //也可以拿到if外面,这样只是为了考虑in_d=0个数不止一个的情况
37 count[root]=num[root];
38 Find_max_halfSCC(root);
39 }
// 然后再执行第[3][4]步即可。至于求解最大半连通子图中的顶点,只要对新图的逆表作dfs即可。
40 void DFS_reverse_rebuild_ALG(int u)
41 {
42 int son;
43 ENode *ptr=(ENode *)malloc(sizeof(ENode));
44
45 vis_scc[u]=1; //标记+访问+遍历
46 for(int v=0;v<ALG->n;v++)
47 if(u == belong[v])
48 printf("%c ",ALG->vlist[v].vertex); //输出当前强连通分量u中的顶点
49 ptr=reverse_rebuild_ALG->vlist[u].firstedge;
50 while(ptr!=NULL)
51 {
52 son=ptr->key;
53 if(!vis_scc[son])
54 {
55 vis_scc[son]=1;
56 DFS_reverse_rebuild_ALG(son);
57 vis_scc[son]=0;
58 }
59 ptr=ptr->next;
60 }
61 }
62 memset(vis_scc,0,sizeof(vis_scc));
63 for(int ii=1;ii<=num_scc;ii++) //对新图的逆表做一次dfs
64 {
65 if(count[ii] == max_count && !vis_scc[ii])
66 {
67 vis_scc[ii]=1;
68 DFS_reverse_rebuild_ALG(ii);
69 vis_scc[ii]=0; //回溯时用
70 }
71 printf("\n");
72 }