半连通分量--Tarjan/Kosaraju算法

         一个有向图称为半连通(Semi-Connected),满足:对于图中任两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。 若满足,则称G’是G的一个导出子图。

        若G’是G的导出子图,且G’半连通,则称G’为G的半连通子图。若G’是G所有半连通子图中包含节点数最多的,则称G’是G的最大半连通子图。

【1】判断一个图是不是半连通图

    求解:<1>Kosarsju算法: [1] 新图DFS    [2] 方法2

                <2>Tarjan算法:[1] 新图DFS

【1】新图DFS

void init_judge(void)
{
	for(int i=1;i<=num_scc;i++)
	{
		vis_scc[i]=0;
	}
	root=0;
	is_halfSCC=0;
}
void judge_halfSCC(int u,int depth)
{
	ENode *ptr=(ENode *)malloc(sizeof(ENode));
	int son;

	if(depth == num_scc)
		is_halfSCC = 1;
	else
	{
		ptr=rebuild_ALG->vlist[u].firstedge;
		while(ptr!=NULL)
		{
			son=ptr->key;
			if(!vis_scc[son])
			{
				vis_scc[son]=1;
				judge_halfSCC(son,depth+1);
				vis_scc[son]=0;  //回溯时要用到
			}
			ptr=ptr->next;
		}
	}
}
	init_judge();   //【3】half_SCC判定  【主函数程序段】
	for(int cn=1;cn<=num_scc;cn++)
		if(in_d[cn] == 0)
			root = cn;  //找到入度为0的点,做起点dfs
	vis_scc[root]=1;
	judge_halfSCC(root,1);

	if(is_halfSCC)	printf("Yes\n");
	else  printf("No\n");

 

【2】方法2(仅适应于kosaraju算法)

      求出缩点后所有顶点的入度ind[]。思考:如果原图G要是半连通的,那么缩点后的图mat必须要连通,这是基础的前提,不然原图都是不连通的,这时只要判断mat中顶点是否只有一个入度为0的点,如果当前的 DAG 有不止一个入度为 0 的点,那么这些点之间是不可到达的,导致图G不是半连通的。此外,mat就是一棵树,入度为0的顶点就是根,如果这个树不是一条链,那么图G也不是半连通的,不是链就说明有分叉,两个分叉之间是不能到达的,那么如何判断是否有分叉呢?答案是拓扑排序,如果排序到某个节点后,剩下的顺序不能确定,就说明出现了分叉。

      其实程序是判断树的高度是否==num_scc。

void judge_half_SCC(void)
{
	int i;
	ENode *ptr=(ENode *)malloc(sizeof(ENode));

	num_indegree_0=0;
	for(i=1;i<=num_scc;i++)
	{
		if(in_degree[i]==0)
			num_indegree_0++;
	}

	if(num_indegree_0 > 1)  //优先判断重构图是否连通
		printf("No\n");
	else
	{
		depth=0;  //按层处理
		while(1)
		{
			num_indegree_0=0;
			for(i=1;i<=num_scc;i++)
				if(in_degree[i] == 0)
				{
					root=i;
					num_indegree_0++;
				}
			if(num_indegree_0>1 || num_indegree_0==0)
				break;

			in_degree[root]=-1; //标记+下层遍历
			depth++;
			ptr=rebuild_ALG->vlist[root].firstedge;
			while(ptr!=NULL)
			{
				in_degree[ptr->key]--;
				ptr=ptr->next;
			}
		}
		if(depth==num_scc)  //若相等
			printf("Yes\n");
		else  printf("No\n");
	}
}

【2】求出有向图的最大半连通子图

      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]。

/*深度优先搜索寻找最大权值*/
void init_find(void)
{
	for(int i=1;i<=num_scc;i++)
	{
		vis_scc[i]=0;
		count[i]=num[i];
	}
	root=0;
}
void Find_max_halfSCC(int u)
{
	ENode *ptr=(ENode *)malloc(sizeof(ENode));
	int son;

	ptr=rebuild_ALG->vlist[u].firstedge;
	while(ptr!=NULL)
	{
		son=ptr->key;
		if(!vis_scc[son])
		{
			vis_scc[son]=1;
			count[son]+=count[u];
			Find_max_halfSCC(son);
			vis_scc[son]=0;  //回溯时要用到
		}
		ptr=ptr->next;
	}
}
<span style="white-space:pre">	</span>init_find();
	for(int cnt=1;cnt<=num_scc;cnt++)  //【3.判定】
		if(in_degree[cnt] == 0)
		{
			root=cnt;  //找到入度为0的点,做起点DFS
			
			vis_scc[root]=1;  //也可以拿到if外面,这样只是为了考虑in_d=0个数不止一个的情况
			count[root]=num[root];
			Find_max_halfSCC(root);
		}                                                                                                                                         然后再执行第[3][4]步即可。至于求解最大半连通子图中的顶点,只要对新图的逆表作dfs即可。
void DFS_reverse_rebuild_ALG(int u)
{
	int son;
	ENode *ptr=(ENode *)malloc(sizeof(ENode));

	vis_scc[u]=1;   //标记+访问+遍历
	for(int v=0;v<ALG->n;v++)
		if(u == belong[v])
			printf("%c ",ALG->vlist[v].vertex);  //输出当前强连通分量u中的顶点
	ptr=reverse_rebuild_ALG->vlist[u].firstedge;
	while(ptr!=NULL)
	{
		son=ptr->key;
		if(!vis_scc[son])
		{
			vis_scc[son]=1;
			DFS_reverse_rebuild_ALG(son);
			vis_scc[son]=0;
		}
		ptr=ptr->next;
	}
}
<span style="white-space:pre">	</span>memset(vis_scc,0,sizeof(vis_scc));
	for(int ii=1;ii<=num_scc;ii++)  //对新图的逆表做一次dfs
	{
		if(count[ii] == max_count && !vis_scc[ii])
		{
			vis_scc[ii]=1;
			DFS_reverse_rebuild_ALG(ii);
			vis_scc[ii]=0;  //回溯时用
		}
		printf("\n");
	}
 

                
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值