tarjan算法是在dfs生成一颗dfs树的时候按照访问顺序的先后,为每个结点分配一个时间戳,然后再用low[u]表示结点能访问到的最小时间戳

以上的各种应用都是在此拓展而来的。

割点:如果一个图去掉某个点,使得图的连通分支数增加,那么这个点就是割点

某个点是割点,当且仅当这个点的后代没有连回自己祖先的边。即low[v] >= dfn[u]     , v是u的后代

需要注意的是根结点的特判,因为根结点没有祖先,根结点是割点,当且仅当根结点有两个以上的儿子。

问题:重边对该算法有影响吗?没有影响。

   需要注意的地方? 图至少有三个点以上, 否则需要注意一下。

  

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
vector<int> g[N];
int dfs_clock,dfn[N],low[N];
bool isCut[N];
void init(int n)
{
dfs_clock = ;
for(int i=; i<=n; ++i)
dfn[i] = low[i] = isCut[i] = ;
}
//重边对割点没有影响,且该算法对有向图同样适用
void tarjan(int u, int fa)
{
dfn[u] = low[u] = ++dfs_clock;
int child = ;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa) continue;//如果是树枝边的反向访问,则不能用来更新low[u]
child++;
if(dfn[v]==)
tarjan(v,u);
low[u] = min(low[u],low[v]);//用树枝边,或者后向边来跟新low[u]
if(low[v] >= dfn[u])
isCut[u] = true;
}
if(fa==- && child>=) isCut[u] = true;
}
int main()
{
int n,m,i,u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init(n);
for(i=; i<m; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
tarjan(,-);
for(i=; i<=n; ++i)
if(isCut[i])
printf("%d ",i);
puts("");
}
return ;
}

割边:如果一个图去掉某条边,使得图的连通分支数增加,那么这条边就是割边(桥)

某条边是割边,当且仅当某个点的后代没有连回自己或者自己祖先的边,即low[v] > dfn[u],  那么边(u,v)是割边

问题:重边对该算法有影响吗? 有影响。 所以要判断是不是有重边

  

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
vector<int> g[N];
int dfs_clock,dfn[N],low[N],cntCut;
void init(int n)
{
cntCut = dfs_clock = ;
for(int i=; i<=n; ++i)
{
dfn[i] = low[i] = ;
g[i].clear();
}
}
//重边对割边有影响,比如有2--3 ,2--3两条边,那么边2--3就不是割边,因为去掉还是连通的,所以要判断一下
void tarjan(int u, int fa)
{
dfn[u] = low[u] = ++dfs_clock;
bool flag = false;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa && !flag)//在这里判断有没有重边
{
flag = true;
continue;
}
if(dfn[v]==)
tarjan(v,u);
low[u] = min(low[u],low[v]);
if(low[v] > dfn[u])//这里统计的是割边的数量,如果要记录割边,那么就标记边,或者把边入栈
cntCut++;
}
}
int main()
{
int n,m,i,u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init(n);
for(i=; i<m; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
tarjan(,-);
printf("%d\n",cntCut);
}
return ;
}

点-双连通分量:如果任意两点存在两条点不重复的路径,那么就说这个图是点-双连通的。点-双连通的极大子图称为双连通分量

双连通分量之间的分界点是割点。而且双连通分量不可能分布在树根结点两端。所以我们将边入栈,当遇到割点时,就将边出栈,直到有边等于当前边。就跳出

问题:重边对该算法有影响吗? 没有影响

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
struct Edge
{
int u,v;
Edge(){};
Edge(int u, int v)
{
this->u = u;
this->v = v;
}
};
vector<int> g[N],bcc[N];
int bccno[N],dfn[N],low[N],dfs_clock,cnt;
stack<Edge> st;
void init(int n)
{
cnt = dfs_clock = ;
for(int i=; i<=n; ++i)
{
bccno[i] = low[i] = dfn[i] = ;
bcc[i].clear();
g[i].clear();
}
}
void tarjan(int u, int fa)
{
dfn[u] = low[u] = ++dfs_clock;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(dfn[v]==)
{
st.push(Edge(u,v));
tarjan(v,u);
low[u] = min(low[u],low[v]);
if(low[v] >= dfn[u])//如果这个点是割点,那么先前入栈的一些边是属于一个双连通分量的
{
Edge x;
cnt++;
for(;;)
{
x = st.top();
st.pop();
if(bccno[x.u] != cnt)
{
bccno[x.u] = cnt;
bcc[cnt].push_back(x.u);
}
if(bccno[x.v] != cnt)
{
bccno[x.v] = cnt;
bcc[cnt].push_back(x.v);
}
if(x.u==u && x.v==v)
break;
}
}
}
else if(v!=fa && dfn[v] < dfn[u])
{
st.push(Edge(u,v));
low[u] = min(low[u],dfn[v]);
} }
}
int main()
{
int n,m,i,u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init(n);
for(i=; i<m; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
tarjan(,-);
for(i=; i<=cnt; ++i)
{
printf("bcc %d has vertex:",i);
while(!bcc[i].empty())
{
printf("%d ",bcc[i].back());
bcc[i].pop_back();
}
puts("");
}
}
return ;
}

