Open In App

Eulerian Path/Eulerian Circuit in Python

Last Updated : 16 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Eulerian Paths and circuits are fundamental concepts in graph theory, named after the Swiss mathematician Leonard Euler. All paths and circuits along the edges of the graph are executed exactly once. In this article, we’ll delve deeper into understanding Eulerian methods and circuits, and implement an algorithm to identify them in Python.

An Eulerian path is a path in a graph that visits every edge exactly once and an Eulerian circuit is an Eulerian path that starts and ends at the same vertex.

To find an Eulerian path or circuit in a graph, the graph must satisfy certain conditions:

  • Eulerian Circuit: Every vertex has an even degree.
  • Eulerian Path: The graph has exactly two vertices with an odd degree.

Approach to Find Eulerian Path/Circuit in a Graph in Python

1. Check Connectivity:

  • Idea: An Eulerian path or circuit requires the graph to be connected.
  • Steps:
    • Use Depth First Search (DFS) to check if all vertices are reachable from any starting vertex.

2. Count Odd Degree Vertices:

  • Idea: An Eulerian path requires exactly two vertices with an odd degree, while an Eulerian circuit requires all vertices to have an even degree.
  • Steps:
    • Count the degree of each vertex in the graph.
    • Identify vertices with an odd degree.

3. Determine the Starting Vertex:

  • Idea:
    • If an Eulerian path exists, start from one of the odd-degree vertices.
    • If an Eulerian circuit exists, start from any vertex.
  • Steps:
    • Choose a starting vertex based on the above conditions.

4. Traverse the Graph to Find Eulerian Path/Circuit:

  • Idea: Use a stack to keep track of the current path and backtrack when necessary.
  • Steps:
    • Start from the chosen starting vertex.
    • Traverse the graph, visiting each edge exactly once.
    • If there are no remaining edges from the current vertex, backtrack and add the current vertex to the Eulerian path.

Step-by-step approach:

  • Initializes the graph with an empty adjacency list.
  • Adds an edge between vertices u and v.
  • Removes the edge between vertices u and v.
  • Performs DFS from vertex u to mark reachable vertices.
  • Checks if the graph is connected using DFS.
  • Determines if the graph has an Eulerian path or circuit based on vertex degrees.
  • Finds an Eulerian path starting from a vertex.
  • Checks if an edge (u, v) is a bridge by temporarily removing it and using DFS.

Below is the implementation of the above approach:

Python
from collections import defaultdict

class Graph:
    def __init__(self):
        self.graph = defaultdict(list)

    def add_edge(self, u, v):
        self.graph[u].append(v)
        self.graph[v].append(u)

    def remove_edge(self, u, v):
        self.graph[u].remove(v)
        self.graph[v].remove(u)

    def dfs(self, u, visited):
        visited[u] = True
        for v in self.graph[u]:
            if not visited[v]:
                self.dfs(v, visited)

    def is_connected(self):
        visited = {node: False for node in self.graph}
        # Find a vertex with non-zero degree
        for node in self.graph:
            if len(self.graph[node]) > 0:
                break
        # If no vertex with non-zero degree is found, the graph is connected
        if not node:
            return True
        # Start DFS traversal from a vertex with non-zero degree
        self.dfs(node, visited)
        # Check if all non-zero degree vertices are visited
        for node in self.graph:
            if not visited[node] and len(self.graph[node]) > 0:
                return False
        return True

    def is_eulerian(self):
        if not self.is_connected():
            return False
        # Count vertices with odd degree
        odd_degree_count = sum(1 for node in self.graph if len(self.graph[node]) % 2 != 0)
        if odd_degree_count == 0:
            return 'Eulerian Circuit'
        elif odd_degree_count == 2:
            return 'Eulerian Path'
        else:
            return False

    def eulerian_path(self):
        euler_path = []
        start_vertex = next(iter(self.graph))
        current_vertex = start_vertex
        while self.graph[current_vertex]:
            next_vertex = self.graph[current_vertex][0]
            if len(self.graph[current_vertex]) == 1:
                # If the current vertex has only one adjacent vertex
                euler_path.append((current_vertex, next_vertex))
                self.remove_edge(current_vertex, next_vertex)
            else:
                # If there are multiple adjacent vertices
                for vertex in self.graph[current_vertex]:
                    if not self.is_bridge(current_vertex, vertex):
                        next_vertex = vertex
                        break
                euler_path.append((current_vertex, next_vertex))
                self.remove_edge(current_vertex, next_vertex)
            current_vertex = next_vertex
        return euler_path

    def is_bridge(self, u, v):
        # Remove edge (u, v) and check if the graph becomes disconnected
        self.remove_edge(u, v)
        visited = {node: False for node in self.graph}
        self.dfs(u, visited)
        # Add edge (u, v) back
        self.add_edge(u, v)
        return not visited[v]

# Example usage:
g = Graph()
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 3)
g.add_edge(2, 4)
g.add_edge(3, 3)

euler_type = g.is_eulerian()
if euler_type:
    print("Graph contains an", euler_type)
    if euler_type == 'Eulerian Path':
        print("Eulerian Path:", g.eulerian_path())
elif euler_type is False:
    print("Graph doesn't contain an Eulerian Path or Circuit")

Output
('Graph contains an', 'Eulerian Path')
('Eulerian Path:', [(0, 1), (1, 2), (2, 0)])

Time Complexity: O(V+E)
Auxiliary Space: O(V)



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads