欧拉回路和欧拉路径

什么是欧拉回路

从一个点出发,沿着边行走,经过每一个边恰好一次(要经过所有的边),之后在回到出发点。 有哈密尔顿回路,并不一定存在欧拉回路。

欧拉回路一定遍历了所有顶点。但是存在欧拉回路是不是一定存在哈密尔顿回路呢? 其实不是的,因为哈密尔顿回路要求每一个顶点只能遍历一次。欧拉回路不要求每一个顶点只遍历一次。

欧拉回路是欧拉研究格尼斯堡七桥问题发现的。 也就是通常大家说的 一笔画问题 很长时间没有人能解决这个问题。他很长时间也没有解决这个问题。最后欧拉尝试证明这个问题没有解。这是图论领域的开端。

欧拉回路存在的性质

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-exmEZ4Eq-1587987613919)(/Users/xieyunfei/Library/Application Support/typora-user-images/image-20200427094320801.png)]

对于每一个点,我们进去一次,出去一次,就要耗费两条边。为什么是耗费两条边呢? 因为这两条边之后不能再走了。

对于欧拉回路来说,每个边都走一遍,回到原点。 每个点都必须有进有出。每个点相连的边数必须是偶数。 (每一点的度都必须是偶数)

上面这个图就没有欧拉回路。对于无向连通图来说,每个点的度是偶数<---->图存在欧拉回路 。对于每个点的度数都是偶数—> 无向连通图是存在欧拉回路,我们是怎么知道的呢? 现在我们来证明一下[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Un7na8mG-1587987613922)(/Users/xieyunfei/Library/Application Support/typora-user-images/image-20200427095157344.png)]

这个图所有的顶点度数都是偶数,这个图一定有环。我们从任意一个顶点出发,先随便找一个环。如果这个环就是原图,已经找了欧拉回路。否则,剩下的边一定和我们找到了环相连,并且所有的顶点的度数依然是偶数,也就是依然存在环。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4RfqEfg-1587987613923)(/Users/xieyunfei/Library/Application Support/typora-user-images/image-20200427100144402.png)]

剩下的环和之前的环是有公共点的。 两个相连的环一定可以组成新的环。

判断一个图是否存在欧拉回路

在整个图是连通的情况下,我们直接验证每个顶点的度 是不是偶数。如果每个顶点的度是偶数,那么,这个图一定是存在欧拉回路的。

import java.util.LinkedList;
import java.util.Queue;

public class EulerLoop {
     
    private Graph g;
    private boolean[] visited;

    public EulerLoop(Graph g) {
     
        this.g = g;
        visited = new boolean[g.V()];
    }

    //  判断整个图是否联通
    private boolean CC() {
     
       
        bfs(0);
        return  AllVisited();
    }

    //判断是不是所有的顶点都已经被访问过
    private boolean AllVisited() {
     
        for (int i = 0; i < visited.length; i++) {
     
            if (!visited[i])
                return false;
        }
        return true;
    }

    // BFS进行以s为起点的图的连通分量的遍历
    private void bfs(int s) {
     
        visited[s] = true;
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(s);
        while (!queue.isEmpty()) {
     
            int v = queue.poll();
            for (int w : g.adj(v)) {
     
                if (!visited[w]) {
     
                    visited[w] = true;
                    queue.offer(w);
                }
            }
        }
    }

    //首先要进行连通性判断
    public boolean hasEulerLoop() {
     
        if (!CC())  // 如果不连通 直接返回false
            return false;

        for (int v = 0; v < g.V(); v++) {
     
            if (g.degree(v) % 2 != 0)
                return false;
        }
        return true;
    }
}

如何寻找欧拉回路

一般有三种方法,我们这里实现最简单的一中,Hierholzer算法,这种算法实现起来比较简单,也很容易理解。

回溯法(指数级别)

与寻找哈密尔顿回路相比,我们把遍历顶点变成遍历边。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UW0I686s-1587987613927)(/Users/xieyunfei/Library/Application Support/typora-user-images/image-20200427111242063.png)]

Fleury算法(贪心法)

如果有多条边可以选择·的话,不走桥。 我们在寻找桥的过程是不能预处理的。 我们在寻找欧拉回路的过程中在不断删除边。因此寻找桥需要进行动态寻找。

Hierholzer 算法(线性算法)

(首先我们要确定这个图是连通的)时间复杂度O(E ) 需要使用一个栈,一个叫做 curPath 当前路径,从某个起始点出发,随便找一个环,这个环的路径放在curPath 中。每次,当我们访问到一个顶点,如果这个顶点的度为0 , 说明我们找到了一个环。然后我们从栈中把顶点依次退栈,把度为0的顶点加入结果列表中,直到退栈的顶点度不为0,(这个顶点就是两个环的公共点,我们需要用这个顶点 把不同的环串起来)再次进行遍历。

import java.util.ArrayList;
import java.util.Stack;

public class Hierholzer {
     
    private Graph G;
    private boolean[] visited;

    public Hierholzer(Graph G) {
     
        visited = new boolean[G.V()];
        this.G = G ;
    }

    public boolean hasEulerLoop() {
     
        dfs(0);
        return Allvisited();
    }

    private boolean Allvisited() {
     
        for (int i = 0; i < G.V(); i++) {
     
            if (!visited[i])
                return false;
        }
        return true;
    }

    private void dfs(int s) {
     
        visited[s] = true;

        for (int w : G.adj(s)) {
     
            if (!visited[w])
                dfs(w);
        }
    }

    // 核心算法
    public ArrayList<Integer> result() {
     
        ArrayList<Integer> res = new ArrayList<>();
        if (!hasEulerLoop()) return res;

        Graph g = (Graph) G.clone();
        Stack<Integer> stack = new Stack<>();
        int curv = 0;
        stack.push(curv);//第一次放入的0 完全是为了满足while 循环条件的
        while (!stack.isEmpty()) {
     
            if (g.degree(curv) != 0) {
     //当顶点curv 的度不为0时
                stack.push(curv); // 把curv放入栈中
                int w = g.adj(curv).iterator().next(); // 找出curve下一个没有访问的顶点
                g.removeEdge(curv, w); // 然后删除curve 和 w 之间的顶点
                curv = w; // 这里我觉得用 过河拆桥 比较形象

            } else {
      // 当一个点的度为0时,说明此时恰好找到了一个环
                res.add(curv);
                curv = stack.pop();  // 回退-->我们找回退路上第一个度不为0的,也就是环与环之间的枢纽
            }
        }
        return res;
    }

    public  static  void main(String[]  args){
     
        Graph g = new Graph("g5.txt") ;
        Hierholzer hierholzer =  new Hierholzer(g);
        System.out.println(hierholzer.result());

    }
}

你可能感兴趣的:(图论)