Hierholzer 算法
算法概述
Hierholzer 算法是一种用于在有向图或无向图中寻找欧拉回路或欧拉通路的高效算法。它由德国数学家 Carl Hierholzer 于 1873 年提出。
欧拉回路:一条遍历图中每条边恰好一次,并最终回到起点的回路。
欧拉通路:一条遍历图中每条边恰好一次,但不一定回到起点的路径。
该算法以其简洁性和 O(|E|) 的线性时间复杂度而闻名。
算法的前提条件
在应用 Hierholzer 算法之前,必须首先判断图是否存在欧拉回路或通路。
无向图:
欧拉回路:所有顶点的度数为偶数。
欧拉通路:恰好有 0 个或 2 个顶点的度数为奇数。如果有两个奇度数顶点,它们分别是路径的起点和终点。
有向图:
欧拉回路:每个顶点的入度等于出度。
欧拉通路:恰好有一个顶点的出度比入度大 1(起点),恰好有一个顶点的入度比出度大 1(终点),其余所有顶点的入度等于出度。
如果图不满足上述条件,则不存在欧拉回路或通路。
算法步骤(用于求欧拉回路)
假设我们已知图满足存在欧拉回路的条件。
选择起点:从图中任选一个顶点作为当前顶点
v(对于欧拉回路,任意顶点都可作为起点)。将其压入一个栈stack中。深度优先遍历:
只要当前顶点
v还有未访问的边,就执行以下循环:
a. 从v的邻边中任意选择一条未访问的边(v, u)。
b. 将这条边标记为已访问。
c. 将顶点v压入栈stack。
d. 将当前顶点移动到u(即令v = u)。
回溯与构建回路:
当当前顶点
v没有未访问的边时(意味着你走到了一个“死胡同”),这个顶点必然是回路的一部分。将
v加入到你的结果列表(或链表)circuit的头部。从栈
stack中弹出栈顶元素,并将其设为新的当前顶点v(即回溯到上一个顶点)。
循环与结束:
重复步骤 2 和 3,直到栈
stack为空,并且当前顶点v也没有未访问的边为止。此时,列表
circuit中存储的就是逆序的欧拉回路。
最终结果:列表 circuit 中从尾到头的序列,或者直接将其反转,即为从起点出发的欧拉回路。
算法演示(无向图)
假设我们有以下无向图,所有顶点度数均为偶数,存在欧拉回路。
1/ \2---3/ \ / \
4---5---6我们以顶点 1 为起点。
stack = [],circuit = [],v = 1DFS 开始:从
1出发,我们选择路径1 -> 2 -> 5 -> 3 -> 1。此时形成了一个环,但并未遍历所有边。栈
stack现在为[1, 2, 5, 3]当前顶点
v = 1,并且它没有未访问的边了(因为边1-2和1-3都已访问)。
回溯:
将
1加入circuit。circuit = [1]弹出栈顶
3,v = 3。检查
3,发现它还有未访问的边(3 -> 5,3 -> 6)。
再次 DFS:从
3出发,选择路径3 -> 5 -> 6 -> 3。栈
stack变为[1, 2, 5, 3, 5, 6]当前顶点
v = 3,没有未访问的边了。
回溯:
将
3加入circuit。circuit = [3, 1]弹出栈顶
6,v = 6。6没有未访问边。将
6加入circuit。circuit = [6, 3, 1]弹出栈顶
5,v = 5。5没有未访问边。将
5加入circuit。circuit = [5, 6, 3, 1]弹出栈顶
3,v = 3。(这个3是第一次 DFS 时压入的)将
3加入circuit。circuit = [3, 5, 6, 3, 1]弹出栈顶
5,v = 5。(第一次 DFS 时压入的)将
5加入circuit。circuit = [5, 3, 5, 6, 3, 1]弹出栈顶
2,v = 2。检查2,发现它还有未访问的边(2 -> 4)。
最后一次 DFS:从
2出发,选择路径2 -> 4 -> 5 -> 2。...(过程类似)
最终回溯:最终,栈被弹空,
circuit中存储了完整的回路。circuit = [2, 5, 4, 2, 5, 3, 5, 6, 3, 1]
欧拉回路(从 1 开始阅读 circuit):1 -> 3 -> 6 -> 5 -> 3 -> ... 或者将 circuit 反转得到正序:1, 2, 4, 5, 2, 3, 5, 6, 3, 1。可以验证这条路径遍历了所有边且回到起点。
