tarjan算法简介
tarjan算法是Robert Tarjan发明的基于深度优先搜索的算法,在无向图中可以用来求图的割点,在有向图中可以求图的强连通片。
辅助数组
算法中有两个标志用的数组:dfn[]与low[]
dnf[]:深度优先搜索时的时间戳,记录每个点被发现时的时刻。
low[x]:在无向图中表示在深度优先搜索树中以顶点x为根的子树中的所有顶能够到达的最早的顶(不包括根节点回边)。在有向图中表示以顶点x为根的子树中所有顶连出的边能到达的最早的能到达x的顶
算法思想
求割点
对于生成树中的根节点,只要子树个数不为一就是割点;对于非根节点u,对于边(u, v),如果low[v]>=dfn[u],就是说去掉u以后以v为跟的子树中的顶无法到达u之前的顶,此时u就是割点,而low[v]<dfn[u]时,显然u不是一个割点。
求强连通片
tarjan算法在求强连通片时需要辅助栈的帮助,可以在线性时间内求出有向图的强连通片。
显然的是low[u]<=dfn[u],tarjan的做法是在DFS首次发现时入栈,每当遇到low[u]=dfn[u]的顶时弹出栈中元素直到u被弹出,在DFS进行的过程中栈中元素表示的就是能到达下一个被访问的顶的顶的集合,而low[u]=dfn[u]的发生意味着得到一个强连通片,这时弹出这个强连通片中的元素也保证了栈中元素的上述性质。很多文章中认为判断是否属于同一强连通片的标准是low值是否相等,我认为不是这样的,能判断是否一个强连通片是依靠出栈的时间,同一批出栈的顶属于同一强连通片,这一点在代码中也得到体现。
代码实现
强连通片
void tarjan(int u) {
int v;
low[u] = dfn[u] = ++inde;
sta[top++] = u;
instack[u] = true;
for (int i = head[u]; i != -1; i = edge[i].nxt) {
v = edge[i].to;
if (!dfn[v]) {
tarjan(v);
if (low[u] > low[v])
low[u] = low[v];
} else if (instack[v] && low[u] > dfn[v])
low[u] = dfn[v];
// } else if (instack[v] && low[u] > low[v])
// low[u] = low[v];
}
if (low[u] == dfn[u]) {
scc++;
do {
v = sta[--top];
instack[v] = false;
bel[v] = scc;
num[scc]++;
} while (v != u);
}
}
注意到其中有唯一一处注释,这里用low还是dfn更新low是一个比较难思考的问题,无论用low还是dfn好像都很难证明出算法的正确性或是不正确,在我看来对于求强连通片来说两种写法得到的结果都是正确的,但是注释中的写法不利于与求割点的代码思想统一,写成low的写法也很难说出low数组表示的含义,如果非要说那就是子树中所有顶通过不止一步能到达的最早的顶,但是这样的话low值将会与DFS时的遍历顺序有关,而且因为只需要比较low与dfn的大小如果子树中所有顶都不能一步走到x之前,用注释中的写法并不会对结果产生影响。因此两种写法对于求强连通片都是正确的,但是用dfn更美观更合理。
割点
略
总结
只是自己的理解并不一定正确,用来记录学习只记录了有疑惑的地方所以可读性不高。