费了好大劲,从网上爬来的目前能接受这个,直接改了点的模板... <<思路来自这位大佬http://www.cnblogs.com/nullzx/,每看一次,理解就更深了一点>>
思路:
假设DFS中我们从顶点U访问到了顶点V(此时顶点V还未被访问过),那么我们称顶点U为顶点V的父顶点,V为U的孩子顶点。在顶点U之前被访问过的顶点,我们就称之为U的祖先顶点。
1.显然如果顶点U的所有孩子顶点可以不通过父顶点U而访问到U的祖先顶点,那么说明此时去掉顶点U不影响图的连通性,U就不是割点。相反,如果顶点U至少存在一个孩子顶点,必须通过父顶点U才能访问到U的祖先顶点,那么去掉顶点U后,顶点U的祖先顶点和孩子顶点就不连通了,说明U是一个割点。(low(v)>=dfn(u),原理见数据结构部分)
2.我们还需要考虑一个特殊情况,就是DFS的根顶点(一般情况下是编号为0的顶点),因为根顶点没有祖先顶点。其实根顶点是不是割点也很好判断,如果从根顶点出发,一次DFS(即只从根节点的一条边出发)就能访问到所有的顶点,那么根顶点就不是割点。反之,如果回溯到根顶点后,还有未访问过的顶点,需要在邻接顶点上再次进行DFS,根顶点就是割点。(son>1 ,构造过程见代码)
(看思路时,仔细对照这张图)
数据结构:
dfn数组表示时间戳,子顶点的dfn值一定比父顶点的dfn值大,在访问一个顶点后,它的dfn的值就确定下来了,不会再改变。
low数组中的值表示DFS中该顶点不通过父顶点能访问到的祖先顶点中最小的顺序值(或者说时间戳),在DFS中,我们根据情况不断更新low的值。如果:
dfn[v] < low[u]
那么
low[u] = dfn[v]
如果顶点U还有它分支,每个分支回溯时都进行上述操作,那么顶点low[u]就表示了不通过顶点U的父节点所能访问到的最早祖先节点。
代码应用:
Tarjan算法从图的任意顶点开始跑都可以得出割点集和割边集。
对于求割点和割边,因为是无向图(且图连通) 若图连通 tarjan只需运行一次,就可以把图跑完。若非连通,需要多次跑。
c++ 代码
int now,root=1,low[maxn],dfn[maxn]; //now是时间 //root是根节点,一般从tarjan()默认1开始,root也为1 vector<int> g[maxn]; int cut[maxn]; //存储每个点是否是割点 void init(){ now=0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(cut,0,sizeof(cut)); for(int i=0;i<maxn;i++) g[i].clear(); } void tarjan(int u,int father){ int son=0; dfn[u]=low[u]=++now; for(int i=0; i<g[u].size(); i++){ int v=g[u][i]; if(dfn[v]&&v!=father) low[u]=min(low[u],dfn[v]); if(dfn[v]==0){ tarjan(v,u); son++; low[u]=min(low[u],low[v]); if((u==root&&son>1)||(u!=root&&dfn[u]<=low[v])) //1.根节点连接至少两个子图 2.非根节点,子节点无法不通过父节点而回到祖先节点 cut[u]=1; } } } tarjan(1,-1);