文章参考博文:http://www.cnblogs.com/en-heng/p/4002658.html。对无相连通图的割点问题做了较为详细的描述。现将问题在这里做一个简要的描述:
在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点。对求割点的问题,最著名的求解方法是基于DFS的。DFS过程会产生DFS搜索树。
观察DFS搜索树,我们可以发现有两类节点可以成为割点:
- 对根节点u,若其有两棵或两棵以上的子树,则该根结点u为割点;
- 对非叶子节点u(非根节点),若其子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与u的子树的节点不再连通;则节点u为割点。
dfsnum[u]
记录节点u在DFS过程中被遍历到的次序号,
low[u]
记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小)
参考的博文中存在一个问题,度为1的结点求得的low值并是其DFS树结点的dfsnum
值,即没有跳过父边,不符合“非父子边”的概念,这会在之后的求割线的问题是制造麻烦,因此,需要加上对是否为DFS树的父子边加以判断:
过程需要一些全局变量:
int [] dfsnum = new int[20]; //DFS结点遍历的顺序,根结点为1,接下来遍历到的依次+1
int num = 0; //用来为DFS标序号,初始化为0
int parent = 0; //用来判断是否根结点是割点
static int [] parents = new int[20]; //用来记录DFS祖先的数组
int []low = new int [20]; //每个结点在不经过DFS树的父结点时,能够回到的最小标号的结点
寻找割点的程序如下:
public void CutVertices(int u){ //寻找割点/关节点
low[u]= dfsnum[u] = ++num;
for(int v=0;v<VertexCount;v++){ //VertexCount为实际的图中的结点个数
if(adjMatrix[u][v]==1 && dfsnum[v]==0){ //u到v有边,且v没有访问过,adjMatrix为邻接矩阵
parents[v] = u;
if(u==0)
parent++;
CutVertices(v); //递归处理
if(low[v]>=dfsnum[u] && u!=0) //注意,此处为>=,u=0说明是根结点,判断方法是不一样的
System.out.println("Cut Vertex:"+u);
low[u] = Math.min(low[u],low[v]);
}
else if(adjMatrix[u][v]==1 && parents[u]!=v){ //有边,且v已经访问过,且u在DFS树中的祖先不是v
low[u] = Math.min(low[u],dfsnum[v]);
}
}
if(parent>1)
System.out.println("Cut Vertex:0");
}
寻找割线的问题与割点问题类似,只是条件由
low[v]>=dfsnum[u]
改变为:
low[v]>dfsnum[u]
其程序如下:
public void Bridges(int u){ //寻找割桥/割边
low[u]= dfsnum[u] = ++num;
for(int v=0;v<VertexCount;v++){ //VertexCount为实际的图中的结点个数
if(adjMatrix[u][v]==1 && dfsnum[v]==0){ //u到v有边,且v没有访问过,adjMatrix为邻接矩阵
parents[v] = u;
if(u==0)
parent++;
Bridges(v);
if(low[v]>dfsnum[u] && u!=0)
System.out.println("Cut Bridge:"+u+" "+v);
low[u] = Math.min(low[u],low[v]);
}
else if(adjMatrix[u][v]==1 && parents[u]!=v){ //有边,且v已经访问过,且u在DFS树中的祖先不是v
low[u] = Math.min(low[u],dfsnum[v]);
}
}
}