有向图的 Hierholzer 算法
给定一个有向欧拉图,打印一个欧拉回路。欧拉回路是一条遍历图的每一条边的路径,路径结束于起始顶点。
例子:
输入:下图的邻接列表输出:0 -> 1 -> 2 -> 0 输入:下图的邻接表输出 : 0 -> 6 -> 4 -> 5 -> 0 -> 1 -> 2 -> 3 -> 4 -> 2 -> 0 解释:在这两种情况下,我们都可以通过沿着边缘追踪欧拉电路如输出所示。
我们已经讨论了找出给定图是否是欧拉图的问题。在这篇文章中,讨论了一种打印欧拉轨迹或电路的算法。使用 Fleury 算法可以解决相同的问题,但是其复杂度为 O(E*E)。使用 Hierholzer 算法,我们可以找到 O(E) 中的电路/路径,即线性时间。
以下是算法:参考(维基)。请记住,如果以下条件为真,则有向图具有欧拉环 (1) 所有非零度数的顶点都属于单个强连通分量。 (2) 每个顶点的入度和出度相同。该算法假设给定的图有一个欧拉回路。
- 选择任何起始顶点 v,然后沿着从该顶点开始的边轨迹直到返回 v。不可能卡在 v 以外的任何顶点,因为当轨迹进入另一个顶点时,每个顶点的入度和出度必须相同顶点 w 必须有一条未使用的边离开 w。
这样形成的游览是一个封闭的游览,但可能不会覆盖初始图的所有顶点和边。 - 只要存在属于当前游览的顶点 u,但其相邻边不属于游览,则从 u 开始另一条路径,沿着未使用的边直到返回 u,并将以此方式形成的游览加入到以前的巡回演出。
因此,我们的想法是继续跟踪未使用的边缘并移除它们,直到我们被卡住。一旦我们卡住了,我们就回溯到当前路径中最近的具有未使用边的顶点,然后重复这个过程,直到所有边都被使用。我们可以使用另一个容器来维护最终路径。
举个例子:
设初始有向图如下让我们从 0 开始我们的路径。因此,curr_path = {0} 和 circuit = {} 现在让我们使用边 0->1 现在,curr_path = {0,1} 和 circuit = {} 同样我们达到 2 然后再次达到 0 现在,curr_path = {0,1,2} 和 circuit = {} 然后我们转到 0,现在由于 0 没有任何未使用的边缘,我们将 0 放入电路并返回轨道直到找到边缘然后我们有 curr_path = {0,1,2} 和 circuit = {0} 同样,当我们回溯到 2 时,我们没有找到任何未使用的边。因此将 2 放入电路并再次回溯。 curr_path = {0,1} 和 circuit = {0,2} 到达 1 后,我们通过未使用的边 1->3,然后是 3->4、4->1,直到遍历所有边。两个容器的内容如下所示: curr_path = {0,1,3,4,1} 和 circuit = {0,2} 现在所有边都已使用,curr_path 被一一弹出到电路中。最后,我们有 circuit = {0,2,1,4,3,1,0} 我们反向打印电路以获得遵循的路径。即0->1->3->4->1->1->2->0
以下是上述方法的实现:
C++
// A C++ program to print Eulerian circuit in given
// directed graph using Hierholzer algorithm
#include
using namespace std;
void printCircuit(vector< vector > adj)
{
// adj represents the adjacency list of
// the directed graph
// edge_count represents the number of edges
// emerging from a vertex
unordered_map edge_count;
for (int i=0; i curr_path;
// vector to store final circuit
vector circuit;
// start from any vertex
curr_path.push(0);
int curr_v = 0; // Current vertex
while (!curr_path.empty())
{
// If there's remaining edge
if (edge_count[curr_v])
{
// Push the vertex
curr_path.push(curr_v);
// Find the next vertex using an edge
int next_v = adj[curr_v].back();
// and remove that edge
edge_count[curr_v]--;
adj[curr_v].pop_back();
// Move to next vertex
curr_v = next_v;
}
// back-track to find remaining circuit
else
{
circuit.push_back(curr_v);
// Back-tracking
curr_v = curr_path.top();
curr_path.pop();
}
}
// we've got the circuit, now print it in reverse
for (int i=circuit.size()-1; i>=0; i--)
{
cout << circuit[i];
if (i)
cout<<" -> ";
}
}
// Driver program to check the above function
int main()
{
vector< vector > adj1, adj2;
// Input Graph 1
adj1.resize(3);
// Build the edges
adj1[0].push_back(1);
adj1[1].push_back(2);
adj1[2].push_back(0);
printCircuit(adj1);
cout << endl;
// Input Graph 2
adj2.resize(7);
adj2[0].push_back(1);
adj2[0].push_back(6);
adj2[1].push_back(2);
adj2[2].push_back(0);
adj2[2].push_back(3);
adj2[3].push_back(4);
adj2[4].push_back(2);
adj2[4].push_back(5);
adj2[5].push_back(0);
adj2[6].push_back(4);
printCircuit(adj2);
return 0;
}
Python3
# Python3 program to print Eulerian circuit in given
# directed graph using Hierholzer algorithm
def printCircuit(adj):
# adj represents the adjacency list of
# the directed graph
# edge_count represents the number of edges
# emerging from a vertex
edge_count = dict()
for i in range(len(adj)):
# find the count of edges to keep track
# of unused edges
edge_count[i] = len(adj[i])
if len(adj) == 0:
return # empty graph
# Maintain a stack to keep vertices
curr_path = []
# vector to store final circuit
circuit = []
# start from any vertex
curr_path.append(0)
curr_v = 0 # Current vertex
while len(curr_path):
# If there's remaining edge
if edge_count[curr_v]:
# Push the vertex
curr_path.append(curr_v)
# Find the next vertex using an edge
next_v = adj[curr_v][-1]
# and remove that edge
edge_count[curr_v] -= 1
adj[curr_v].pop()
# Move to next vertex
curr_v = next_v
# back-track to find remaining circuit
else:
circuit.append(curr_v)
# Back-tracking
curr_v = curr_path[-1]
curr_path.pop()
# we've got the circuit, now print it in reverse
for i in range(len(circuit) - 1, -1, -1):
print(circuit[i], end = "")
if i:
print(" -> ", end = "")
# Driver Code
if __name__ == "__main__":
# Input Graph 1
adj1 = [0] * 3
for i in range(3):
adj1[i] = []
# Build the edges
adj1[0].append(1)
adj1[1].append(2)
adj1[2].append(0)
printCircuit(adj1)
print()
# Input Graph 2
adj2 = [0] * 7
for i in range(7):
adj2[i] = []
adj2[0].append(1)
adj2[0].append(6)
adj2[1].append(2)
adj2[2].append(0)
adj2[2].append(3)
adj2[3].append(4)
adj2[4].append(2)
adj2[4].append(5)
adj2[5].append(0)
adj2[6].append(4)
printCircuit(adj2)
print()
# This code is contributed by
# sanjeev2552
Python3
# Python3 program to print Eulerian circuit in given
# directed graph using Hierholzer algorithm
def printCircuit(adj):
# adj represents the adjacency list of
# the directed graph
if len(adj) == 0:
return # empty graph
# Maintain a stack to keep vertices
# We can start from any vertex, here we start with 0
curr_path = [0]
# list to store final circuit
circuit = []
while curr_path:
curr_v = curr_path[-1]
# If there's remaining edge in adjacency list
# of the current vertex
if adj[curr_v]:
# Find and remove the next vertex that is
# adjacent to the current vertex
next_v = adj[curr_v].pop()
# Push the new vertex to the stack
curr_path.append(next_v)
# back-track to find remaining circuit
else:
# Remove the current vertex and
# put it in the circuit
circuit.append(curr_path.pop())
# we've got the circuit, now print it in reverse
for i in range(len(circuit) - 1, -1, -1):
print(circuit[i], end = "")
if i:
print(" -> ", end = "")
# Driver Code
if __name__ == "__main__":
# Input Graph 1
adj1 = [[] for _ in range(3)]
# Build the edges
adj1[0].append(1)
adj1[1].append(2)
adj1[2].append(0)
printCircuit(adj1)
print()
# Input Graph 2
adj2 = [[] for _ in range(7)]
adj2[0].append(1)
adj2[0].append(6)
adj2[1].append(2)
adj2[2].append(0)
adj2[2].append(3)
adj2[3].append(4)
adj2[4].append(2)
adj2[4].append(5)
adj2[5].append(0)
adj2[6].append(4)
printCircuit(adj2)
print()
0 -> 1 -> 2 -> 0
0 -> 6 -> 4 -> 5 -> 0 -> 1 -> 2 -> 3 -> 4 -> 2 -> 0
替代实施:
以下是根据上述代码所做的改进
* 上面的代码记录了每个顶点的边数。这是不必要的,因为我们已经在维护邻接列表。我们简单地删除了 edge_count 数组的创建。在算法中,我们将 `if edge_count[current_v]` 替换为 `if adj[current_v]`
* 上述代码将初始节点两次推入堆栈。尽管他编码结果的方式是正确的,但这种方法令人困惑且效率低下。我们通过将下一个顶点而不是当前顶点附加到堆栈来消除这种情况。
* 在作者测试算法的主体部分,邻接列表`adj1`和`adj2`的初始化有点奇怪。那个药水也得到了改进。
Python3
# Python3 program to print Eulerian circuit in given
# directed graph using Hierholzer algorithm
def printCircuit(adj):
# adj represents the adjacency list of
# the directed graph
if len(adj) == 0:
return # empty graph
# Maintain a stack to keep vertices
# We can start from any vertex, here we start with 0
curr_path = [0]
# list to store final circuit
circuit = []
while curr_path:
curr_v = curr_path[-1]
# If there's remaining edge in adjacency list
# of the current vertex
if adj[curr_v]:
# Find and remove the next vertex that is
# adjacent to the current vertex
next_v = adj[curr_v].pop()
# Push the new vertex to the stack
curr_path.append(next_v)
# back-track to find remaining circuit
else:
# Remove the current vertex and
# put it in the circuit
circuit.append(curr_path.pop())
# we've got the circuit, now print it in reverse
for i in range(len(circuit) - 1, -1, -1):
print(circuit[i], end = "")
if i:
print(" -> ", end = "")
# Driver Code
if __name__ == "__main__":
# Input Graph 1
adj1 = [[] for _ in range(3)]
# Build the edges
adj1[0].append(1)
adj1[1].append(2)
adj1[2].append(0)
printCircuit(adj1)
print()
# Input Graph 2
adj2 = [[] for _ in range(7)]
adj2[0].append(1)
adj2[0].append(6)
adj2[1].append(2)
adj2[2].append(0)
adj2[2].append(3)
adj2[3].append(4)
adj2[4].append(2)
adj2[4].append(5)
adj2[5].append(0)
adj2[6].append(4)
printCircuit(adj2)
print()
0 -> 1 -> 2 -> 0
0 -> 6 -> 4 -> 5 -> 0 -> 1 -> 2 -> 3 -> 4 -> 2 -> 0
时间复杂度: O(V+E)。