一:什么是欧拉回路?
不知道你有没有玩过这样一种叫“一笔画”,从某一点开始画一个图形或图案,期间笔不能从纸上离开而且每条边只能画一次。
下面有三个例子,你可以先试一试看看能不能“一笔画”
第一个图其实是根本画不出来的,第二个图可以画出,但是不存在起点和终点为同一点的情况,第三个图可以轻松画出,而且存在起点和终点为同一点的情况
欧拉回路问题:
如果图G中的一个路径包括每个边恰好一次,则该路径称为欧拉路径(Euler path)。
如果一个回路是欧拉路径,则称为欧拉回路(Euler circuit)。
具有欧拉回路的图称为欧拉图(简称E图)。具有欧拉路径但不具有欧拉回路的图称为半欧拉图。
二: 无向图中欧拉回路存在的条件
什么情况下才存在欧拉回路呢?
充要条件:当且仅当图是连通的而且每个顶点的度是偶数
想想一下,如果一个节点v
的度为1,那么进入v
后只能留在v
中,不可能再出来。
但是当有两个度为奇数的节点分别作为起点和终点,欧拉路径还是有可能存在的,如果奇数度的顶点多余两个,连欧拉路径都不可能存在。
三:如何得到欧拉回路
求解起点和终点重合的欧拉回路问题可以基于深度优先思想来实现,与DFS算法的区别就是,DFS算法中每个节点基本上只会访问一遍,而欧拉回路算法中要求放宽,只是每个边只能访问一边,但是某一个节点可以多次访问,但基本思想是一致的。DFS算法
深度优先搜索的主要问题在于当访问返回开始节点时,可能还剩下某些边没有访问到,也就是访问提前结束了,比较好补救方法就是,在那些没有访问到的路径的第一个节点重新开始新一轮深度优先搜索,将新的回路加到原来回路中,继续此过程直到所有的边都被遍历为止
四:Java实现
import java.util.*;
/**
* @author ht113
*/
public class EulerCircuit {
private List<Integer> path;
/**
* this cost O(|E| + |V|) time
* @param unDirectedEdges: adjacency matrix,[1,2] represents edge from node1 to node2
* @param n: the num of nodes
* @param k: the start node of Euler Circuit
* @return
* @throws NotFoundException
*/
public List<Integer> EulerCircuitByDFS(int[][] unDirectedEdges, int n, int k) throws NotFoundException{
if (unDirectedEdges==null||unDirectedEdges.length<=1||n<=2) {
throw new NotFoundException();
}
//init undirected graph,use adjacency list
//{key:1, value:<2, 3>} represents edge from node1 to node2,node3
Map<Integer, List<Integer>> graph = new HashMap<>();
//making graph takes O(E)
//iterate the adjacency matrix
for(int i = 0; i<unDirectedEdges.length; i++) {
int[] edge = unDirectedEdges[i];
//add (edge[0], edge[1])
if (!graph.containsKey(edge[0])) {
graph.put(edge[0], new ArrayList<Integer>());
}
graph.get(edge[0]).add(edge[1]);
//add (edge[1], edge[0])
if (!graph.containsKey(edge[1])) {
graph.put(edge[1], new ArrayList<Integer>());
}
graph.get(edge[1]).add(edge[0]);
}
path = new ArrayList<>();
//ECDFS takes O(V)
try {
ECDFS(graph, k, k, path);
}catch (NotFoundException e){
throw e;
}
return path;
}
/**
* special dfs for Euler Circuit
* @param graph
* @param k: start node
* @param origin: the origin start node
* @param currentPath
* @throws NotFoundException
*/
public void ECDFS(Map<Integer, List<Integer>> graph, int k, int origin, List<Integer> currentPath)
throws NotFoundException{
currentPath.add(k);
for(int i = 0; i<graph.get(k).size(); i++){
int neighbor = graph.get(k).get(i);
//and the degree of node w is odd
if(neighbor!=origin && graph.get(neighbor).size()%2!=0){
throw new NotFoundException();
}
graph.get(k).remove(i);
graph.get(neighbor).remove(Integer.valueOf(k));
//when dfs return to the origin start node
//some edges may not have been visited
if(neighbor==origin){
currentPath.add(origin);
boolean allSeen;
do{
boolean tmp = true;
for(int j = 0; j<currentPath.size(); j++) {
int entryNode = currentPath.get(j);
tmp &= graph.get(entryNode).size() == 0;
if(!tmp) {
List<Integer> tempPath = new ArrayList<>();
ECDFS(graph, entryNode, entryNode , tempPath);
//add child circuit path
int index = currentPath.indexOf(entryNode);
currentPath.remove(index);
currentPath.addAll(index, tempPath);
}
}
allSeen = tmp;
}while (!allSeen);
return;
}
else {
ECDFS(graph, neighbor, origin, currentPath);
}
}
}
public static class NotFoundException extends Exception{
public NotFoundException(){
super("Euler Circuit Not Found");
}
}
}
测试:
输入:
unDirectedEdges | n | k |
---|---|---|
{{1,3}, {1,4}, {2,3}, {2,8}, {3,4}, {3,6}, {3,7},{3,9}, {4,5}, {4,7}, {4,10}, {4,11}, {5,10}, {6,9}, {7,9},{7,10}, {8,9}, {9,10}, {9,12}, {10,11}, {10,12}} | 12 | 5 |
{{1,3},{1,2},{3,4}, {2,3}, {2,4}}; | 4 | 4 |
预计输出:
某一条从节点5开始节点5结束的欧拉回路
Euler Circuit Not Found
实际输出:
[5, 4, 1, 3, 2, 8, 9, 10, 12, 9, 3, 4, 7, 3, 6, 9, 7, 10, 4, 11, 10, 5]
Euler Circuit Not Found
测试通过
你可能还敢兴趣的图论算法(均附Java实现代码):