前言
思想大概明白,发现挺难写的。【重写了一遍】
参考书籍:《啊哈,算法》
参考链接:http://www.cnblogs.com/en-heng/p/4002658.html
不小心点进来的,推荐看以上链接。
功能实现
给定一个图,寻找其割点,并输出
中文版参考
新版本
/**
*
* 图的割点算法:
* 对顶点的访问次序进行记录形成时间戳
* 每到达一个顶点,就以该顶点的下一个顶点为起点进行遍历
* 寻找该起点所能到达的顶点,时间戳的值最小的是哪个顶点。
* 若能到达的顶点时间戳大于该起点的父顶点,则表示,该父顶点是割点。
* 否则不是割点
*
* 使用深度优先遍历图时,访问顶点的次序称为时间戳
* 如果固定一个顶点为起点,那么这个时间戳是唯一的
*
* 判断割点的方法是对当前访问顶点的子顶点进行深度优先遍历
* 查看该子顶点是否可以绕过其父顶点,到达其他的顶点
* 并且其到达的其他顶点最小的时间戳,小于其父顶点的时间戳
* 否则,该子顶点的父顶点就是割点
*
* 在程序中实现此功能,需要记录正常访问时各个顶点的时间戳
* 还需要记录各个顶点,在绕过其父顶点时,能访问到的顶点的最小时间戳
*
* 同时还有一个特殊情况,如果根顶点作为父节点,其有两个[子树]时,必定为割点。
* 就是其有左右各一堆。去掉根顶点,肯定是左边一堆,右边一堆,两边无法连通。
*/
旧版本
/**
* 删除一个无向图中某个顶点后,任意两点不再连通,称为图的割点。
*
*
* 基于深度优先的割点算法
*
* 对于一个无向图,判断其是否存在割点
* 使用深度优先搜索遍历图,生成dfs搜索树
* 在使用深度优先搜索对任意一个顶点初次访问时
* 记录下该顶点被访问的次序,称为时间戳。(第一个访问的顶点时间戳是1,第二个访问的顶点是2,以此类推)
* 同时对该顶点的子节点进行深度优先搜索,记录其在不经过父节点时,能回到的最小时间戳,此时有两种情况
* 如果为根节点
* 当且仅当其有两颗或以上子树时,才是割点
* 子树的意义复习:http://mmc.sxtcm.com/wlkj/sjjg/6-1.html
* 如果非根节点
* 该子节点要么回到了比父节点更老的祖先(更老。时间戳更小)
* 要么该子节点只能回到自己本身,或者比自己更年轻的子节点(更年轻,时间戳更大)此情况时,该子节点的父节点为割点
*/
代码实现
import java.util.Scanner;
public class NewCutVertex {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n, m;
n = in.nextInt();
m = in.nextInt();
map = new int[n + 1][m + 1];
isVisited = new boolean[n + 1];
visitOrder = new int[n + 1];
lowerVisitOrder = new int[n + 1];
for (int i = 0, a, b; i < m; i++) {
a = in.nextInt();
b = in.nextInt();
map[a][b] = 1;
map[b][a] = 1;
}
dfs(1, 1);
}
//根节点
static final int ROOT = 1;
static int[][] map;
static int[] visitOrder;
static int[] lowerVisitOrder;
static int currVisitTimes = 1;
static boolean[] isVisited;
public static void dfs(int currV, int vFather) {
/**
* 记录当前访问顶点的时间戳
* 首次访问时,其能访问到的最小时间戳也是自己
*/
visitOrder[currV] = lowerVisitOrder[currV] = currVisitTimes++;
isVisited[currV] = true;
for (int i = 1, child = 0; i < map.length; i++) {
//如果从当前顶点,能够到达下一个子顶点i
if (map[currV][i] == 1) {
/**
* 2种情况
* 如果还没有访问过顶点i
* 那么,就继续深入,以i点为起点,进入下一个点
* 直到最后一个点,在这个过程中,最小时间戳会得到更新
* 更新当前访问顶点的最小时间戳,如果当前顶点能够访问到顶点i
* 顶点i作为当前顶点的子顶点
* 如果i的最小时间戳不小于(即大于等于)当前顶点的时间戳
* 那么当前顶点的时间戳是割点
* 如果已经访问过顶点i
* 那么就是在寻找当前顶点的最小时间戳
* 在顶点i的时间戳与当前顶点的最小时间戳之间,取较小者,作为当前顶点的最小时间戳
*/
if (!isVisited[i]) {
//判断根顶点的子树数量
child++;
//下面这句执行完之后,currV是父顶点,i是子顶点
dfs(i, currV);
//更新最小时间戳
lowerVisitOrder[currV] = Math.min(lowerVisitOrder[currV], lowerVisitOrder[i]);
//所以这里是判读i的最小时间戳
if (currV != ROOT && lowerVisitOrder[i] >= visitOrder[currV]) {
System.out.println(currV);
}
//如果是根节点
if (currV == ROOT && child >= 2) {
System.out.println(currV);
}
} else if (i != vFather){
lowerVisitOrder[currV] = Math.min(lowerVisitOrder[currV], visitOrder[i]);
}
}
}
}
}
结果
输入
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6
输出
2