tarjan算法(割点/割边/点连通分量/边连通分量/强连通分量)
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算法(割点/割边/点连通分量/边连通分量/强连通分量)的更多相关文章
- Tarjan算法求解桥和边双连通分量(附POJ 3352 Road Construction解题报告)
http://blog.csdn.net/geniusluzh/article/details/6619575 在说Tarjan算法解决桥和边双连通分量问题之前我们先来回顾一下Tarjan算法是如何 ...
- ZOJ Problem - 2588 Burning Bridges tarjan算法求割边
题意:求无向图的割边. 思路:tarjan算法求割边,访问到一个点,如果这个点的low值比它的dfn值大,它就是割边,直接ans++(之所以可以直接ans++,是因为他与割点不同,每条边只访问了一遍) ...
- tarjan求割点割边的思考
这个文章的思路是按照这里来的.这里讨论的都是无向图.应该有向图也差不多. 1.如何求割点 首先来看求割点.割点必须满足去掉其以后,图被分割.tarjan算法考虑了两个: 根节点如果有两颗及以上子树,它 ...
- UVA 796 Critical Links (tarjan算法求割边)
这是在kuangbin的题目里看到的,不得不吐槽一下,题目中居然没给出数据范围,还是我自己猜的-本来是一道挺裸的题,但是我wa了好多次,原因就是这里面有两个坑点,1重边特判,2输出时左边必须比右边小. ...
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...
- Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】
一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...
- (转)Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)
基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ...
- tarjan算法(强连通分量 + 强连通分量缩点 + 桥(割边) + 割点 + LCA)
这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量, ...
- 连通分量模板:tarjan: 求割点 && 桥 && 缩点 && 强连通分量 && 双连通分量 && LCA(近期公共祖先)
PS:摘自一不知名的来自大神. 1.割点:若删掉某点后.原连通图分裂为多个子图.则称该点为割点. 2.割点集合:在一个无向连通图中,假设有一个顶点集合,删除这个顶点集合,以及这个集合中全部顶点相关联的 ...
- 有向图tarjan算法求连通分量的粗浅讲解、证明, // hdu1269
打算开始重新复习一遍相关算法.对于有向图tarjan算法,通过学习过很多说法,结合自己的理解,下面给出算法自己的观点. 算法总模型是一个dfs,结合一个stack(存放当前尚未形成SCC的点集合),记 ...
随机推荐
- 【虚拟化实战】容灾设计之三Stretched Cluster
作者:范军 (Frank Fan) 新浪微博:@frankfan7 Stretched Cluster是一把双刃剑,会用的如行云流水,用不好反而受其限制. 传统的vSphere Cluster是指一个 ...
- JQuery选择器操作
!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"><head runat="se ...
- dumpbin
在使用VC时,可以用DUMPBIN.EXE来得到某个DLL中所输出的符号的清单.如下面的 命令:dumpbin -exports Cmpnt1.dll如: C:/WINDOWS/system32> ...
- Delphi调用C++导出的QT类
打开VS2008创建一个dll项目(创建了一个QT Library项目),新建头文件q4dapplication.h定义纯虚类: #ifndef Q4DAPPLICATION#define Q4DAP ...
- Spring配置DataSource数据源
在Spring框架中有例如以下3种获得DataSource对象的方法: 1.从JNDI获得DataSource. 2.从第三方的连接池获得DataSource. 3.使用DriverManagerDa ...
- 数字证书, 数字签名, SSL(TLS) , SASL .
因为项目中要用到TLS + SASL 来做安全认证层. 所以看了一些网上的资料, 这里做一个总结. 1. 首先推荐几个文章: 数字证书: http://www.cnblogs.com/hyddd/ar ...
- [Word使用笔记]分类简介
什么Vistual Studio , Eclipse , Xcode , 都弱爆了,Word比他们难多了 - -! 此分类用于记录Word的一些使用
- MySql 链接url 参数详解
最近 整理了一下网上关于MySql 链接url 参数的设置,有不正确的地方希望大家多多指教: mysql JDBC URL格式如下: jdbc:mysql://[host:port],[host:po ...
- swfupload组件后台获取中文文件名称乱码的问题解决
问题描写叙述:用swfupload上传文件,含有中文名称的文件上传会报错,我用的是获取FileItem对象,用FileItem对象的getName()方法获取文件名会乱码,试着用request. ...
- SQL SERVER CHARINDEX函数
CHARINDEX函数经常常使用来在一段字符中搜索字符或者字符串.假设被搜索的字符中包括有要搜索的字符,那么这个函数返回一个非零的整数,这个整数是要搜索的字符在被搜索的字符中的開始位数.即CHARIN ...