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 }
复制代码

 

完结撒花

posted @   ほしのかえで  阅读(251)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示