tarjan算法(一)

最近几天准备根据VJ上面kuangbin带你飞专题的题把tarjan算法学习一下,这一篇是根据POJ1236来学习通过tarjan算法查找强连通分量

这个blog对于tarjan算法的讲解很详细,先贴在这里:http://www.lydshy.com/wordpress/92 和 http://www.lydshy.com/wordpress/115

下面是我自己的一些理解

[有向图强连通分量]

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components),简记为SCC。


tarjan算法是通过深度优先搜索(DFS)来查找强连通分量的,其中个有两个很重要的标记,dfn[i]和low[i],dfn[i]表示使用深搜搜索到节点i时所搜索的次数,low[i]表示包含i的子树的根节点最小的dfn·····或者说是从i可以到达的节点中的dfn最小的节点的dfn,啊,感觉还是很拗口,我去画个图····

5号节点能追溯到的dfn最小的节点是2好,其low为2,所以low[5]=2;而且每次搜索都把当前节点压入栈中,当搜索了当前节点中所有的点之后,如果low[k]==dfn[k],则栈中从k到栈顶的所有元素属于一个连通分量

以下算法流程是摘抄自开头的博客

接下来是对算法流程的演示。

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。


返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。


返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。


继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。


至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。


以下是POJ1236的题目和题解

A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B 
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school. 
Input
The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.
Output
Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.
Sample Input
5
2 4 3 0
4 5 0
0
0
1 0
Sample Output
1
2


题目大意:有一些学校可以相互传输(单向)信息,现在给出其联系图,让你求出要使所有人都收到信息,最少要发送几个信息,和最少增加几条线路可以使只发送一条信息就能使所有人都收到

题解:第一个答案是所有入度为0的强连通分量的个数,第二个答案是所有入度为0和所有出度为0的连通分量数中的最大值的个数,这一点画个简易的图就可以看出来了

下面是代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>

using namespace std;
#define MAX 105
int head[MAX];
int u[MAX*MAX];
int v[MAX*MAX];
int _next[MAX*MAX];

int low[MAX];
int dfn[MAX];//可以代替vis【MAX】
int cnt[MAX];//每一个点所属的联通分量的标号 
int re;//联通分量个数 
int stack[MAX];
int top;
int in[MAX];
int out[MAX];
void  dfs(int k,int num){
	dfn[k]=low[k]=num;
	stack[++top]=k;
	for(int i=head[k];i!=-1;i=_next[i]){
		int e=v[i];
		if(!dfn[e]){ 
			dfs(e,++num);
			low[k]=min(low[k],low[e]);
		}
		else if(dfn[e]) low[k]=min(low[k],dfn[e]);//这里为什么是min(low,dfn)之后要画图解释 	
	}
	if(dfn[k]==low[k]){ //dfn==low 出栈 
		re++;
		do{	
			cnt[stack[top]]=re;
		}while(stack[top--]!=k);//之前因为--放到循环里面导致出错  习惯让栈顶空着了···· 
	}
}

int main(){
	int n;
	while(~scanf("%d",&n)){
		int t;
		int c=0;
		memset(head,-1,sizeof(head));
		memset(dfn,0,sizeof(dfn));
		memset(in,0,sizeof(in));
		memset(out,0,sizeof(out));
		top=0;
		re=0;
		for(int i=1;i<=n;i++){
			while(scanf("%d",&t)&&t){
				u[c]=i;
				v[c]=t;
				_next[c]=head[i];
				head[i]=c++;
			}
		}
		for(int i=1;i<=n;i++){
			if(!dfn[i]) dfs(i,1);
		}
		int ans1=0,ans2=0;
		for(int i=0;i<c;i++){
			if(cnt[u[i]]!=cnt[v[i]]){
				in[cnt[v[i]]]++;
				out[cnt[u[i]]]++;
			}
		}
		for(int i=1;i<=re;i++){
			if(!in[i]) ans1++;
			if(!out[i]) ans2++;
		}
		if(re==1) printf("1\n0\n");
		else printf("%d\n%d\n",ans1,max(ans1,ans2));
	}
}
 

之前也看到过一些代码,里面对于low[v]的更新不区分是树枝边还是后向边(指向在当前栈中的点的边),一律都是low[u]=min(low[v],low[u]),这两种的区别我现在还不是非常的清楚,但是好像在找割点和桥的时候是有很大区别的,我在这里推荐树枝边和后向边分开来更新,就是我的代码中的更新方法,如果之后我有更深的理解会进行补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值