Tarjan 算法介绍及用法

Tarjan 算法介绍及用法Tarjan 简介 这是一个有关图联通的算法 它基于 dfs 在解决有环的有向图或无向图的问题时 很多算法不好是操作 那么就先要将环进行缩点 将其转换为 DAG 有向无环图 或一棵树 然后问题应会迎刃而解常规操作 首先补几个概念 强连通 在一个 DAG 中 有 a b 两点 若 a 可以到达 b 且 b 可以到达 a 则 a b 即为强连通 强连通图 若在一个 DAG 中 任意两

Tarjan

简介:

  • 这是一个有关图联通的算法,它基于dfs 在解决有环的有向图或无向图的问题时,很多算法不好是操作…
  • 那么就先要将进行缩点,将其转换为DAG(有向无环图)或一棵,然后问题应会迎刃而解

常规操作:

具体用法:

常规的用法:缩点,割点,割边,点双,边双

缩点:
概念:化环为点
原理:通过栈来将环内存下,并在第二次访问到该环的根节点时,依次弹出,进行编号
模板(code):


1.在有向图中:

void tarjan(int x){ dfn[x]=low[x]=++tim; stk[++top]=x; vis[x]=1; SREP(i,0,E[x].size()){ int y=E[x][i]; if(!dfn[y]){ tarjan(y); chkmin(low[x],low[y]); } else if(vis[y]) chkmin(low[x],dfn[y]); } if(dfn[x]==low[x]){ tot++; do{ Id[stk[top]]=tot; vis[stk[top]]=0; }while(x!=stk[top--]); } }

2.在无向图中:

void tarjan(int x,int f){ dfn[x]=low[x]=++tim; stk[++top]=x; vis[x]=1; bool flag=1; SREP(i,0,E[x].size()){ int y=E[x][i]; if(y==f && flag){flag=0;continue;} if(!dfn[y]){ tarjan(y,x); chkmin(low[x],low[y]); } else if(vis[y]) chkmin(low[x],dfn[y]); } if(dfn[x]==low[x]){ tot++; do{ Id[stk[top]]=tot; vis[stk[top]]=0; }while(x!=stk[top--]); } }

然而一般题目多是无向图,接下来都用无向图了…

割点:
概念:若删掉某点后,原连通图分裂为多个子图,则称该点为割点。
原理:若low[y]>=dfn[x],则x为割点。因为low[y]>=dfn[x],则说明y通过子孙无法到达x的祖先。那么对于原图,去掉x后,必然会分成两个子图。

模板(code):

void tarjan(int x,int f){ dfn[x]=low[x]=++tim; stk[++top]=x; bool flag=1; int child=0; SREP(i,0,E[x].size()){ int y=E[x][i]; if(y==f && flag){flag=0;continue;} if(!dfn[y]){ child++; tarjan(y,x); chkmin(low[x],low[y]); if(low[y]>=dfn[x]){ cut[x]=1; //该点为割点  } } else chkmin(low[x],dfn[y]); } if(!f && child==1)cut[x]=0;//注意初始点,特判  }

割边(桥):
概念:删掉该边后,原图必然会分裂为多个子图
原理:若low[y]>dfn[x],则边(x,y)为桥。由割点同理可得。但是由于可能存在重边,需要把一条无向边拆成的两条标号相同的有向边,记录每个点的父亲到它的边的标号,如果边(x,y)是x的后向边,就不能用dfn[u]更新low[v]。这样如果遍历完v的所有子节点后,发现low[y]=dfn[y],说明x的后向边(x,y)为割边。
模板(code):


void tarjan(int x,int f){ dfn[x]=low[x]=++tim; stk[++top]=x; bool flag=1; SREP(i,0,E[x].size()){ int y=E[x][i]; if(y==f && flag){flag=0;continue;} if(!dfn[y]){ child++; tarjan(y,x); chkmin(low[x],low[y]); if(low[y]>dfn[x]){ mark[x][y]=mark[y][x]=1; //该边为割边  } } else chkmin(low[x],dfn[y]); } }

点双(点双连通分量):
概念:在求出所有的割点以后,把所有的割点删除,原图变成了多个连通块。
原理:在求割点的过程中就能把每个点双连通分支求出。通过栈存储双连通分支。在搜索图时,每找到一条后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足dfn[x]<=low(y),说明x是一个割点,同时把边从栈顶一个个取出,直到遇到了边(x,y),取出的这些边与其关联的点,从而组成一个点双,一般再用个容器存一下每个点双。
模板(code):


void tarjan(int x,int f){ dfn[x]=low[x]=++tim; stk[++top]=x; vis[x]=1; bool flag=1; int child=0; SREP(i,0,E[x].size()){ int y=E[x][i]; if(y==f && flag){flag=0;continue;} if(!dfn[y]){ child++; tarjan(y,x); chkmin(low[x],low[y]); if(low[y]>=dfn[x]){ cut[x]=1; V[++tot].clear(); do{ vis[stk[top]]=0; Id[stk[top]]=tot; V[tot].pb(stk[top]); }while(y!=stk[top--]); V[tot].pb(x); solve();//解决该点双上的问题  } } else if(vis[y])chkmin(low[x],dfn[y]); } if(!f && child==1)cut[x]=0; }

边双(边双连通分量):
概念:在求出所有的割边以后,把所有的割边删除,原图变成多个连通块。不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
原理:与点双同理,在求割边的过程中就能把每条边双连通分支求出,也是通过栈,不过此时自然是存边了,而不是点,之后栈的弹出边界与点双也是同理的,这里就不再赘述。
模板(code):


void tarjan(int x,int fa){ dfn[x]=low[x]=++tim; LREP(x){ int y=E[i].y; if(E[i].flag||dfn[y]>=dfn[x])continue; E[i].flag=E[i^1].flag=1; stk[++top]=i; if(!dfn[y]){ tarjan(y,x); low[x]=min(low[x],low[y]); if(low[y]>dfn[x]){ tot++; do{ int k=stk[top]; if(mark[E[k].y]!=tot){ em[++cnt]=make_pair(tot,E[k].y); mark[E[k].y]=tot; } if(mark[E[k^1].y]!=tot){ em[++cnt]=make_pair(tot,E[k^1].y); mark[E[k^1].y]=tot; } Id[k>>1]=tot; }while(i!=stk[top--]); } } else low[x]=min(low[x],dfn[y]); } }

总结:

tarjan是一个非常使用且易记忆的算法,将一张有环图转换为DAG一棵树后,
也就变成了一个十分常规的问题了,然后就可以再运用合理的数据结构或算法将其迎刃而解!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233823.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号