Skip to content
Related Articles

Related Articles

Check if a cycle exists between nodes S and T in an Undirected Graph with only S and T repeating | Set – 2

View Discussion
Improve Article
Save Article
Like Article
  • Last Updated : 30 Mar, 2022

Given an undirected graph with N nodes and two vertices S & T, the task is to check if a cycle between these two vertices exists (and return it) or not, such that no other node except S and T appears more than once in that cycle. 

Examples:

Input: N = 7, edges[][] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 2}, {2, 6}, {6, 0}}, S = 0, T = 4

Output: No simple cycle from S to T exists 
Explanation: No simple cycle from S to T exists,  
because node 2 appears two times, in the only cycle that exists between 0 & 4

Input: N = 7,  edges[][] = {{0, 1}, {1, 2}, {1, 6}, {2, 3}, {3, 4}, {4, 5}, {5, 2}, {2, 6}, {6, 0}},, S = 0, T = 4

Output:  0->1->3->4->5->2->6->0
Explanation: The cycle doesn’t repeat any node (except 0)

 

Naive approach: The naive approach of the problem is discussed in Set-1 of this problem.

Efficient Approach: In the naive approach there is checking for all possible paths. The idea in this approach is similar to the Ford Fulkerson algorithm with Edmonds-Karp implementation, but with only 2 BFS. Follow the below steps to solve the problem

  1. First, make a directed graph by duplicating each node (except S and T) in receiver and sender:
    • If the original graph had the edge: {a, b}, the new graph will have {sender_a, receiver_b}
    • The only node that points to a sender is his receiver, so the only edge that ends in sender_v is: {receiver_v, sender_v}
    • The receiver only points to his sender, so the adjacency list of receiver_v is: [sender_v]
  2. Run a BFS to find a path from S to T, and memorize the path back (using a predecessor array).
  3. Invert the edges in the path found, this step is similar to the update of an augmenting path in Ford Fulkerson.
    • While inverting memorize the flow from one node to another in the path
    • So, if the previous node of cur is pred, then flow[cur] = pred
    • Finally, memorize the last node (before the node t), let’s call it: first_node (because it’s the first node, after t, of the flow_path), first_node = flow[t]
  4. Run a BFS again to find the second path, and memorize the path back (using a predecessor array).
  5. Memorize the flow of the second path again:
    • Only mark the flow if there wasn’t a previous flow in an opposite direction, this way two opposite flows will be discarded. Therefore, if flow[pred] == cur don’t do: flow[cur] = pred
    • If the previous node of cur is pred in a path, then flow[cur] = pred
  6. Finally, join the paths:
    • Traverse both paths by the flow, one path starting in first_node and the other flow[t]
    • As we have 2 paths from t to s, by reverting one of them we will have one path from s to t and another from t to s.
    • Traverse one path from s to t, and the other from t to s.

All this work duplicating the graph and registering the flow is done to assure that the same node won’t be traversed twice.

Below is the implementation of the above approach:

Python3




# Python program for the above approach
  
# Auxiliary data struct for the BFS:
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None
  
  
class queue:
    def __init__(self):
        self.head = None
        self.tail = None
  
    def empty(self):
        return self.head == None
  
    def push(self, val):
        if self.head is None:
            self.head = Node(val)
            self.tail = self.head
        else:
            self.tail.next = Node(val)
            self.tail = self.tail.next
  
    def pop(self):
        returned = self.head.val
        self.head = self.head.next
        return returned
  
  
# BFS to find the paths
def bfs(graph, s, t):
  
    # Number of nodes in original graph
    N = len(graph)//2
  
    Q = queue()
    Q.push(s)
  
    predecessor = list(-1 for _ in range(2 * N))
    predecessor[s] = s
  
    while not Q.empty():
        cur = Q.pop()
  
        # Add neighbors to the queue
        for neighbour in graph[cur]:
  
            # If we reach node we found the path
            if neighbour == t or neighbour == t + N:
                predecessor[t] = cur
                predecessor[t + N] = cur
                return predecessor
            # Not seen
            if predecessor[neighbour] == -1:
                Q.push(neighbour)
                predecessor[neighbour] = cur
  
    return None
  
# Invert the path and register flow
  
  
def invert_path(graph, predecessor, flow, s, t):
    N = len(graph)//2
    cur = t
  
    while cur != s:
        pred = predecessor[cur]
  
        if flow[pred] != cur:
            flow[cur] = pred
  
        # Reverse edge
        graph[cur].append(pred)
        graph[pred].remove(cur)
  
        cur = pred
  
    # Node S and T are not duplicated
    # so we don't reverse the edge s->(s + N)
    # because it shouldn't exist
    graph[s].append(s + N)
  
    return flow
  
# Return the path by the flow
def flow_path(flow, first_node, s):
    path = []
    cur = first_node
    while cur != s:
        path.append(cur)
        cur = flow[cur]
  
    return path
  
# Function to get the cyclle with 2 nodes
def cycleWith2Nodes(graph, s = 0, t = 1):
    # Number of nodes in the graph
    N = len(graph)
  
    # Duplicate nodes:
  
    # Adjacency list of sender nodes
    graph += list(graph[node] for node in range(N))
  
    # Adjacency list of receiver nodes
    graph[:N] = list([node + N] for node in range(N))
    # print('duplicated graph:', graph, '\n')
  
    # Find a path from s to t
    predecessor = bfs(graph, s, t)
    if predecessor is not None:
        # List to memorize the flow
        # flow from node v is:
        # flow[v], which gives the node who
        # receives the flow
        flow = list(-1 for _ in range(2 * N))
  
        flow = invert_path(graph, predecessor, flow, s, t)
        first_node = flow[t]
    else:
        print("No cycle")
        return
  
    # Find second path
    predecessor = bfs(graph, s, t)
    if predecessor is not None:
  
        flow = invert_path(graph, predecessor, flow, s, t)
        # Combine both paths:
  
        # From T to S
        path1 = flow_path(flow, first_node, s)
  
        path2 = flow_path(flow, flow[t], s)
        # Reverse the second path
        # so we will  have another path
        # but from s to t
        path2.reverse()
  
        simpleCycle = [s]+path2+[t]+path1+[s]
        print(simpleCycle[::2])
  
    else:
        print("No cycle")
  
  
# Driver Code
if __name__ == "__main__":
    graph = [
        [1, 6],       # 0
        [0, 2, 3],     # 1
        [1, 3, 5, 6],   # 2
        [1, 2, 4],     # 3
        [3, 5],       # 4
        [2, 4],       # 5
        [0, 2],       # 6
  
    ]
    cycleWith2Nodes(graph, s = 0, t = 4)

Output

[0, 6, 2, 5, 4, 3, 1, 0]

Complexity Analysis: Given below are the complexity for each function

  1. Create duplicate graph: O(V+E)
  2. 2 BFS:  O(V+E)
  3. 2 inversions (registering flow) : O(path size) <= O(V+E)
  4. Recreate the paths from the flow array: O(path size) <= O(V+E)
  5. Reverse one path:  O(path size) <= O(V+E)

Time Complexity: O(V+E)
Auxiliary Space: O(N*N), where N is the count of vertices in the graph.


My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!