图系列(四)欧拉通路与欧拉回路

欧拉通路与欧拉回路

之前,写了图系列一二三,现在出四啦!这也意味着,对于图的部分,可以说50%以上常用的内容就已经过了一遍了。欧拉路的部分会稍微难一点,主要是我们要和定义打交道了。至于其他图的理论,我感觉比较有用的就不剩下多少了。可能就还有同构什么的,还会有一些探讨的空间。好长一段时间没有写东西啦!这篇文章,大致会经过几次修改完成。主要参考了Leetcode的这道题——重新安排行程。其实,这道题目,用图一二三的方法也能解决,但是非常复杂,我花了很长时间。

定义

下面这段话,请同学们略过。

我真是越来越佩服欧拉了,哈哈哈!因为最近看数学比较多,所以经常看到欧拉的名字。这些数学家挺伟大的,这些数学理论给我们计算机人提供了指导,虽然有多少能派上用场真的不好说,但是有总比没有好。不过,话说回来,对于计算机人来说,理论当然重要,但是怎么去实现也是非常重要的!以前,我上离散数学,学得懂,但是不会用。拿到实际问题,根本不知道怎么用数学知识去解决。比如,排列与组合就是典型的例子,道理我都懂,怎么写个算法出来呢?以前我一直搞不出来,所以挫败感挺严重的。不过,现在一切都越来越明朗了。我感觉,我的数学知识逐渐能够派上用场了。要能够融会贯通还是需要付出挺多时间的,个人来讲,感觉学数学还是挺有兴趣的,所以,多花点时间不是什么大问题。

上图为柯尼斯堡的七座桥(摘自Wikipedia),要求你走过七座桥恰好一次。这个问题是无解的,因为图中一共有四个度数为奇数的顶点。奇定点意味着,你从这个点出发,如果要用完所有的边,你一定回不来;如果进入这个点,用完所有的边,你一定出不来。所以,要刚好走一遍,只能有两个奇定点。

1. 欧拉通路: 通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。(不需要回到原点)

2. 欧拉回路: 通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。(要能够回到原点)

3. 半欧拉图:具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图。

4. 欧拉图: 具有欧拉回路的无向图称为欧拉图。 

5. 连通图

    a. 对于无向图G,若从顶点vi到顶点vj有路径相连,则称vi和vj是连通的。如果在无向图G中,任意两点都是互通的,那么无向图G是连通的。如何用代码实现呢?其实从一个点出发,通过广度/深度优先遍历,能够遍历到每一个点,说明该图是连通的。

    b. 对于有向图G,若从顶点vi到顶点vj有路径相连(图中所有的边是同向的),则称vi和vj是连通的。如果在有向图G中,任意两点vi和vj,存在一条路径从vi到vj且存在一条路径从vj到vi,则有向图G是强连通的。如果任意两点都是连通的,则称G是连通的。(必须遍历C(n, 2)个定点组合了,我现在能想到的办法。)

性质

1. 对于无向图G

    a.  G 是欧拉图当且仅当 G 是连通的且没有奇度顶点。(我们首先可以对每个点的度数进行统计,判断是否符合条件。然后,进行深度优先遍历,如果能够遍历完所有的点,那么G是欧拉图)

    b. G 是半欧拉图当且仅当 G 是连通的且 G 中恰有 2 个奇度顶点。(首先,对度数进行统计,判断是否符合条件。然后,进行一次深度优先遍历就好了,如果能够遍历完所有的点,那么G是欧拉图)

证明无向图是连通的,只需要进行深度/广度优先遍历,能够遍历所有的点就行。

2. 对于有向图G

   a. G是欧拉图当且仅当 G 的所有顶点属于同一个连通分量且每个顶点的入度和出度相同。(首先,对度数进行统计,判断是否符合条件。然后,进行一次深度优先遍历就好了,如果能够遍历完所有的点,那么G是欧拉图。)

   b. G 是半欧拉图当且仅当 G 的所有顶点属于同一个连通分量且:1)恰有一个点的出度与入度相差1;2)恰有一个点的入度与出度相差1;3)其他顶点的入度与出度相等。(首先,对度数进行统计,判断是否符合条件。然后,从出度与入度相差1的顶点出发进行一次深度优先遍历就好了,如果能够遍历完所有的点,那么G是欧拉图。)

算法实现 - 针对有向图

1. 生成一张图,List<String> edge是有两个String定点构成的边,List<List<String>> graph是由图中所有的边构成的。

