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. 贴一个CMemDC 代码,这东西真不错噢,短小精悍,可谓极品

    罗索客 发布于 2006-11-28 21:53 点击:3941次  来自: 原文: http://yuantao82.spaces.live.com/Blog/cns!8FC0A772D812A22 ...

  2. jquery ajax验证用户名是否存在(后台spring mvc)

    controller层 @ResponseBody @RequestMapping(value = "/user/isExist", produces = "applic ...

  3. Android本地视频播放器开发--视频解码

    在上一章Android本地视频播放器开发--SDL编译编译中编译出sdl的支持库,当时我们使用的2.0,但是有些api被更改了,所以在以下的使用者中我们使用SDL1.3的库,这个库我会传上源码以及编译 ...

  4. 可运行jar包调用exe可运行文件,子进程阻塞

    背景: 须要在项目的測试工具中加入一个button,点击后直接打开某exe工具. 这个工具的功能是导入txt文件,转为excel报表输出. 无奈解析了两行之后就停止不动了,也不报错.关闭測试工具后,就 ...

  5. Hangfire Highlighter Tutorial

    Hangfire Highlighter Tutorial Hangfire是一个开源且商业免费使用的工具函数库.可以让你非常容易地在ASP.NET应用(也可以不在ASP.NET应用)中执行多种类型的 ...

  6. 使用svnkit 的相关实例及相关问题汇总

    SVNKIT操作SVN版本库的完整例子 http://www.cnblogs.com/wangjiyuan/p/svnkitwanchenglizi.html#!comments 2.SVNClien ...

  7. Gradle学习之使用java plugin

    请通过下面方式下载本系列文章的Github演示样例代码:   git clone https://github.com/davenkin/gradle-learning.git     Gradle最 ...

  8. C Coding Standard

    1 共同 Rule 1 编译的Warnings不能被忽略掉 Rule 2 在已有Code或者三方的code基础上的改动,同意使用原来的coding standard Rule 3 假设同意C和C++都 ...

  9. Linux SSH常用总结(转)

    一.连接到远程主机 格式: ssh name@remoteserver 例如: ssh ickes@192.168.27.211 二.连接到远程主机指定的端口 格式: ssh name@remotes ...

  10. ACdream 1148(莫比乌斯反演+分块)

    传送门:GCD SUM 题意:给出N,M执行如下程序:long long  ans = 0,ansx = 0,ansy = 0;for(int i = 1; i <= N; i ++)   fo ...