算法:用Java实现图论算法中欧拉回路的求解(一笔画问题),并求出 "奥运五环" 的所有一笔画画法

今天我准备用Java来求解图论算法中的欧拉回路(EulerCircuit)问题,这个名字可能对大家有些陌生,如果我说一笔画,大家肯定就都清楚了。原来有一个"七桥问题",不知道的可以百度,这里就不详述。继而引出了一笔画的问题。后来欧拉大神就提出了一笔画的一个原则,那就是一个图形如果可以一笔画,那么把它抽象为图的话,度为奇数的顶点只能是0个或两个。意思也就是,可以一笔画的图形,顶点要么全部都是度为偶数的,要么有且仅有两个度为奇数的顶点,而且一笔画路径的开始和结束,都得是这两个度为奇数的顶点。然后一笔画问题就又叫欧拉回路了

如何用Java编程,来实现图论算法的欧拉回路问题,求解出图的一笔画路径呢?今天我准备以大家都熟知的奥运五环为例,来告诉大家我的实现思路和过程:

在这里插入图片描述
以上就是奥运五环,共有八个顶点,每个顶点的度都是偶数。根据欧拉定理,可以从任意顶点出发对奥运五环进行一笔画。我们可以先把奥运五环的顶点进行标记,如下图:

在这里插入图片描述
以下是我的实现思路:

  1. 检查一笔画图形每个顶点的度,如果不是全偶数度顶点或有且仅有两个奇数度顶点,则该图形无法一笔画
  2. 先将一笔画图形的所有顶点进行编号,如上图
  3. 然后计算每个顶点之间的连线数,如奥运五环图,A和B之间有3条连线,C和D之间有两条连线,D和E之间有1条连线
  4. 将一笔画图形抽象为新的图,重新分布顶点,将所有有连线的顶点都加上一条边,边是无向边,且权重为他们之间的连线数
  5. 从某一点出发(有且仅有两个度为奇数的顶点的图,则必须以其中一个奇数度顶点出发),然后递归用深度优先搜索DFS来探索一笔画路径,每走过一条边,则边的权重减一。如果当前顶点和下一个顶点之间的权重减到了0,或者本来就是0,则两点之间不可达,就换其他可达的顶点,继续递归往深处遍历,直到完成一笔画,或者接下来所有相邻顶点之间的边权重都为0,无路可走
  6. 如果无路可走但完成了一笔画(所有边的权重都为0了),则可以输出正确结果,完美结束递归。如果未完成一笔画且无路可走,直接结束当前分支递归
  7. 初始化决定一个开始顶点,求出所有下一个可达的顶点集,遍历下一个顶点集,多批次递归寻路,以求出图形的所有一笔画路径

下面这张图,就是上面步骤4中说的奥运五环抽象的图,顶点间的边的权重,就是在奥运五环中的连线数:
在这里插入图片描述
以下是上面的奥运五环抽象图的邻接矩阵:
在这里插入图片描述

下面是我用Java实现的欧拉回路的求解算法代码,并以奥运五环为例,用该算法求出 “奥运五环” 的所有一笔画画法路径。画的时候如果从A点到B点有多条路径,随意画一条路径即可,都是等价的。算法的原理和精髓都在下面的代码和其间的注释之中:

import java.util.ArrayList;
import java.util.List;

/**
 * @author LiYang
 * @ClassName EulerCircuit
 * @Description 求解欧拉回路(一笔画)问题
 * @date 2019/11/20 16:05
 */
public class EulerCircuit {

    /**
     * 返回奥运五环图的邻接矩阵(顶点间有几条连线,权重就是几)
     * @return 奥运五环图的邻接矩阵
     */
    public static int[][] initOlympicRingsMatrix() {
        //生成奥运五环图的邻接矩阵
        return new int[][] {
                {0, 3, 0, 1, 0, 0, 0, 0},
                {3, 0, 1, 0, 0, 0, 0, 0},
                {0, 1, 0, 2, 0, 1, 0, 0},
                {1, 0, 2, 0, 1, 0, 0, 0},
                {0, 0, 0, 1, 0, 2, 0, 1},
                {0, 0, 1, 0, 2, 0, 1, 0},
                {0, 0, 0, 0, 0, 1, 0, 3},
                {0, 0, 0, 0, 1, 0, 3, 0}
        };
    }

