部分内容转载自http://www.cnblogs.com/en-heng/p/4002658.html点击打开链接
1.相关概念
无向连通图:无向图是连通的,当且仅当从任意节点开始的深度优先搜索将会遍历到每一个节点。
双连通图:一个无向连通图,如果不存在删除后就使得剩下的图不再连通的节点,那么这样的无向连通图就是双连通的。
割点:如果一个图不是双连通的,那么将其删除使图不再连通的节点称为割点。
关节点和重连通图在实际中较多应用。显然,一个表示通信网络的图的连通度越高,其系统越可靠,无论是哪一个站点出现故障或遭到外界破坏,都不影响系统的正常工作;又如,一个航空网若是重连通的,则当某条航线因天气等某种原因关闭时,旅客仍可从别的航线绕道而行;再如,若将大规模的集成电路的关键线路设计成重连通的话,则在某些元件失效的情况下,整个片子的功能不受影响,反之,在战争中,若要摧毁敌方的运输线,仅需破坏其运输网中的关节点即可。
2. 例子

- DFS搜索树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树,如图(b)所示。
- 树边:在搜索树中的实线所示,可理解为在DFS过程中访问未访问节点时所经过的边。
- 回边:在搜索树中的虚线所示,可理解为在DFS过程中遇到已访问节点时所经过的边。
3.算法思路
该算法是R.Tarjan发明的。观察DFS搜索树,我们可以发现有两类节点可以成为割点:
- 对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
- 对非叶子节点u(非根节点),若其子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与u的子树的节点不再连通;则节点u为割点。
对于根结点,显然很好处理;但是对于非叶子节点,怎么去判断有没有回边是一个值得深思的问题。
我们用dfn[u]
记录节点u在DFS过程中被遍历到的次序号,low[u]
记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小),那么low[u]的计算过程如下:
我们可以分为如下步骤计算:
1. DFS前向遍历,为每个节点编号num,和初始化low
2.后向遍历得到每个节点的low,以及判断是否是割点
4. java源代码
import java.util.*;
/**寻找割点*/
public class FindArt {
static class Node
{
Node(String name)
{
this.name=name;
Childen=new ArrayList<Node>();
}
boolean visited;
int num;
int low;
String name;
Node parent;
List<Node> Childen;
public boolean equals(Node node)
{
if(this.name==node.name) return true;
return false;
}
}
public static void main(String args[])
{
Node A=new Node("A");
Node B=new Node("B");
Node C=new Node("C");
Node D=new Node("D");
Node E=new Node("E");
Node F=new Node("F");
Node G=new Node("G");
A.Childen.add(B);
A.Childen.add(D);
B.Childen.add(A);
B.Childen.add(C);
C.Childen.add(B);
C.Childen.add(D);
C.Childen.add(G);
D.Childen.add(A);
D.Childen.add(C);
D.Childen.add(E);
D.Childen.add(F);
E.Childen.add(D);
E.Childen.add(F);
F.Childen.add(D);
F.Childen.add(E);
G.Childen.add(C);
find(A);
count=1;
print(A);
}
private static int count=1;
private static List<Node> points=new ArrayList<Node>();
private static List<Node> mynode=new ArrayList<Node>();
public static void find(Node node)
{
List<Node> childs=node.Childen;
node.num=count++;
node.low=node.num;
node.visited=true;
for(Node n:childs)
{
if(!n.visited)
{
n.parent=node;
//前向遍历,给每个节点编号
find(n);
//判断是否是割点
if(n.low>=node.num&&node.num!=1)
{
points.add(node);
System.out.println(node.name+"是割点");
}
//后向遍历,计算low
node.low=Math.min(node.low,n.low);
}
else
{
//背向边中num
if(node.parent!=null&&!node.parent.equals(n))
node.low=Math.min(node.low,n.num);
}
}
}
public static void print(Node node)
{
mynode.add(node);
List<Node> childs=node.Childen;
System.out.println("name"+node.name+" num:"+node.num+" low:"+node.low);
for(Node n:childs)
{
if(!mynode.contains(n))
{
print(n);
}
}
}
}
5.执行结果
C是割点
nameA num:1 low:1
nameB num:2 low:1
nameC num:3 low:1
nameD num:4 low:1
nameE num:5 low:4
nameF num:6 low:4
nameG num:7 low:7