tarjan算法详解--图论--强连通图

1.有向图连通性(强连通图)

1.1有向图

如图所示,此图是个有向图。
在这里插入图片描述

1.2有向图连通性

也叫强连通图,能从任意一个顶点到达另一个点
注意不一定是双向边!

2.tarjan算法简述

首先,我们要将一个图拆散,看作几个环图
如下图
在这里插入图片描述
将此图看作这几部分环图,如下图所示
在这里插入图片描述
抽象后如下图所示
在这里插入图片描述
这就是 tarjan 最难理解的点 —— 缩点
其他的。。。就很好理解了吧???
下面看我的注释吧

3.实现过程

3.1 例题-刻录光盘

自己去看题[doge]

3.1.1 输入&&初始化

注意 一定要初始化 就因为没初始化我查了半个小时的错

memset(head , -1 , sizeof(head)) ;
	memset(edge , -1 , sizeof(edge)) ;
	
	scanf("%d" , &n) ;
	for(int i = 1;i <= n;++i){
		int a ;
		scanf("%d" , &a) ;
		while(a != 0){
			add(i , a) ;
			scanf("%d" , &a) ;
		}
	}

这里我用的 链式前向星 从0开始 初始化-1
链式前向星用法参下

struct Edge{
	int to ;
	int next ;
}edge[40005] ;

void add(int from , int to){
	edge[cnt].to = to ;
	edge[cnt].next = head[from] ;
	head[from] = cnt++ ;
}

3.1.2 tarjan(类似深搜)

需要进行每个点的遍历

for(int i = 1;i <= n;++i){
		if(!vis[i]){
			tarjan(i) ;
		}
	}

然后像深搜一样进行搜索

void tarjan(int u){
	tts++ ;//发现时间增加
	dfn[u] = low[u] = tts ;//记录发现时间、后代发现时间最小值
	vis[u] = true ;//标记已访问过
	instack[u] = true ;//记录已经入栈
	tj.push(u) ;//入栈
	for(int i = head[u];i != -1;i = edge[i].next){
		if(!vis[edge[i].to]){//如果没访问过
			int v = edge[i].to ;
			tarjan(v) ;//tarjan算法继续搜
			low[u] = min(low[v] , low[u]) ;//回溯:
			//如果发现时间比后代的发现时间大,更新后代发现时间最小值
		}else if(instack[edge[i].to]){
			low[u]= min(low[u] , dfn[edge[i].to]) ;
			//如果发现过此节点=,并且还在数据栈中,说明是h图,更新发现时间最小值
		}
	}
	
	//出环操作
	if(dfn[u] == low[u]){
	//如果发现时间 == 后代发现时间
		count++ ;
		//新的一个环,环数自增1
		int v = 0 ;
		do{
		//记录此元素颜色并出栈
			v = tj.top() ;
			color[v] = count ;
			tj.pop() ;
			instack[v] = false ;//记录也要出
		}while(v != u) ;
	}
}

3.1.3 缩点

已经讲过,不再赘述

for(int i = 1;i <= n;++i){
		for(int j = head[i];j != -1;j = edge[j].next){
			int v = edge[j].to ;
			if(color[i] != color[v]){
				din[color[v]]++ ;
			}
		}
	}

	int ans = 0 ;
	for(int i = 1;i <= count;++i){
		if(din[i] == 0){
			ans++ ;
		}
	}
	printf("%d" , ans) ;

至此完成算法。

3.2 完整答案

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std ;

int head[205] ;
int cnt ;
int low[205] ,dfn[205] ;

struct Edge{
	int to ;
	int next ;
}edge[40005] ;

void add(int from , int to){
	edge[cnt].to = to ;
	edge[cnt].next = head[from] ;
	head[from] = cnt++ ;
}

bool vis[205] ;
bool instack[205] ;
stack<int> tj ;
int count = 0 ;
int color[205] ;
int tts = 0 ;

void tarjan(int u){
	tts++ ;//
	dfn[u] = low[u] = tts ;//
	vis[u] = true ;
	instack[u] = true ;
	tj.push(u) ;
	for(int i = head[u];i != -1;i = edge[i].next){
		if(!vis[edge[i].to]){
			int v = edge[i].to ;
			tarjan(v) ;
			low[u] = min(low[v] , low[u]) ;
		}else if(instack[edge[i].to]){
			low[u]= min(low[u] , dfn[edge[i].to]) ;
		}
	}
	
	if(dfn[u] == low[u]){
		count++ ;
		int v = 0 ;
		do{
			v = tj.top() ;
			color[v] = count ;
			tj.pop() ;
			instack[v] = false ;//
		}while(v != u) ;
	}
}

int n ;
int din[205] ;

int main(){
	memset(head , -1 , sizeof(head)) ;
	memset(edge , -1 , sizeof(edge)) ;
	
	scanf("%d" , &n) ;
	for(int i = 1;i <= n;++i){
		int a ;
		scanf("%d" , &a) ;
		while(a != 0){
			add(i , a) ;
			scanf("%d" , &a) ;
		}
	}
	
	for(int i = 1;i <= n;++i){
		if(!vis[i]){
			tarjan(i) ;
		}
	}
	 
	for(int i = 1;i <= n;++i){
		for(int j = head[i];j != -1;j = edge[j].next){
			int v = edge[j].to ;
			if(color[i] != color[v]){
				din[color[v]]++ ;
			}
		}
	}

	int ans = 0 ;
	for(int i = 1;i <= count;++i){
		if(din[i] == 0){
			ans++ ;
		}
	}
	printf("%d" , ans) ;
	return 0 ;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值