Tarjan算法----寻找连通图中的割点
寻找连通图中的割点
谈到求割点最经典的算法就是tarjan。这个算法的思路是利用dfs过程建立一棵深度优先搜索树(dfs tree)。对于一个点u到他所能到达的v,若v未被访问过则这个v与u的连线我们可以称作父子边,
若v已被访问过那我们就把u与v之间的连线成为回边。
由这些父子边与回边组成的“图”我们就称它为深度优先搜索树(dfs tree)。
举个例子

图一是一个连通图,图二就是以0为根节点生成的dfs tree。实线是父子边,虚线是回边。
我们观察上图可以看出,根节点只有一个子树,所以不是割点。但我们可以自己在尝试换个图,自己画出对应的dfs tree,会发现根节点要是有两个以上的子树(subtree),那么他必然是一个割点(Aps)。
我们在仔细观察上图还会发现对于一个 non-leaf,non-root node u 的子节点 v,以v为根的子树若没有一条回边指向 u 节点的祖先节点,那么这个u点必然是一个割点(APs)。下图帮助理解这句绕口的话

第一个条件的处理代码实现非常容易,但对于第二个条件的处理就要复杂一些。
先声明两个数组(i是各节点的编号):
disc[ui] 用来维护每个点ui被访问的次序。
low[ui] 用来维护每个以ui节点为根的子树中的vi节点所能到达的最先被访问的点的次序,即 low[u] = min{low[u],low[v]}。若v节点已被访问,说明这个v节点是u节点的祖先,那么low[u] = min{low[u],disc[v]}。
这里low[v]的值比disc[u]小就说明u的子树中存在节点v可以回溯回u的一个祖先节点。
tarjan算法 c++实现
1 #include<iostream> 2 using namespace std; 3 4 int adj[550][550]; 5 int parent[550],disc[550],low[550]; 6 bool visit[550]; 7 bool ap[550]; 8 int n; 9 10 void dfs(int u){ 11 static int time = 0; 12 int children = 0; 13 visit[u] = true; 14 disc[u] = low[u] = ++time; 15 for(int i=0;i<n;i++){ 16 if(adj[u][i] != 0){ 17 int v = i; 18 if(!visit[v]){ 19 children++; 20 parent[v] = u; 21 dfs(v); 22 low[u] = min(low[u],low[v]); 23 if(parent[u] == -1 && children>1){ 24 ap[u] = true; 25 } 26 if(parent[u] != -1 && low[v] >= disc[u]){ 27 ap[u] = true; 28 } 29 } 30 else if(parent[u] != v){ 31 low[u] = min(low[u],disc[v]); 32 } 33 } 34 } 35 } 36 37 void init(){ 38 for(int i=0;i<n;i++){ 39 parent[i] = -1; 40 visit[i] = false; 41 ap[i] = false; 42 disc[i] = 0; 43 low[i] = 0; 44 } 45 } 46 47 int main(){ 48 int m; 49 cin>>n>>m; 50 for(int i=0;i<m;i++){ 51 int u,v; 52 cin>>u>>v; 53 adj[u][v] = 1; 54 adj[v][u] = 1; 55 } 56 for(int i=0;i<n;i++){ 57 if(ap[i]) 58 cout<<ap[i]<<endl; 59 } 60 }
最后说一下算法的时间复杂度为O(V+E)。dfs的时间复杂度也是O(V+E)。
References:https://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/

浙公网安备 33010602011771号