/**
 * @Author: zhaoyangyingmu
 * @Date: 2020/9/9
 * @Description: graph
 * @version: 1.0
 */
public class Graph {
    static String[] graphStr = new String[] {
            "[[\"EZE\",\"AXA\"],[\"TIA\",\"ANU\"],[\"ANU\",\"JFK\"],[\"JFK\",\"ANU\"],[\"ANU\",\"EZE\"],[\"TIA\",\"ANU\"],[\"AXA\",\"TIA\"],[\"TIA\",\"JFK\"],[\"ANU\",\"TIA\"],[\"JFK\",\"TIA\"]]",// 欧拉回路
            "[[\"JFK\",\"SFO\"],[\"JFK\",\"ATL\"],[\"SFO\",\"ATL\"],[\"ATL\",\"JFK\"],[\"ATL\",\"SFO\"]]", // 欧拉通路
            "[[\"MUC\",\"LHR\"],[\"JFK\",\"MUC\"],[\"SFO\",\"SJC\"],[\"LHR\",\"SFO\"]]",// 欧拉通路
            "[[\"MUC\",\"LHR\"],[\"JFK\",\"MUC\"],[\"SFO\",\"SJC\"],[\"LHR\",\"SFO\"], [\"LHJ\",\"LHJ\"]]" // LHJ不可达
    };
    public static List<List<String>> generateGraph(String str) {
        str = str.substring(2, str.length()-2);
        String[] edges = str.split("],\\[");
        List<List<String>> graph = new LinkedList<>();
        for (String edge: edges) {
            edge = edge.replaceAll("\"", "");
            String[] begin2end = edge.split(",");
            List<String> edgeList = new LinkedList<>();
            edgeList.add(begin2end[0]);
            edgeList.add(begin2end[1]);
            graph.add(edgeList);
        }
        return graph;
    }
    public static List<List<String>> generateGraph(int idx) {
        return generateGraph(graphStr[idx]);
    }
}

2. 出入度统计,构造邻接链表,设置所有的边为未访问的;最后深度优先遍历,用完的边要删除掉,返回一笔画的结果。

import java.util.*;

class Solution {
    Map<String, List<String>> adjs = new HashMap<>();// 邻接链表
    Map<String, int[]> degrees = new HashMap<>(); // 入度+出度
    Map<String, Boolean> visited = new HashMap<>(); // 是否访问
    List<String> eulerPath = new LinkedList<>();

    /**
     * 如果是一张欧拉图,返回一笔画的路径(不唯一)
     * 否则返回null
     * */
    public List<String> traverseEulerPath(List<List<String>> graph) {
        for (List<String> edge: graph) {
            String begin = edge.get(0);
            String end = edge.get(1);

            if (!adjs.containsKey(begin)) adjs.put(begin, new LinkedList<>());
            adjs.get(begin).add(end);

            if (!degrees.containsKey(begin)) degrees.put(begin, new int[]{0, 0});
            degrees.get(begin)[1] += 1;
            if (!degrees.containsKey(end)) degrees.put(end, new int[]{0,0});
            degrees.get(end)[0] += 1;

            if (!visited.containsKey(begin)) visited.put(begin , false);
            if (!visited.containsKey(end)) visited.put(end, false);
        }

        // 出入度统计
        String begin = null;
        String end = null;
        for (String point: degrees.keySet()) {
            int[] degree = degrees.get(point);
            if (degree[0] - degree[1] == 1) {// end point
                if (end == null) end = point;
                else return null;
            }
            else if (degree[1] - degree[0] == 1) {
                if (begin == null) begin = point;
                else return null;
            }
            else if (degree[1] - degree[0] == 0) continue;
            else return null; // 出度不等于入度
        }
        if ((begin == null && end != null)
                || (begin == null && end != null )) return null; // 只有一个点符合条件

        // begin与end同时为null,从任意点开始访问
        // 从begin开始访问
        if (begin == null) begin = degrees.keySet().iterator().next();
        dfs(begin);

        // 检查是否访问了所有的点
        for (String point: visited.keySet()) {
            if (!visited.get(point)) {
                System.out.println("point: " + point + " not available.");
                return null;
            }
        }

        Collections.reverse(eulerPath);
        return eulerPath;
    }

    private void dfs(String point) {
        visited.put(point, true);
        while (adjs.containsKey(point) && adjs.get(point).size() > 0) {
            String tmp = adjs.get(point).get(0);
            adjs.get(point).remove(0);
            dfs(tmp);
        }
        eulerPath.add(point);
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值