边-双连通分量:如果任意两点存在两条边不重复的路径,那么就说这个图是边-双连通的,边-双连通的极大子图成为边双连通分量。

边双连通分量的分界点是割边。双连通分量可以分布在根结点的两端。所以不能在for(int i=0; i<g[u].size(); ++i) 这个循环里面判断割边,而要在外面递归返回时判断

问题:重边对该算法有影响吗?有影响,就好像影响割边算法一样

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
vector<int> g[N];
stack<int> st;
int dfn[N],low[N],dfs_clock,bccno[N],cnt;
void init(int n)
{
cnt = dfs_clock = ;
for(int i=; i<n; ++i)
{
dfn[i] = low[i] = ;
bccno[i] = ;
g[i].clear();
}
} void tarjan(int u, int fa)//边双连通分量可能分布在某子树树根的两个分支上
{
low[u] = dfn[u] = ++dfs_clock;
st.push(u);
bool flag = ;
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(v==fa && !flag) //重边对边连通分量的判断有影响,所以要判断一下
{
flag = true;
continue;
}
if(dfn[v]==)
tarjan(v,u);
low[u] = min(low[u],low[v]);
}
if(dfn[u]==low[u])//说明这个点的所有后代都没有连回自己祖先的边,
{
cnt++;
for(;;)
{
int x = st.top();
st.pop();
bccno[x] = cnt;
if(x==u)
break;
}
}
}
int main()
{
int n,m,i,u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init(n);
for(i=; i<m; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
tarjan(,-);
for(i=; i<=n; ++i)
printf("vexter %d belong to bcc %d\n",i,bccno[i]);
}
return ;
}

问添加最少多少条边,使得整个图边-双连通, 首先求出边-双连通分量,然后缩点,最后变成一棵树,那么要加的边 就是(叶子结点+1)/2

强连通分量:如果有向图的任意两点可以,那么就说这个图是强连通的,一个图的极大子图是强连通的,那么就说这个子图是强连通分量

对于一个scc,我们要判断哪个点是该scc最先被发现的点,然后将后来发现的点出栈,知道遇到这个点。 那么出栈的点都属于一个强连通分量

