Tarjan算法学习笔记

在有向图G中,如果两个顶点u,v间有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个顶点强连通。

连通图的极大连通子图就是他自己,非连通图有多个极大连通子图(说傻话就是分拨,连着的分一拨,每拨都是极大连通子图)(极大:能包含的边都包含进去了e)

如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向非强连通图的极大强连通子图,称为强连通分量(单蹦的点也可以作为强连通分量)

tarjan算法的时间复杂度为 O ( n + m ) O(n+m) O(n+m)

树枝边:DFS时经过的边,即DFS搜索树上的边

后向边:与DFS方向相反,从某个结点指向其某个祖先的边(返祖边)

横叉边:从某个结点指向搜索树中的另一子树中的某结点的边

dfn:时间戳 也就是dfs序
low:追溯值 当前这个点能跑到的点中dfn的最小值

然后就是算法流程,如果我以后忘了的话或者觉得自己又无法理解tarjan算法本质,那就看看这个dalao的算法流程模拟
%%%

tarjan 求强联通分量模板

#include<bits/stdc++.h>
#define ll long long 
#define INF 0x3f3f3f3f
using namespace std;
const int N=1e4+10,M=N<<1;
int head[N],ver[M],nxt[M],idx;
int dfn[N],low[N],stk[N],top;
bool vis[N];
int bel[N],cap[N],num,cnt;
int n,m;
void add(int u,int v){
	ver[idx]=v,nxt[idx]=head[u],head[u]=idx++;
}
void tarjan(int x){
	dfn[x]=low[x]=++cnt;
	stk[++top]=x;
	vis[x]=1;
	for(int i=head[x];~i;i=nxt[i]){
		int y=ver[i];
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);//如果是一个强连通分量里的点,在回溯的过程中,环上的low都会被更新成当前强连通分量起点的dfn
		}else if(vis[y]) low[x]=min(low[x],dfn[y]);//反向边,可以理解为到了一个环的起点,那就更新这个点的dfn
	}
	if(low[x]==dfn[x]){
		num++;
		int y;
		do{
			y=stk[top--];
			vis[y]=0;//把标记取消后面遍历的点上面的两个if都进不去  因为后面在遍历的点肯定不是在此强连通分量中 
			bel[y]=num;
			++cap[num];
		}while(x!=y);
	}
} 
int main(){
	memset(head,-1,sizeof head);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	} 
	for(int i=1;i<=n;++i)
		if(!dfn[i]) tarjan(i);//这是防止有多个连通块 
	printf("%d %d\n",num,cap[3]);
	return 0;
}
/*
6 8
1 2 
1 3 
3 5 
5 6
4 6
2 4
4 1
3 4
ans:3 4
强连通分量:{1,3,4,2},{5},{6} 
*/

怒刷两道水题找自信 P2341 P2863

给定无向连通图 G = ( V , E ) G =(V,E) G=(V,E)

若对于 x ∈ V x \in V xV ,从图中删去节点x以及与x关联的边之后,G分裂成两个或两个以上不相连的子图,则称x为 G G G 的割点

若对于 e ∈ E e\in E eE,从图中删去边e之后,G分裂成两个不相连的子图,则称e为G的桥或割边

一般无向图(不一定连通) 的“割点”和“桥”就是它的各个连通块的“割点”和“桥”


割点的判断:在我学完tarjan求强连通分量后,再学割点就感觉没那么困难了

就是割点分两种情况:判根和判非根

判根:分两种情况 第一种就是它的son only 1,那这个根不是割点

第二种就是son的数量 ≥ 2 \geq 2 2,其中如果有几个儿子在删去root还在一个团中,那肯定不行的,但是因为我们的tarjan是基于dfs
的,那肯定遍历的时候这些兔崽子都会遍历到,所以根本不用慌

判非根:给定一条边 ( u , v ) (u,v) (u,v),如果 d f n u ≤ l o w v dfn_u\leq low_v dfnulowv 那u就是割点 一点毛病没有


盲敲了一会模板发现一个问题:就是割点里用不到vis数组,所以我现在思考这个问题

  • 首先割点就是分访问过和没访问过用dfn来表示很好理解
  • 在求强连通分量的时候,vis数组在求完一个强联通分量后以后再有点乱入,但是求割点你乱不乱入的没啥关系,你割点还是你割点
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N=2e4+10,M=2e5+10;
int n,m,dfn[N],low[N],cnt,root,vis[N];
bool cut[N];
int head[N],ver[M],nxt[M],idx;
void tarjan(int x) {
	int son=0;
	dfn[x]=low[x]=++cnt;
	for(int i=head[x]; ~i; i=nxt[i]) {
		int y=ver[i];
		if(!dfn[y]) {
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(x==root) son++;
			if(dfn[x]<=low[y]&&x!=root) cut[x]=1;
		} 
		else low[x]=min(low[x],dfn[y]);
	}
	if(x==root&&son>=2) cut[x]=1;
}
void add(int u,int v){
	ver[idx]=v,nxt[idx]=head[u],head[u]=idx++;
}
int main() {
	memset(head,-1,sizeof head);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; ++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	for(int i=1; i<=n; ++i) if(!dfn[i]) root=i,tarjan(i);
	int ans=0;
	for(int i=1; i<=n; ++i) if(cut[i]) ans++;
	printf("%d\n",ans);
	for(int i=1; i<=n; ++i) if(cut[i]) printf("%d ",i);
	return 0;
}

割边应该就改一改就行,先不写了。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Tarjan算法和Kosaraju算法都是求解有向图强连通分量的算法,它们的时间复杂度都为O(N+M),其中N为图中节点数,M为图中边数。 Tarjan算法的基本思想是通过DFS遍历图中的节点,并在遍历的过程中维护一个栈,用于存储已经遍历过的节点。在遍历的过程中,对于每个节点,记录它被遍历到的时间戳和能够到达的最小时间戳,当一个节点的最小时间戳等于它自身的时间戳时,说明这个节点及其之前遍历到的节点构成了一个强连通分量,将这些节点从栈中弹出即可。 Kosaraju算法的基本思想是先对原图进行一次DFS,得到一个反向图,然后再对反向图进行DFS。在第二次DFS的过程中,每次从未被访问过的节点开始遍历,遍历到的所有节点构成一个强连通分量。 两种算法的具体实现可以参考以下代码: ```python # Tarjan算法 def tarjan(u): dfn[u] = low[u] = timestamp timestamp += 1 stk.append(u) for v in graph[u]: if not dfn[v]: tarjan(v) low[u] = min(low[u], low[v]) elif v in stk: low[u] = min(low[u], dfn[v]) if dfn[u] == low[u]: scc = [] while True: v = stk.pop() scc.append(v) if v == u: break scc_list.append(scc) # Kosaraju算法 def dfs1(u): vis[u] = True for v in graph[u]: if not vis[v]: dfs1(v) stk.append(u) def dfs2(u): vis[u] = True scc.append(u) for v in reverse_graph[u]: if not vis[v]: dfs2(v) # 构建图和反向图 graph = [[] for _ in range(n)] reverse_graph = [[] for _ in range(n)] for u, v in edges: graph[u].append(v) reverse_graph[v].append(u) # Tarjan算法求解强连通分量 dfn = [0] * n low = [0] * n timestamp = 1 stk = [] scc_list = [] for i in range(n): if not dfn[i]: tarjan(i) # Kosaraju算法求解强连通分量 vis = [False] * n stk = [] scc_list = [] for i in range(n): if not vis[i]: dfs1(i) vis = [False] * n while stk: u = stk.pop() if not vis[u]: scc = [] dfs2(u) scc_list.append(scc) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值