Tarjan算法模板
算法简介:
Tarjan 算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
强联通分量(Strongly Connected Components)(SCC)含义:有向图中任意两点间可达,实际上形成一个环。
有向图的 DFS 生成树主要有 4 种边(不一定全部出现):
树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点(白点)的时候就形成了一条树边。
返祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边(用栈来判断)。
横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过且不在栈中的结点。
前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点(黑点dfn[u]<dfn[v])的时候形成的。
无向图不存在横叉边和前向边。
在有向图中,我们经常需要把一个SCC缩成一个点,然后生成一个有向无环图(DAG),或把一个无向图缩点后变成一棵树,然后可以有很多优秀的性质进行解决。
算法实现:
Step 1.从图的某一点 u 开始,对图进行 DFS(u),赋上 dfn[u] 值和 low[u] 值;
Step 2.DFS 时先将 u 压入栈中,然后遍历邻接边,邻接边定点为 v;
Step 3.若(u,v)为树边,则DFS(v),回溯时更新:low[u] = min(low[u], low[v]);
Step 4.若(u,v)为返祖边,更新:low[u] = min(low[u], dfn[v]);
Step 5.节点 u 变黑,即其所有子树访问结束时,若 dfn[u]==low[u] 时,此时栈顶节点到节点 u,为一个 SCC。
模板详解:天立OI专题训练提高组:图论-tarjan A.缩点1 http://47.108.49.170/contest/31/problem/1
题目描述:
有一个 n 个点,m 条边的有向图,请求出这个图点数大于 1 的强联通分量个数。
输入格式:
第一行为两个整数 n 和 m 。
第二行至 m + 1 行,每一行有两个整数 a 和 b ,表示有一条从 a 到 b 的有向边。
输出格式:
仅一行,表示点数大于 1 的强联通分量个数。
样例输入:
5 4
2 4
3 5
1 2
4 1
样例输出:
1
数据范围与提示:
对于全部的测试点,保证 2 <= n <= 2 * 104,2 <= m < 5 * 104,1 <= a,b <= n。
核心代码:
1 int Time,low[maxn],dfn[maxn],n,m,a,b;//Time表示节点u当前的时间戳,dfn数组记录节点u的时间戳,low数组记录节点u不通过树边能够到达的时间戳最小的节点
2 int cnt,top,stl[maxn],siz[maxn],head[maxn],len;//手写stl数组代替栈,top表示栈顶,cnt表示新图节点数,siz[i]表示新图节点i由多少个旧图节点组成
3 bool vis[maxn];//表示节点[i]的在栈情况
4 void Tarjan(int u) {
5 dfn[u] = low[u] = ++Time;//记录节点u的访问时间,目前节点u不通过树边能到达的时间戳最小的节点就为它本身
6 vis[u] = 1;//标记节点u已入栈
7 stl[++top] = u;//节点u入栈
8 for(int i = head[u];i;i = e[i].next){
9 int v = e[i].to;
10 if(!dfn[v]){//节点v没访问过就访问节点v
11 Tarjan(v);
12 low[u] = min(low[u],low[v]);//回溯时更改u点的时间戳,若u点能通过子孙节点的返祖边减小时间戳则更改时间戳,若不能则不变
13 }
14 else if(vis[v]){//判断是否为返祖边
15 low[u] = min(low[u],dfn[v]);//通过返祖边更改时间戳
16 }
17 }
18 //以下为缩点操作
19 if(dfn[u] == low[u]){
20 cnt++;//新图节点 + 1
21 while(stl[top + 1] != u){//把栈顶到节点u之间的点全部出栈,写成 top + 1 避免漏掉栈顶元素
22 int v = stl[top--];//取出栈顶元素
23 vis[v] = 0;//将节点v标记为已出栈
24 siz[cnt]++;//新图节点大小 + 1
25 }
26 }
27 }
代码中已经解释得很到位了,这里就不再赘述。
接下来是完整代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 const int maxn = 1e5 + 5;
4 struct Edge{
5 int to,next;
6 }e[maxn];
7 int Time,low[maxn],dfn[maxn],n,m,a,b;//Time表示节点u当前的时间戳,dfn数组记录节点u的时间戳,low数组记录节点u不通过树边能够到达的时间戳最小的节点
8 int cnt,top,stl[maxn],siz[maxn],head[maxn],len;//手写stl数组代替栈,top表示栈顶,cnt表示新图节点数,siz[i]表示新图节点i由多少个旧图节点组成
9 bool vis[maxn];//表示节点[i]的在栈情况
10 void Insert(int u,int v){//建立邻接表
11 e[++len].to = v;
12 e[len].next = head[u];
13 head[u] = len;
14 }
15 void Tarjan(int u) {
16 dfn[u] = low[u] = ++Time;//记录节点u的访问时间,目前节点u不通过树边能到达的时间戳最小的节点就为它本身
17 vis[u] = 1;//标记节点u已入栈
18 stl[++top] = u;//节点u入栈
19 for(int i = head[u];i;i = e[i].next){
20 int v = e[i].to;
21 if(!dfn[v]){//节点v没访问过就访问节点v
22 Tarjan(v);
23 low[u] = min(low[u],low[v]);//回溯时更改u点的时间戳,若u点能通过子孙节点的返祖边减小时间戳则更改时间戳,若不能则不变
24 }
25 else if(vis[v]){//判断是否为返祖边
26 low[u] = min(low[u],dfn[v]);//通过返祖边更改时间戳
27 }
28 }
29 //以下为缩点操作
30 if(dfn[u] == low[u]){
31 cnt++;//新图节点 + 1
32 while(stl[top + 1] != u){//把栈顶到节点u之间的点全部出栈,写成 top + 1 避免漏掉栈顶元素
33 int v = stl[top--];//取出栈顶元素
34 vis[v] = 0;//将节点v标记为已出栈
35 siz[cnt]++;//新图节点大小 + 1
36 }
37 }
38 }
39 void Read() {
40 scanf("%d%d",&n,&m);
41 for(int i = 1;i <= m;i++) {
42 scanf("%d%d",&a,&b);
43 Insert(a,b);//若是无向图则还需要Insert(b,a)
44 }
45 for(int i=1;i<=n;i++){
46 if(!dfn[i]){
47 Tarjan(i);
48 }
49 }
50 int ans = 0;
51 for(int i=1;i<=n;i++){
52 if(siz[i] > 1){
53 ans++;
54 }
55 }
56 printf("%d",ans);
57 }
58 int main(){
59 Read();
60 return 0;
61 }
完结撒花
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现