概念:
欧拉路径:经过图中所有的边一次(一笔画)是欧拉路径
欧拉回路:经过图中所有的边一次,并回到起点,是欧拉回路。
欧拉图:存在欧拉回路的图就是欧拉图
欧拉回路是欧拉路径,欧拉路径不一定是欧拉回路。
半欧拉图:只有欧拉路径(非回路)的图是半欧拉图。
判断一个无向图和有向图是否有欧拉路径和欧拉回路很简单,无向图看每个点的度数,有向图看每个点的出入度。
欧拉路径 | 欧拉回路 | |
---|---|---|
无向图 | 奇度数结点数为 0 或 2 | 奇度数结点数为 0 |
有向图 | 起点的入度 - 出度 = -1,终点的入度 - 出度 = 1, 其余点入度=出度 | 每个结点的入度 = 出度 |
无向图中,如果奇度数点个数为2,可以直接看出该图是半欧拉图,有一个非回路的欧拉路径。而且以这两个点为起终点。
推荐一个不错的教程,来自油管的 WilliamFiset
Existence of Eulerian Paths and Circuits | Graph Theory
另一个有关欧拉路径回路的问题是找出欧拉路径回路。用深搜dfs就可以很容易的找出来了。
具体的说是 Hierholzer’s Algorithm —Hierholzer算法
具体思路有点像dfs版拓扑排序。这个算法前提是图已经是判断出来是有欧拉路径或欧拉图了,然后还要找到开始结点。无向图中,没有奇数点就随便找起点,有两个奇数点,这两个点就是起终点,可以随便选取一个做起点。有向图中,所有点出入度都相同就任意取起点,如果有一个入度-出度=-1的点,这个点就是起点,而且还有一个入度-出度=1的为终点。
起点找完,开始深搜,
- 每搜一条边,要标记为走过,
- 遇到阻塞(该结点没有后续邻边或该结点的邻边都搜完),该节点入栈,
- 回溯,继续搜可以走的边,遇到阻塞一样要入栈。
- 直到深搜结束,得到一个序列存在栈中
因为每次都是阻塞了才入栈,说明每次入栈的点都是最靠后的,栈中的序列是倒序的。最后弹出元素得到的序列就是顺序的路径。
存图数据可以用邻接矩阵,也可以用邻接表,这里我用了邻接矩阵,写起来很方便,还可以用作标记数组,标记边的访问情况。
油管中的视频有动态的演示,推荐大家去看一看,逼站也有对应的翻译的视频
Eulerian Path Algorithm | Graph Theory
dfs求欧拉回路的方法大致如下:
伪代码:
fun dfs(u):
for (v, v < n, v++):
if (graph[u][v]):
graph[u][v] = graph[v][u] = 0
dfs(u)
stack.put(u)
main:
# graph[u][v] == 1 表示u-->v有边,0表示没边,如果有重边就可以在原来的基础上加1
stack, graph[n][n]
if (has_euler_path()):
start = find_start()
dfs(start)
while (!stack.empty)
print(stack.pop())
else:
print("The graph do not exist Euler Path.")
由于dfs容易爆栈,这里用python写了个非递归的,用栈模拟递归的方法。从当前结点开始搜,搜到一个可以访问的结点就入栈,去边,break,继续上一层循环,直到遇到阻塞就出栈,把出栈的结点压入路径的栈中。
def get_path(st):
"""Hierholzer算法(非递归)"""
path = []
stack = [st]
while stack:
curnode = stack[-1]
for v in range(n):
if graph2[curnode][v]:
graph2[curnode][v