tarjan原理,割点,桥
来源:互联网 发布:政府部门域名申请 编辑:程序博客网 时间:2024/05/22 02:00
tarjan原理
tarjan是图论中常用的算法,用于求图中的强连通分量,割点等
这里先讲强连通分量的做法
强连通分量的定义:
在一个有向图的子图中,任意两个点相互可达,也就是存在互通的路径,那么这个子图就是强连通分量(或者称为强连通分支)。如果一个有向图的任意两个点相互可达,那么这个图就称为强连通图。
tarjan基于dfs。而且每个强连通分量恰好是深搜树的一颗子树。
如果u是某个强连通分量的根,那么:
(1)u不存在路径可以返回到它的祖先
(2)u的子树也不存在路径可以返回到u的祖先。
DFN[i]标记i这个点被访问到的时间
low[i]表示i这个点直接或者间接可以到达的点里面最早被访问到的点的时间(实际上就是同一个强连通分量里的根)
步骤:
从第1个点(u)开始搜索,刚开始要把DFN[u]和low[u]赋值为被访问到的时间, 每次都去遍历这个点关联的边<u,v>:
如果v不在栈里面,继续去递归搜索v,等到回溯以后,就要对low[u]判断,如果u的子树能到达更早的点,那么就把low[u]赋值为low[v[i]]。
如果v已经在栈里面了,此时形成一个环,那么此时对于low[u]的修改就是:
这样很明显,只有强连通分量的根的DFN值和low值是相等的,其他的点都进行过修改,所以如果DFN[i]==low[i],说明我们找到了一个强连通分量的根,要得到这个强连通分量,只要把相应元素出栈就行了。
由于每个点只访问1次,每条边也是1次,所以tarjan算法的时间复杂度是O(n+m)。
代码:
#include<stdio> #include<cstring> const int maxn=10005; int DFN[maxn];//记录每个点被访问到的时间 int low[maxn];//记录点可以直接或间接到达的最早被访问到的点(也就是那个强连通分量的根) int stack[maxn]; int sccnum[maxn];//标记每个点属于第几个强连通分量 bool instack[maxn]; int sccNum;//强连通分量的数目 int top; int index; int n; struct node { int to; int next; }edge[10*maxn]; int head[maxn]; int tot; void addedge(int from,int to) { edge[tot].to=to; edge[tot].next=head[from]; head[from]=tot++; } void tarjan(int i) { DFN[i]=low[i]=++index;//刚刚搜到这个点,DFN和low都赋值为被访问到的时间 stack[top++]=i;//入栈 instack[i]=1; for(int j=head[i];j!=-1;j=edge[j].next) { if(!DFN[edge[j].to])//如果没有被访问过 { tarjan(edge[j].to); //这个时候low可能要修改,值为i或者i的子树可以到达的最早被访问到的点的时间 if(low[i]>low[edge[j].to]) low[i]=low[edge[j].to]; } else if(instack[edge[j].to])//已经在栈 { if(low[i]>DFN[edge[j].to]) low[i]=DFN[edge[j].to]; } } if(DFN[i]==low[i])//找到根 { sccNum++; int v; do { v=stack[--top]; sccnum[v]=sccNum; instack[v]=0;//标记出栈 }while(v!=i); } } void solve() { memset(DFN,0,sizeof(DFN)); memset(instack,0,sizeof(instack)); index=0; sccNum=0; top=0; for(int i=1;i<=n;i++) if(!DFN[i]) tarjan(i); } int main() { int m,a,b; while(~scanf("%d%d",&n,&m)) { if(n==0 && m==0) break; memset(head,-1,sizeof(head)); tot=0; for(int i=0;i<m;i++) { scanf("%d%d",&a,&b); addedge(a,b); } solve(); if(sccNum==1) printf("Yes\n"); else printf("No\n"); } return 0; }
基于强联通分量,还可以扩展出一些其他的算法:
1. 割点:
定义:在一张无向图中,如果去掉某个顶点以及和这个顶点相关联的边,使得整个图的连通分支数增 加,那么这个点就是一个割点.
tarjan求割点:
若一个节点为割点,一定满足以下两个条件之一:
1).u是dfs搜索树的根,并且u含2棵及2棵以上的子树,即不同子树的节点要想联通必须经过u,那么把u删去图中的连通分支一定增加。
2).u不是dfs搜索树的根,并且有不等式 low[v]>=DFN[u],其中(u,v)是树枝边(即v通过u延伸开去),因为v一定不会到达时间比u小的点,否则low[v]一定会小于dfn[u],所以v要达到祖先一定要经过u。
代码:
割点.md
2.桥:
定义:
在一张无向图中,如果去掉边(u,v)使得图的连通分支数增加,那么边(u,v)便称为桥.
类似于割点,只不过是把点变为一条边而已。边(u,v)是无向图的桥当且仅当(u,v)满足 low[v]>DFN[u],另外要对于u,v间有重边进行特判。
代码:
void tarjan(int u,int root,int fa) { DFN[u]=low[u]=++index; instack[u]=1; int cnt=0; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(!instack[v]) { tarjan(v,root,u); cnt++; if(low[u]>low[v]) low[u]=low[v]; if(u==root && cnt>1) cut_point[u]=1; else if(u!=root && low[v]>=DFN[u]) cut_point[u]=1; } else if(v!=fa && low[u] > DFN[v]) low[u]=DFN[v]; }
3.双联通分量:
在无向连通图中,如果删除该图的任何一个结点都不能改变该图的连通性,则该图为双连通的无向图。一个连通的无向图是双连通的,当且仅当它没有关键点。换言之,双连通分量里任何2个顶点之间都至少有2条不相交的路径
求法类似于求强联通分量,只不过是在无向图中。
代码:
void tarjan(int u,int fa) { instack[u]=1; DFN[u]=low[u]=++index; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(fa==edge[i].id) continue; if(!instack[v]) { tarjan(v,edge[i].id); low[u]=min(low[u],low[v]); } else low[u]=min(DFN[v],low[u]); } }
阅读全文
0 0
- tarjan原理,割点,桥
- BZOJ1123BLO Tarjan割点+乘法原理+dfs
- tarjan求桥及割点
- 割点和桥---Tarjan算法
- Tarjan算法求桥和割点
- poj2117 tarjan()+割点
- 【tarjan】【割点】
- tarjan 割点
- BZOJ2730矿场搭建 Tarjan割点 乘法原理
- 模板整理: 图论---tarjan缩点/桥/割点
- 割点判断(tarjan)
- tarjan算法之 割边,割点
- tarjan算法应用 割点 桥 双连通分量
- 割点,桥,双连通分量Tarjan ,入门练习
- 求桥和割点的Tarjan算法
- tarjan算法应用 割点 桥 双连通分量
- 【学习笔记】图论 Tarjan LCA 割点 桥 暑假7.5
- tarjan算法应用 割点 桥 双连通分量
- 【BZOJ3295】【Cqoi2011】动态逆序对
- 拆分数列
- 实战演练 test16 T1
- 【mysql】细说 数据库隔离级别 及实现
- Qt 学习之路 2(29):绘制设备
- tarjan原理,割点,桥
- 关于Visual Studio 2010与64位系统的问题
- [SCOI2011]糖果
- Java反射初探
- CentOS 7.2 下安装部署 LAMP 详解
- Qt 学习之路 2(30):Graphics View Framework
- [HAOI2006]受欢迎的牛
- NOI导刊2010提高(06)
- 分页