这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可。

tarjan算法的功能很强大, 可以用来求解强连通分量,缩点,桥,割点,LCA等,日后写到相应的模板题我就会放上来。

1.强连通分量(分量中是任意两点间都可以互相到达)

  1. 按照深度优先遍历的方式遍历这张图。

  2. 遍历当前节点所出的所有边。在遍历过程中:

    ( 1 ) 如果当前边的终点还没有访问过,访问。

    回溯回来之后比较当前节点的low值和终点的low值。将较小的变为当前节点的low值。(因为遍历到终点时有可能触发了2)

    ( 2 ) 如果已经访问过,那我们一定走到了一个之前已经走过的点(终点的时间戳一定比当前的小)

    则比较当前节点的low值和终点的dfn值。将较小的变为当前节点的low值

  3. 在回溯过程中,对于任意节点u用其出边的终点v的low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路径,所以v能够到的节点u也一定能够到。

  4. 当一个节点的dfn值和low值相等时,这个节点是一个强联通分量的“根”。压栈,输出。

例题:http://acm.hdu.edu.cn/showproblem.php?pid=1269

#include<stdio.h>
#include<stack>
#include<algorithm>
#include<string.h>
using namespace std; int n, m, cnt, deep, kinds_color;
int head[10000 + 10];
int dfn[10000 + 10], low[10000 + 10], vis[10000 + 10];
stack<int>S; struct Edge
{
int to, next;
}edge[100000 + 10]; void add(int u, int v)
{
edge[++ cnt].to = v;
//edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
} void tarjan(int now)
{
dfn[now] = low[now] = ++deep;
S.push(now);
vis[now] = 1;
for(int i = head[now]; i != 0; i = edge[i].next)
{
int to = edge[i].to;
if(!dfn[to])
{
tarjan(to);
low[now] = min(low[now], low[to]);
}
else if(vis[to])
low[now] = min(low[now], dfn[to]);
}
if(dfn[now] == low[now])
{
kinds_color ++;
while(1)
{
int temp = S.top();
S.pop();
if(temp == now)
break;
}
}
} int main()
{
int a, b;
while(scanf("%d%d", &n, &m)!=EOF)
{
if(n == 0 && m == 0)
break;
cnt = deep = kinds_color = 0;
memset(head, 0, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(vis, 0, sizeof(vis));
memset(low, 0, sizeof(low));
for(int i = 1; i <= m; i ++)
{
scanf("%d%d", &a, &b);
add(a, b);
}
for(int i = 1; i <= n; i ++)
if(!dfn[i])
tarjan(i);
if(kinds_color == 1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}

  

2.强连通分量缩点(多了步化简图的操作)

主要步骤跟上面求分量是一模一样的,区别在于需要在栈出的过程中,记录每个点所处的哪个分量

if(dfn[now] == low[now])
{
k_color ++; //分块
while(1)
{
int temp = S.top();
S.pop();
color[temp] = k_color; //记录每个点所属的分量块
vis[temp] = 0;
if(temp == now)
break;
}
}

  

for(int i = 1; i <= n; i ++)//遍历原图
{
for(int j = head[i]; j != -1; j = edge[j].next)
{
int to = edge[j].to;
int x = color[i], y = color[to];//x, y为强连通分量的编号
if(x != y)//如果起点终点属于不同的连通分量,就可以建为新图的边了,点为连通分量编号
{
add1(x, y);
// in[y] ++;这是拓扑排序的入度 无视掉
}
}
}

  

3.tarjan求割点

例题:https://www.luogu.org/problemnew/show/P3388

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std; int n, m, ans;
int cnt, head[20010];
int dfn[20010], low[20010], deep;
int flag[20010]; struct Edge
{
int to, next;
}edge[100010 * 2]; void add(int a, int b)
{
edge[++ cnt].to = b;
edge[cnt].next = head[a];
head[a] = cnt;
} void tarjan(int now, int root) //求割点是不需要栈结构的
{
dfn[now] = low[now] = ++deep;
int child = 0;//根节点的特判
for(int i = head[now]; i != -1; i = edge[i].next)
{
int to = edge[i].to;
if(!dfn[to])
{
tarjan(to, root);
low[now] = min(low[now], low[to]);
if(low[to] >= dfn[now] && now != root)//表示该节点绕不回上面 ,那么上面的点是割点,因为去割掉之后下面的点就与上面的点分离了
{
flag[now] = 1;
}
if(now == root)//求根节点的子树数量
child ++;
}
low[now] = min(low[now], dfn[to]);//注意是dfn
}
if(child >= 2 && now == root) //如果根节点的子树数量大于等于2 ,将根节点去掉之后两颗子树就分离了
{
flag[now] = 1;
}
} int main()
{
scanf("%d%d", &n, &m);
cnt = deep = ans = 0;
mem(head, -1);
mem(dfn, 0);
mem(low, 0);
mem(flag, 0);
for(int i = 1; i <= m; i ++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
for(int i = 1; i <= n; i ++)
if(!dfn[i])
tarjan(i, i);
for(int i = 1; i <= n; i ++)
if(flag[i])
ans ++;
printf("%d\n", ans);
for(int i = 1; i <= n; i ++)
if(flag[i])
printf("%d ", i);
return 0;
}

 4.求无向图的割边/桥

割边:在一个无向图中,去掉一条边(u, v),可以使图的连通分量增多的边, 就是割边,也称做桥

原理:利用tarjan算法, 对于一条边的起点u,终点v,如果满足条件 low[v] > dfn[u], 那么(u, v)就是一条割边, 因为这意味着不存在其他的边使得v可以回到u, 那么割掉就使图分离了.

需要注意的是跟割点不同, 没有等于号, 否则说明存在其他边回到起点, 那么就不是割边。

还有一点与割点不同, tarjan(int now, int pre),这里第二个变量记录的不是遍历起点的根节点, 而是记录now的父亲节点pre, 这样的话可以通过if(to != pre)保证不往回指,因为这是个无向图, 前向星会存回边.

例题:https://www.luogu.org/problemnew/show/P1656

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std; int n, m, sum;
int head[200], cnt;//链式前向星数组
int dfn[200], low[200], deep;//tarjan数组 struct Edge
{
int from, to, next;
}edge[5000 * 2]; void add(int a, int b)
{
edge[++ cnt].to = b;
edge[cnt].from = a;
edge[cnt].next = head[a];
head[a] = cnt;
} struct ANS
{
int from, to;
}ans[5000 * 2]; bool cmp(ANS a, ANS b)
{
if(a.from != b.from)
return a.from < b.from;
else
return a.to < b.to;
} void tarjan(int now, int pre)
{
dfn[now] = low[now] = ++ deep;
for(int i = head[now]; i != -1; i = edge[i].next)
{
int to = edge[i].to;
if(!dfn[to])
{
tarjan(to, now);//pre指向now
low[now] = min(low[now], low[to]);
if(low[to] > dfn[now])
{
ans[++ sum].from = edge[i].from;
ans[sum].to = to;
}
}
else if(to != pre)
low[now] = min(low[now], dfn[to]);
}
} int main()
{
scanf("%d%d", &n, &m);
cnt = deep = sum = 0;
mem(head, -1), mem(dfn, 0), mem(low, 0);
for(int i = 1; i <= m; i ++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
for(int i = 1; i <= n; i ++)
if(!dfn[i])
tarjan(i, -1);
sort(ans + 1, ans + 1 + sum, cmp);
for(int i = 1; i <= sum; i ++)
printf("%d %d\n", ans[i].from, ans[i].to);
}

  

tarjan算法(强连通分量 + 强连通分量缩点 + 桥(割边) + 割点 + LCA)的更多相关文章

  1. Tarjan算法求有向图强连通分量并缩点

    // Tarjan算法求有向图强连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> #inc ...

  2. 『Tarjan算法 有向图的强连通分量』

    有向图的强连通分量 定义:在有向图\(G\)中,如果两个顶点\(v_i,v_j\)间\((v_i>v_j)\)有一条从\(v_i\)到\(v_j\)的有向路径,同时还有一条从\(v_j\)到\( ...

  3. 【原创】tarjan算法初步(强连通子图缩点)

    [原创]tarjan算法初步(强连通子图缩点) tarjan算法的思路不是一般的绕!!(不过既然是求强连通子图这样的回路也就可以稍微原谅了..) 但是研究tarjan之前总得知道强连通分量是什么吧.. ...

  4. Tarjan算法求出强连通分量(包含若干个节点)

    [功能] Tarjan算法的用途之一是,求一个有向图G=(V,E)里极大强连通分量.强连通分量是指有向图G里顶点间能互相到达的子图.而如果一个强连通分量已经没有被其它强通分量完全包含的话,那么这个强连 ...

  5. tarjan算法(求强连通子块,缩点)

    tarjan算法求图中的强连通子图的个数. #include<iostream> #include<stack> #include<queue> #include& ...

  6. Tarjan 算法求强联通分量

    转载自:http://blog.csdn.net/xinghongduo/article/details/6195337 还是没懂Tarjan算法的原理.但是感觉.讲的很有道理. 说到以Tarjan命 ...

  7. Tarjan算法 (强联通分量 割点 割边)

    变量解释: low 指当前节点在同一强连通分量(或环)能回溯到的dfn最小的节点 dfn 指当前节点是第几个被搜到的节点(时间戳) sta 栈 vis 是否在栈中 ans 指强连通分量的数量 top ...

  8. 强连通分量的Tarjan算法

    资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tar ...

  9. Tarjan算法初探 (1):Tarjan如何求有向图的强连通分量

    在此大概讲一下初学Tarjan算法的领悟( QwQ) Tarjan算法 是图论的非常经典的算法 可以用来寻找有向图中的强连通分量 与此同时也可以通过寻找图中的强连通分量来进行缩点 首先给出强连通分量的 ...

随机推荐

  1. POI导出数据以Excel的方式录入,下载

    简单描述:把数据导出到excel文件中.过程:获取要导出的数据列表(list),创建excel文件,数据放入. 代码: //html代码 <div class="btn-group&q ...

  2. 利用CSS3实现鼠标悬停在图片上图片缓慢缩放的两种方法

    1.改变background-size属性 将图片作为某个html元素的背景图片,用transition属性改变图片的大小. .container{ background-size: 100% 100 ...

  3. Jmeter性能测试之关联(三)

    介绍下性能测试很重要的一个知识点---关联, 很多时候程序会在上一个请求随机生成一串字符串, 作为下一个请求的入参验证点, 其实就是动态的入参, 这个时候就需要用到关联, 常用的关联技术就是正则表达式 ...

  4. [慢更]Sublime Text 快捷键及使用过的插件

    整理自己常用的sublime text命令和插件 1.pretty json Json 快速格式化,免去url访问json站点的烦恼. 摘自:https://segmentfault.com/a/11 ...

  5. POJ 1515 Street Directions (边双连通)

    <题目链接> 题目大意: 有m条无向边,现在把一些边改成有向边,使得所有的点还可以互相到达.输出改变后的图的所有边(无向边当成双向的有向边输出). 解题分析: 因为修改边后,所有点仍然需要 ...

  6. Codeforces 375B Maximum Submatrix 2 (DP)

    <题目链接> 题目大意:给出一个01矩阵,行与行之间可以互换位置,问能够得到最大的全1矩阵的面积. #include <bits/stdc++.h> using namespa ...

  7. [PA2014]Parking

    [PA2014]Parking 题目大意: 停车场是一个宽度为\(w(w\le10^9)\)的矩形.我们以其左下角顶点为原点,坐标轴平行于矩形的边,建立直角坐标系.停车场很长,我们可以认为它一直向右边 ...

  8. C语言面对对象设计模式汇编

    面向对象发展到今天,已经出现了许许多多优秀的实践.方法和技术.很多的技术都能够有效的提高软件质量.IBM上的<面向对象软件开发和过程>系列文章对面对对象设计从如下层面进行了详细的介绍:代码 ...

  9. jade模版js中接收express的res.render

    router: router.get('/', function(req, res, next) { res.render('index', { title:{name:'aaa',age:23} } ...

  10. SQLyog 最新版本12.5-64bit 完美破解,亲测可用!

    声明:本文只是提供一个网络上找到的针对12.5版本的注册码使用方式做一个说明,不建议企业用户破解,有条件的还是希望大家购买原版.当然个人学习用的但又不想购买原版的,这里只是提供个途径,请勿用做商业用途 ...