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 & 4Input: 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
- 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]
- Run a BFS to find a path from S to T, and memorize the path back (using a predecessor array).
-
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]
- Run a BFS again to find the second path, and memorize the path back (using a predecessor array).
- 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
- 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:
# 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 )
|
import java.util.*;
public class CycleBetweenVertices {
static int N;
static List<Integer>[] graph;
static int [] parent;
static boolean [] visited;
static boolean foundCycle;
static List<Integer> cycle;
static int S, T;
public static List<Integer> findCycle( int n, int [][] edges, int s, int t) {
N = n;
graph = new List[N];
parent = new int [N];
visited = new boolean [N];
foundCycle = false ;
cycle = new ArrayList<>();
S = s;
T = t;
for ( int i = 0 ; i < N; i++) {
graph[i] = new ArrayList<>();
parent[i] = - 1 ;
}
for ( int [] edge : edges) {
int u = edge[ 0 ];
int v = edge[ 1 ];
graph[u].add(v);
graph[v].add(u);
}
dfs(S, - 1 );
if (!foundCycle) {
return new ArrayList<>();
}
Collections.reverse(cycle);
return cycle;
}
public static boolean dfs( int u, int p) {
visited[u] = true ;
parent[u] = p;
for ( int v : graph[u]) {
if (foundCycle) {
return true ;
}
if (!visited[v]) {
if (dfs(v, u)) {
return true ;
}
} else if (v != p && (v == S || v == T)) {
foundCycle = true ;
cycle.add(v);
for ( int i = u; i != v; i = parent[i]) {
cycle.add(i);
}
cycle.add(v);
return true ;
}
}
return false ;
}
public static void main(String[] args) {
int n = 7 ;
int [][] edges = {{ 0 , 1 }, { 1 , 2 }, { 1 , 6 }, { 2 , 3 }, { 3 , 4 }, { 4 , 5 }, { 5 , 2 }, { 2 , 6 }, { 6 , 0 }};
int s = 0 ;
int t = 4 ;
List<Integer> cycle = findCycle(n, edges, s, t);
if (cycle.isEmpty()) {
System.out.println( "No cycle exists between " + s + " and " + t + "." );
} else {
System.out.print( "Cycle between " + s + " and " + t + ": " );
for ( int i = 0 ; i < cycle.size(); i++) {
System.out.print(cycle.get(i));
if (i < cycle.size() - 1 ) {
System.out.print( " -> " );
}
}
System.out.println();
}
}
} //This code is contributed by Akash Jha |
#include <bits/stdc++.h> using namespace std;
int N;
vector< int > graph[10001];
int parent[10001];
bool visited[10001];
bool foundCycle;
vector< int > cycle;
int S, T;
bool dfs( int u, int p);
vector< int > findCycle( int n, vector<vector< int >>& edges, int s, int t) {
N = n;
foundCycle = false ;
cycle.clear();
S = s;
T = t;
for ( int i = 0; i < N; i++) {
graph[i].clear();
parent[i] = -1;
visited[i] = false ;
}
for ( auto edge : edges) {
int u = edge[0];
int v = edge[1];
graph[u].push_back(v);
graph[v].push_back(u);
}
dfs(S, -1);
if (!foundCycle) {
return vector< int >();
}
reverse(cycle.begin(), cycle.end());
return cycle;
} bool dfs( int u, int p) {
visited[u] = true ;
parent[u] = p;
for ( auto v : graph[u]) {
if (foundCycle) {
return true ;
}
if (!visited[v]) {
if (dfs(v, u)) {
return true ;
}
} else if (v != p && (v == S || v == T)) {
foundCycle = true ;
cycle.push_back(v);
for ( int i = u; i != v; i = parent[i]) {
cycle.push_back(i);
}
cycle.push_back(v);
return true ;
}
}
return false ;
} int main() {
int n = 7;
vector<vector< int >> edges = {{0, 1}, {1, 2}, {1, 6}, {2, 3}, {3, 4}, {4, 5}, {5, 2}, {2, 6}, {6, 0}};
int s = 0;
int t = 4;
vector< int > cycle = findCycle(n, edges, s, t);
if (cycle.empty()) {
cout << "No cycle exists between " << s << " and " << t << "." << endl;
} else {
cout << "Cycle between " << s << " and " << t << ": " ;
for ( int i = 0; i < cycle.size(); i++) {
cout << cycle[i];
if (i < cycle.size() - 1) {
cout << " -> " ;
}
}
cout << endl;
}
return 0;
} //This code is contributed by Akash Jha |
using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static int N;
static List< int >[] graph;
static int [] parent;
static bool [] visited;
static bool foundCycle;
static List< int > cycle;
static int S, T;
static bool dfs( int u, int p) {
visited[u] = true ;
parent[u] = p;
foreach ( int v in graph[u]) {
if (foundCycle) {
return true ;
}
if (!visited[v]) {
if (dfs(v, u)) {
return true ;
}
} else if (v != p && (v == S || v == T)) {
foundCycle = true ;
cycle.Add(v);
for ( int i = u; i != v; i = parent[i]) {
cycle.Add(i);
}
cycle.Add(v);
return true ;
}
}
return false ;
}
static List< int > FindCycle( int n, int [][] edges, int s, int t) {
N = n;
foundCycle = false ;
cycle = new List< int >();
S = s;
T = t;
graph = new List< int >[N];
parent = new int [N];
visited = new bool [N];
for ( int i = 0; i < N; i++) {
graph[i] = new List< int >();
parent[i] = -1;
visited[i] = false ;
}
foreach ( var edge in edges) {
int u = edge[0];
int v = edge[1];
graph[u].Add(v);
graph[v].Add(u);
}
dfs(S, -1);
if (!foundCycle) {
return new List< int >();
}
cycle.Reverse();
return cycle;
}
static void Main( string [] args) {
int n = 7;
int [][] edges = new int [][] {
new int [] {0, 1},
new int [] {1, 2},
new int [] {1, 6},
new int [] {2, 3},
new int [] {3, 4},
new int [] {4, 5},
new int [] {5, 2},
new int [] {2, 6},
new int [] {6, 0},
};
int s = 0;
int t = 4;
List< int > cycle = FindCycle(n, edges, s, t);
if (cycle.Count == 0) {
Console.WriteLine($ "No cycle exists between {s} and {t}." );
} else {
Console.Write($ "Cycle between {s} and {t}: " );
Console.WriteLine( string .Join( " -> " , cycle));
}
}
} //This code is contributed by AKash Jha |
let N; let graph = new Array(10001).fill().map(() => []);
let parent = new Array(10001).fill(-1);
let visited = new Array(10001).fill( false );
let foundCycle; let cycle; let S, T; function findCycle(n, edges, s, t) {
N = n;
foundCycle = false ;
cycle = [];
S = s;
T = t;
for (let i = 0; i < N; i++) {
graph[i].length = 0;
parent[i] = -1;
visited[i] = false ;
}
for (let edge of edges) {
let u = edge[0];
let v = edge[1];
graph[u].push(v);
graph[v].push(u);
}
dfs(S, -1);
if (!foundCycle) {
return [];
}
cycle.reverse();
return cycle;
} function dfs(u, p) {
visited[u] = true ;
parent[u] = p;
for (let v of graph[u]) {
if (foundCycle) {
return true ;
}
if (!visited[v]) {
if (dfs(v, u)) {
return true ;
}
} else if (v !== p && (v === S || v === T)) {
foundCycle = true ;
cycle.push(v);
for (let i = u; i !== v; i = parent[i]) {
cycle.push(i);
}
cycle.push(v);
return true ;
}
}
return false ;
} let n = 7; let edges = [[0, 1], [1, 2], [1, 6], [2, 3], [3, 4], [4, 5], [5, 2], [2, 6], [6, 0]]; let s = 0; let t = 4; let cycleResult = findCycle(n, edges, s, t); if (cycleResult.length === 0) {
console.log( "No cycle exists between " + s + " and " + t + "." );
} else {
console.log( "Cycle between " + s + " and " + t + ": " );
for (let i = 0; i < cycleResult.length; i++) {
console.log(cycleResult[i]);
if (i < cycleResult.length - 1) {
console.log( " -> " );
}
}
console.log();
} //This code is contributed by Akash Jha |
[0, 6, 2, 5, 4, 3, 1, 0]
Complexity Analysis: Given below are the complexity for each function
- Create duplicate graph: O(V+E)
- 2 BFS: O(V+E)
- 2 inversions (registering flow) : O(path size) <= O(V+E)
- Recreate the paths from the flow array: O(path size) <= O(V+E)
- 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.