JAVA实践基于DFS的图割点

前言

思想大概明白,发现挺难写的。【重写了一遍】

参考书籍:《啊哈,算法》
参考链接:http://www.cnblogs.com/en-heng/p/4002658.html

不小心点进来的,推荐看以上链接。

功能实现

给定一个图,寻找其割点,并输出

中文版参考

新版本

pic

/**
 *
 * 图的割点算法:
 * 对顶点的访问次序进行记录形成时间戳
 * 每到达一个顶点,就以该顶点的下一个顶点为起点进行遍历
 * 寻找该起点所能到达的顶点,时间戳的值最小的是哪个顶点。
 * 若能到达的顶点时间戳大于该起点的父顶点,则表示,该父顶点是割点。
 * 否则不是割点
 *
 * 使用深度优先遍历图时,访问顶点的次序称为时间戳
 * 如果固定一个顶点为起点,那么这个时间戳是唯一的
 *
 * 判断割点的方法是对当前访问顶点的子顶点进行深度优先遍历
 * 查看该子顶点是否可以绕过其父顶点,到达其他的顶点
 * 并且其到达的其他顶点最小的时间戳,小于其父顶点的时间戳
 * 否则,该子顶点的父顶点就是割点
 *
 * 在程序中实现此功能,需要记录正常访问时各个顶点的时间戳
 * 还需要记录各个顶点,在绕过其父顶点时,能访问到的顶点的最小时间戳
 *
 * 同时还有一个特殊情况,如果根顶点作为父节点,其有两个[子树]时,必定为割点。
 * 就是其有左右各一堆。去掉根顶点,肯定是左边一堆,右边一堆,两边无法连通。
 */

旧版本

/**
 * 删除一个无向图中某个顶点后,任意两点不再连通,称为图的割点。
 *
 *
 * 基于深度优先的割点算法
 *
 * 对于一个无向图,判断其是否存在割点
 * 使用深度优先搜索遍历图,生成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

END

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值