📜  有向图的 Hierholzer 算法

📅  最后修改于: 2022-05-13 01:57:54.460000             🧑  作者: Mango

有向图的 Hierholzer 算法

给定一个有向欧拉图,打印一个欧拉回路。欧拉回路是一条遍历图的每一条边的路径,路径结束于起始顶点。

例子:

输入:下图的邻接列表欧拉1输出:0 -> 1 -> 2 -> 0 输入:下图的邻接表欧拉2输出 : 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,并将以此方式形成的游览加入到以前的巡回演出。

因此,我们的想法是继续跟踪未使用的边缘并移除它们,直到我们被卡住。一旦我们卡住了,我们就回溯到当前路径中最近的具有未使用边的顶点,然后重复这个过程,直到所有边都被使用。我们可以使用另一个容器来维护最终路径。

举个例子:

设初始有向图如下欧拉-3让我们从 0 开始我们的路径。因此,curr_path = {0} 和 circuit = {} 现在让我们使用边 0->1 欧拉-4现在,curr_path = {0,1} 和 circuit = {} 同样我们达到 2 然后再次达到 0 Euler-5现在,curr_path = {0,1,2} 和 circuit = {} 然后我们转到 0,现在由于 0 没有任何未使用的边缘,我们将 0 放入电路并返回轨道直到找到边缘欧拉-6然后我们有 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)。