问题:重边对该算法有影响吗?没有影响

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <math.h>
using namespace std;
typedef long long LL;
const int INF = <<;
const int N = + ;
vector<int> g[N];
stack<int> st;
int cnt,dfs_clock,dfn[N],low[N],sccno[N];
void init(int n)
{
cnt = dfs_clock = ;
for(int i=; i<=n; ++i)
{
dfn[i] = low[i] = sccno[i] = ;
g[i].clear();
}
} void tarjan(int u, int fa)
{
dfn[u] = low[u] = ++dfs_clock;
st.push(u);
for(int i=; i<g[u].size(); ++i)
{
int v = g[u][i];
if(dfn[v]==)
{
tarjan(v,u);
low[u] = min(low[u],low[v]);
}
else if(sccno[v]==)//因为有向图存在横插边,不能用横插边来更新low[u]
{
low[u] = min(low[u],low[v]);
}
}
//同样,因为强连通分量可以分布在根结点的两个分支上,所以在递归返回的时候调用
if(low[u] == dfn[u])
{
cnt++;
for(;;)
{
int x = st.top();
st.pop();
sccno[x] = cnt;
if(x==u)
break;
}
}
}
int main()
{
int n,m,i,u,v;
while(scanf("%d%d",&n,&m)!=EOF)
{
init(n);
for(i=; i<m; ++i)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
tarjan(,-);
for(i=; i<=n; ++i)
printf("vertex %d belong to scc %d\n",i,sccno[i]);
}
return ;
}

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

  1. Tarjan算法求解桥和边双连通分量(附POJ 3352 Road Construction解题报告)

     http://blog.csdn.net/geniusluzh/article/details/6619575 在说Tarjan算法解决桥和边双连通分量问题之前我们先来回顾一下Tarjan算法是如何 ...

  2. ZOJ Problem - 2588 Burning Bridges tarjan算法求割边

    题意:求无向图的割边. 思路:tarjan算法求割边,访问到一个点,如果这个点的low值比它的dfn值大,它就是割边,直接ans++(之所以可以直接ans++,是因为他与割点不同,每条边只访问了一遍) ...

  3. tarjan求割点割边的思考

    这个文章的思路是按照这里来的.这里讨论的都是无向图.应该有向图也差不多. 1.如何求割点 首先来看求割点.割点必须满足去掉其以后,图被分割.tarjan算法考虑了两个: 根节点如果有两颗及以上子树,它 ...

  4. UVA 796 Critical Links (tarjan算法求割边)

    这是在kuangbin的题目里看到的,不得不吐槽一下,题目中居然没给出数据范围,还是我自己猜的-本来是一道挺裸的题,但是我wa了好多次,原因就是这里面有两个坑点,1重边特判,2输出时左边必须比右边小. ...

  5. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  6. Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】

    一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...

  7. (转)Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)

    基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ...

  8. tarjan算法(强连通分量 + 强连通分量缩点 + 桥(割边) + 割点 + LCA)

    这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量, ...

  9. 连通分量模板:tarjan: 求割点 &amp;&amp; 桥 &amp;&amp; 缩点 &amp;&amp; 强连通分量 &amp;&amp; 双连通分量 &amp;&amp; LCA(近期公共祖先)

    PS:摘自一不知名的来自大神. 1.割点:若删掉某点后.原连通图分裂为多个子图.则称该点为割点. 2.割点集合:在一个无向连通图中,假设有一个顶点集合,删除这个顶点集合,以及这个集合中全部顶点相关联的 ...

  10. 有向图tarjan算法求连通分量的粗浅讲解、证明, // hdu1269

    打算开始重新复习一遍相关算法.对于有向图tarjan算法,通过学习过很多说法,结合自己的理解,下面给出算法自己的观点. 算法总模型是一个dfs,结合一个stack(存放当前尚未形成SCC的点集合),记 ...

随机推荐

  1. android onKeydown

    package wyf.ytl; import android.app.Activity; import android.content.Context; import android.os.Bund ...

  2. 【Demo 0010】事件响应链

    本章学习要点:       1.  熟悉iOS事件分发过程以及事件响应链;       2.  掌握基本事件响应方法(单击,双击):       3.  掌握基本手势处理方法:

  3. Main Memory Object-Relational Database Management System

    Main Memory Object-Relational Database Management System FastDBMain Memory Relational Database Manag ...

  4. oracle实现远程连接超简单;枚举与剪枝();PowerDesigner生成数据库代码注意里面的双引號,应该去掉

    点击開始,查看netManager,点击面板的监听程序,默认仅仅有地址1且标记着localhost.新建一个地址,输入本机IP,又一次开启监听程序就可以 △△△ * △△ = △△△△ 某3位数乘以2 ...

  5. JAVA的反射机制学习笔记(二)

    上次写JAVA的反射机制学习笔记(一)的时候,还是7月22号,这些天就瞎忙活了.自己的步伐全然被打乱了~不能继续被动下去.得又一次找到自己的节奏. 4.获取类的Constructor 通过反射机制得到 ...

  6. [UVALive 6663 Count the Regions] (dfs + 离散化)

    链接:https://icpcarchive.ecs.baylor.edu/index.php? option=com_onlinejudge&Itemid=8&page=show_p ...

  7. minidump详细介绍

    Effective minidump 简介 在过去几年里,崩溃转储(crash dump)成为了调试工作的一个重要部分.如果软件在客户现场或者测试实验室发生故障,最有价值的解决方式是能够创建一个故障瞬 ...

  8. codeforces 597B Restaurant

    题目链接:http://codeforces.com/contest/597/problem/B 题目分类:贪心 题目分析:经典的看节目问题(挑战程序设计page 40) 代码: #include&l ...

  9. c++实用技巧

    原地址:http://www.cnblogs.com/easymind223/articles/2576904.html 晚上的时间总是习惯性的在cnblogs逛街,今天又看到了好文章,其c++味道浓 ...

  10. Eclipse Tips

    一.取消拼写检查 Window -> Preferences -> General -> Editors -> Text Editors -> Spelling -> ...