    /**
     * 判断一笔画是否完成,也就是看是否
     * 已经形成了欧拉回路
     * @param matrix 一笔画图邻接矩阵的中间状态
     * @return 是否已经完成
     */
    public static boolean isFinished(int[][] matrix) {
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix.length; j++) {
                
                //如果还有边没有走过
                if (matrix[i][j] > 0) {
                    //判定为未完成
                    return false;
                }
            }
        }
        
        //所有边已走过,判定为已完成
        return true;
    }

    /**
     * 深度拷贝Set<Integer>对象
     * @param source 拷贝源
     * @return 深度拷贝的副本
     */
    public static List<Integer> copyList(List<Integer> source) {
        //拷贝副本
        List<Integer> copy = new ArrayList<>();

        //加入所有元素
        for (Integer item : source) {
            copy.add(item);
        }

        //返回拷贝副本
        return copy;
    }

    /**
     * 深度拷贝邻接矩阵二维数组对象
     * @param matrix 邻接矩阵拷贝源
     * @return 深度拷贝的邻接矩阵副本
     */
    public static int[][] copyMatrix(int[][] matrix) {
        //拷贝副本
        int[][] newMatrix = new int[matrix.length][matrix.length];

        //复制所有值
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix.length; j++) {
                newMatrix[i][j] = matrix[i][j];
            }
        }

        //返回拷贝副本
        return newMatrix;
    }

    /**
     * 求解欧拉回路,探索一笔画路径
     * @param matrix 当前的邻接矩阵
     * @param nextVertex 下一步的路径
     * @param route 当前的路线图
     * @param vertexName 顶点名称字符串数组
     */
    public static void runEulerCircuit(int[][] matrix, int nextVertex, List<Integer> route, String[] vertexName) {
        //先复制一份当前的邻接矩阵
        int[][] newMatrix = copyMatrix(matrix);
        
        //当前驻足的顶点
        int currentVertex = route.get(route.size() - 1);
        
        //走过nextVertex,更新邻接矩阵
        //更新方式为边权重减1,两边通路都要减
        newMatrix[currentVertex][nextVertex] --;
        newMatrix[nextVertex][currentVertex] --;
        
        //再复制一份路径图
        List<Integer> newRoute = copyList(route);
        
        //路径图加入nextVertex
        newRoute.add(nextVertex);
        
        //找到nextVertex的所有下步顶点
        List<Integer> nextList = new ArrayList<>();
        
        //收集下一步可达的顶点
        for (int i = 0; i < newMatrix.length; i++) {
            if (newMatrix[nextVertex][i] > 0) {
                nextList.add(i);
            }
        }
        
        //如果有下一个可达的顶点
        if (nextList.size() > 0) {
            
            //遍历所有的下一步顶点
           for (Integer next : nextList) {
               
               //递归调用本方法,继续往下求解
               runEulerCircuit(copyMatrix(newMatrix), next, copyList(newRoute), vertexName);
           }
            
       //如果现在已经没有任何可达顶点了
        } else {
            
            //如果已经完成了一笔画,求解了欧拉回路
            if (isFinished(newMatrix)) {
                
                //字符串路径集合
                List<String> solutionRoute = new ArrayList<>();
                
                //根据顶点名称字符串数组,得到字符串的路径
                for (Integer vertexIndex : newRoute) {
                    solutionRoute.add(vertexName[vertexIndex]);
                }
                
                //整理路径信息
                String solution = solutionRoute.toString()
                        .replaceAll("\\[", "")
                        .replaceAll("]", "")
                        .replaceAll(", ", " -> ");

                //打印求得的一笔画路径(欧拉回路)
                System.out.println("找到奥运五环一笔画路径:" + solution);
            }
        }
    }

    /**
     * 求解欧拉回路,探索一笔画路径的驱动程序
     * @param matrix 一笔画图的邻接矩阵
     * @param startVertex 开始顶点,如果有且仅有两个度为三的顶点,
     *                    则必须以这两个顶点之一作为起点
     * @param vertexName 顶点名称字符串数组
     */
    public static void runEulerCircuit(int[][] matrix, int startVertex, String[] vertexName) {
        //路径
        List<Integer> route = new ArrayList<>();
        
        //从起点出发
        route.add(startVertex);
        
        //下一个顶点的集合
        List<Integer> nextVertexList = new ArrayList<>();

        //找到下一个可达的顶点
        for (int i = 0; i < matrix.length; i++) {
            if (matrix[startVertex][i] > 0) {
                nextVertexList.add(i);
            }
        }

        //遍历所有下一步可达顶点
        for (Integer nextVertex : nextVertexList) {
            
            //多分支递归调用求解欧拉回路的算法程序
            runEulerCircuit(matrix, nextVertex, route, vertexName);
        }
    }

    /**
     * 运行欧拉回路求解算法,解出奥运五环的所有一笔画路径
     * @param args
     */
    public static void main(String[] args) {
        //奥运五环图的初始邻接矩阵
        int[][] matrix = initOlympicRingsMatrix();
        
        //奥运五环图的顶点名称字符串数组
        String[] vertexName = new String[]{"A", "B", "C", "D", "E", "F", "G", "H"};
        
        //从A点出发,运行算法,解出奥运五环的所有一笔画路径
        runEulerCircuit(matrix, 0, vertexName);
    }
    
}

运行上面代码的main方法,控制台输出所有的奥运五环的一笔画画法路径(大家感兴趣可以自己尝试照着画一下),算法测试通过:

找到奥运五环一笔画路径:A -> B -> A -> B -> C -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> B -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> D -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> A -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> C -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> B -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> D -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> C -> B -> A -> B -> A
找到奥运五环一笔画路径:A -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> C -> B -> A -> B -> A
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package hamierton; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Random; public class EularCircuit { public EularCircuit() { } public static void main(String[] args) { // System.out.println("please input n:"); // BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = 4; try { // n = Integer.parseInt(br.readLine()); } catch (Exception ex) { return; } try { Graph g = new Graph(n); g.printg(); g.circuit(); } catch (Exception e) { System.out.println(e.toString()); e.printStackTrace(); return; } } } class Node { private static int count = 0; private String name; private ArrayList adjacencyList; private boolean visited =false; public Node() { name = "node " + count; adjacencyList = new ArrayList(); } public Node(String name) { this.name = name; adjacencyList = new ArrayList(); } public boolean isAllVisited() { for (int i = 0; i < adjacencyList.size(); i++) { SNode sn = (SNode) adjacencyList.get(i); if (sn.visited == false) { return false; } } return true; } public boolean isvisited(){ return visited; } public void setvisited(){ visited = true; } public int getAdjacencyCount() { return adjacencyList.size(); } public boolean contains(int i) { return this.adjacencyList.contains(new SNode(i)); } public void removeAdjacencyNode(int i) { this.adjacencyList.remove(new SNode(i)); } public void addAdjacencyNode(int i) { this.adjacencyList.add(new SNode(i)); } public SNode getAdjacencyNode(int i) { return (SNode) this.adjacencyList.get(i); } public SNode getAdjacencyNodeEX(int i_ref) { for (int i = 0; i < this.getAdjacencyCount(); i++) { if (getAdjacencyNode(i).index == i_ref) { return getAdjacencyNode(i); } } return null; } public String toString() { return this.name; } } class SNode { public boolean visited = false; public int index = 0; public SNode(int index) { this.index = index; } public boolean equals(Object o) { if (((SNode) o).index == this.index) { return true; } return false; } public String toString() { return "adjacency " + index; } } class Graph { private ArrayList nodeList; private ArrayList path; private int count; public Graph(int n) throws Exception { this.count = n; nodeList = new ArrayList(count); ginit(); } public void circuit() { path = new ArrayList(); int top = 0; int k = 0; path.add(new Integer(0)); while (true) { int i, j; i = top; ArrayList path1 = new ArrayList(); path1.add(new Integer(top)); while (true) { Node node = (Node) nodeList.get(i); for (j = 0; j < this.count; j++) { if (node.contains(j) && node.getAdjacencyNodeEX(j).visited == false) { path1.add(new Integer(j)); node.getAdjacencyNodeEX(j).visited = true; // ((Node) nodeList.get(j)).getAdjacencyNodeEX(i).visited = true; i = j; break; } } if (i == top) { break; } } path.remove(k); path.addAll(k, path1); k++; if (k >= path.size()) { break; } top = ((Integer) path.get(k)).intValue(); } for (int z = 0; z < path.size(); z++) { System.out.print(path.get(z).toString() + " "); } } private void ginit() { int i; for (i = 0; i < 4; i++) { nodeList.add(new Node("node" + i)); } ((Node)nodeList.get(0)).addAdjacencyNode(3); ((Node)nodeList.get(1)).addAdjacencyNode(0); ((Node)nodeList.get(2)).addAdjacencyNode(1); ((Node)nodeList.get(3)).addAdjacencyNode(2); // ((Node)nodeList.get(0)).addAdjacencyNode(3); // ((Node)nodeList.get(1)).addAdjacencyNode(0); // ((Node)nodeList.get(2)).addAdjacencyNode(1); // ((Node)nodeList.get(3)).addAdjacencyNode(2); // for (i = 0; i < n; i++) { // nodeList.add(new Node("node" + i)); // } // ArrayList linked = new ArrayList(); // linked.add(new Integer(0)); // Random rand = new Random(); // // for (i = 1; i < n; i++) { // int size = linked.size(); // // int top = ((Integer) (linked.get(size - 1))).intValue(); // Node node = (Node) (nodeList.get(top)); // // while (true) { // int randint = rand.nextInt(n); // if (randint == top) { // continue; // } // if (node.contains(randint)) { // continue; // } else { // if (!linked.contains(new Integer(randint))) { // linked.add(new Integer(randint)); // } else if (node.getAdjacencyCount() >= (linked.size() - 1 > 6 ? 6 // : linked.size() - 1)) { // continue; // } else { // i--; // } // node.addAdjacencyNode(randint); // Node randnode = (Node) nodeList.get(randint); // randnode.addAdjacencyNode(top); // break; // } // } // } // // for (i = 0; i < this.count - 1; i++) { // Node node = (Node) nodeList.get(i); // if (node.getAdjacencyCount() % 2 != 0) { // int j = 0; // for (j = i + 1; j < this.count; j++) { // Node nn = (Node) nodeList.get(j); // if (nn.getAdjacencyCount() % 2 != 0) { // if (node.contains(j)) { // // if (nn.getAdjacencyCount() != 1 // && node.getAdjacencyCount() != 1) { // node.removeAdjacencyNode(j); // nn.removeAdjacencyNode(i); // break; // } else { // continue; // } // } else { // // node.addAdjacencyNode(j); // nn.addAdjacencyNode(i); // break; // } // } // } // // if (j == this.count) { // int k; // Node nk = null; // for (k = i + 1; k < this.count; k++) { // // nk = (Node) nodeList.get(k); // if (nk.getAdjacencyCount() % 2 != 0) { // break; // } // } // int kk = k; // for (k = 0; k < i; k++) { // Node n1 = (Node) nodeList.get(k); // if (!n1.contains(kk) && !n1.contains(i)) { // n1.addAdjacencyNode(kk); // nk.addAdjacencyNode(k); // n1.addAdjacencyNode(i); // node.addAdjacencyNode(k); // break; // } // } // boolean retry = false; // // if (k == i) { // int vv; // for (vv = 0; vv < this.count; vv++) { // Node vn = (Node) nodeList.get(vv); // if (!vn.contains(i) && i != vv) { // vn.addAdjacencyNode(i); // node.addAdjacencyNode(vv); // retry = true; // break; // } // } // if (vv == count) { // for (vv = 0; vv < count; vv++) { // Node vnn = (Node) nodeList.get(vv); // if (vnn.getAdjacencyCount() > 1) { // vnn.removeAdjacencyNode(i); // node.removeAdjacencyNode(vv); // retry = true; // break; // } // } // } // } // if (retry) { // i = -1; // } // } // } // // } // return this.isEularG(); } public boolean isEularG() { boolean isEular = true; for (int i = 0; i < this.count; i++) { Node n = (Node) nodeList.get(i); if (n.getAdjacencyCount() % 2 != 0) { isEular = false; break; } } return isEular; } public void printg() { for (int i = 0; i < this.count; i++) { Node n = (Node) nodeList.get(i); System.out.print(n.toString() + " "); for (int j = 0; j < n.getAdjacencyCount(); j++) { System.out.print(n.getAdjacencyNode(j).toString() + " "); } System.out.println(); } } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值