Number of paths from source to destination in a directed acyclic graph
Last Updated :
14 Feb, 2023
Given a Directed Acyclic Graph with n vertices and m edges. The task is to find the number of different paths that exist from a source vertex to destination vertex.
Examples:
Input: source = 0, destination = 4
Output: 3
Explanation:
0 -> 2 -> 3 -> 4
0 -> 3 -> 4
0 -> 4
Input: source = 0, destination = 1
Output: 1
Explanation: There exists only one path 0->1
Approach: Let f(u) be the number of ways one can travel from node u to destination vertex. Hence, f(source) is required answer. As f(destination) = 1 here so there is just one path from destination to itself. One can observe, f(u) depends on nothing other than the f values of all the nodes which are possible to travel from u. It makes sense because the number of different paths from u to the destination is the sum of all different paths from v1, v2, v3… v-n to destination vertex where v1 to v-n are all the vertices that have a direct path from vertex u. This approach, however, is too slow to be useful. Each function call branches out into further calls, and that branches into further calls, until each and every path is explored once.
The problem with this approach is the calculation of f(u) again and again each time the function is called with argument u. Since this problem exhibits both overlapping subproblems and optimal substructure, dynamic programming is applicable here. In order to evaluate f(u) for each u just once, evaluate f(v) for all v that can be visited from u before evaluating f(u). This condition is satisfied by reverse topological sorted order of the nodes of the graph.
Below is the implementation of the above approach:
C++
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1000005
vector< int > v[MAXN];
void add_edge( int a, int b, int fre[])
{
v[a].push_back(b);
fre[b]++;
}
vector< int > topological_sorting( int fre[], int n)
{
queue< int > q;
for ( int i = 0; i < n; i++)
if (!fre[i])
q.push(i);
vector< int > l;
while (!q.empty()) {
int u = q.front();
q.pop();
l.push_back(u);
for ( int i = 0; i < v[u].size(); i++) {
fre[v[u][i]]--;
if (!fre[v[u][i]])
q.push(v[u][i]);
}
}
return l;
}
int numberofPaths( int source, int destination, int n, int fre[])
{
vector< int > s = topological_sorting(fre, n);
int dp[n] = { 0 };
dp[destination] = 1;
for ( int i = s.size() - 1; i >= 0; i--) {
for ( int j = 0; j < v[s[i]].size(); j++) {
dp[s[i]] += dp[v[s[i]][j]];
}
}
return dp;
}
int main()
{
int n = 5;
int source = 0, destination = 4;
int fre[n] = { 0 };
add_edge(0, 1, fre);
add_edge(0, 2, fre);
add_edge(0, 3, fre);
add_edge(0, 4, fre);
add_edge(2, 3, fre);
add_edge(3, 4, fre);
cout << numberofPaths(source, destination, n, fre);
}
|
Java
import java.util.*;
class GFG{
static final int MAXN = 1000005 ;
static Vector<Integer> []v = new Vector[MAXN];
static {
for ( int i = 0 ; i < v.length; i++)
v[i] = new Vector<Integer>();
}
static void add_edge( int a, int b, int fre[])
{
v[a].add(b);
fre[b]++;
}
static Vector<Integer> topological_sorting( int fre[], int n)
{
Queue<Integer> q = new LinkedList<Integer>();
for ( int i = 0 ; i < n; i++)
if (fre[i]== 0 )
q.add(i);
Vector<Integer> l = new Vector<Integer>();
while (!q.isEmpty()) {
int u = q.peek();
q.remove();
l.add(u);
for ( int i = 0 ; i < v[u].size(); i++) {
fre[v[u].get(i)]--;
if (fre[v[u].get(i)]== 0 )
q.add(v[u].get(i));
}
}
return l;
}
static int numberofPaths( int source, int destination, int n, int fre[])
{
Vector<Integer> s = topological_sorting(fre, n);
int dp[] = new int [n];
dp[destination] = 1 ;
for ( int i = s.size() - 1 ; i >= 0 ; i--) {
for ( int j = 0 ; j < v[s.get(i)].size(); j++) {
dp[s.get(i)] += dp[v[s.get(i)].get(j)];
}
}
return dp;
}
public static void main(String[] args)
{
int n = 5 ;
int source = 0 , destination = 4 ;
int fre[] = new int [n];
add_edge( 0 , 1 , fre);
add_edge( 0 , 2 , fre);
add_edge( 0 , 3 , fre);
add_edge( 0 , 4 , fre);
add_edge( 2 , 3 , fre);
add_edge( 3 , 4 , fre);
System.out.print(numberofPaths(source, destination, n, fre));
}
}
|
Python3
from collections import deque
MAXN = 1000005
v = [[] for i in range (MAXN)]
def add_edge(a, b, fre):
v[a].append(b)
fre[b] + = 1
def topological_sorting(fre, n):
q = deque()
for i in range (n):
if ( not fre[i]):
q.append(i)
l = []
while ( len (q) > 0 ):
u = q.popleft()
l.append(u)
for i in range ( len (v[u])):
fre[v[u][i]] - = 1
if ( not fre[v[u][i]]):
q.append(v[u][i])
return l
def numberofPaths(source, destination, n, fre):
s = topological_sorting(fre, n)
dp = [ 0 ] * n
dp[destination] = 1
for i in range ( len (s) - 1 , - 1 , - 1 ):
for j in range ( len (v[s[i]])):
dp[s[i]] + = dp[v[s[i]][j]]
return dp
if __name__ = = '__main__' :
n = 5
source, destination = 0 , 4
fre = [ 0 ] * n
add_edge( 0 , 1 , fre)
add_edge( 0 , 2 , fre)
add_edge( 0 , 3 , fre)
add_edge( 0 , 4 , fre)
add_edge( 2 , 3 , fre)
add_edge( 3 , 4 , fre)
print (numberofPaths(source, destination, n, fre))
|
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class GFG {
static readonly int MAXN = 1000005;
static List< int >[] v = new List< int >[ MAXN ];
static int [] fre = new int [MAXN];
static GFG()
{
for ( int i = 0; i < v.Length; i++)
v[i] = new List< int >();
}
static void add_edge( int a, int b)
{
v[a].Add(b);
fre[b]++;
}
static List< int > topological_sorting( int n)
{
Queue< int > q = new Queue< int >();
for ( int i = 0; i < n; i++)
if (fre[i] == 0)
q.Enqueue(i);
List< int > l = new List< int >();
while (q.Count > 0) {
int u = q.Peek();
q.Dequeue();
l.Add(u);
for ( int i = 0; i < v[u].Count; i++) {
fre[v[u][i]]--;
if (fre[v[u][i]] == 0)
q.Enqueue(v[u][i]);
}
}
return l;
}
static int numberofPaths( int source, int destination,
int n)
{
List< int > s = topological_sorting(n);
int [] dp = new int [n];
dp[destination] = 1;
for ( int i = s.Count - 1; i >= 0; i--) {
for ( int j = 0; j < v[s[i]].Count; j++) {
dp[s[i]] += dp[v[s[i]][j]];
}
}
return dp;
}
static void Main( string [] args)
{
int n = 5;
int source = 0, destination = 4;
add_edge(0, 1);
add_edge(0, 2);
add_edge(0, 3);
add_edge(0, 4);
add_edge(2, 3);
add_edge(3, 4);
Console.WriteLine(
numberofPaths(source, destination, n));
}
}
|
Javascript
const MAXN = 1000005;
let v = Array.from({length: MAXN}, () => []);
function add_edge(a, b, fre) {
v[a].push(b);
fre[b] += 1;
}
function topological_sorting(fre, n) {
let q = [];
for (let i = 0; i < n; i++) {
if (!fre[i]) {
q.push(i);
}
}
let l = [];
while (q.length > 0) {
let u = q.shift();
l.push(u);
for (let i = 0; i < v[u].length; i++) {
fre[v[u][i]] -= 1;
if (!fre[v[u][i]]) {
q.push(v[u][i]);
}
}
}
return l;
}
function numberofPaths(source, destination, n, fre) {
let s = topological_sorting(fre, n);
let dp = Array(n).fill(0);
dp[destination] = 1;
for (let i = s.length - 1; i >= 0; i--) {
for (let j = 0; j < v[s[i]].length; j++) {
dp[s[i]] += dp[v[s[i]][j]];
}
}
return dp;
}
let n = 5;
let source = 0, destination = 4;
let fre = Array(n).fill(0);
add_edge(0, 1, fre);
add_edge(0, 2, fre);
add_edge(0, 3, fre);
add_edge(0, 4, fre);
add_edge(2, 3, fre);
add_edge(3, 4, fre);
console.log(numberofPaths(source, destination, n, fre));
|
Time complexity : O(V+E), where V is the number of vertices in the graph and E is the number of edges. This is because the topological sorting takes O(V+E) time and the reverse traversal of the sorted vertices takes O(V) time.
Space complexity : O(V), as the frequency array fre and the dynamic programming array dp both use O(V) space, and the vector of vectors v takes O(V+E) space.
Method 2 : ( Top down dp)
Let us consider the graph below
Let us consider source as 0 ( zero ) and destination as 4 . Now we need to find the number of ways to reach 4 from the source i.e., 0 . One of the basic intuition is that if we are already at the destination we have found 1 valid path . Let us consider that our source and destination are different as of now we don’t know in how many ways we can reach from source to destination . But if there exists some neighbours for source then if the neighbours can reach the destination via some path then in all of these paths we can just append source to get the number of ways to reach the destination from source .
If we can visualise it :
In order to compute the number of ways to reach from source to destination i.e., source to destination . If the neighbours of source i.e., 0 can reach the destination ( 4 ) via some path , then we can just append the source to get the number of ways that the source can reach the destination .
source ( 0 ) neighbours are 4 , 3 , 2
4 is the destination so we have found 1 valid path . So in order to get the path from source we can just append the source in front of destination i.e., 0 -> 4 .
The number of ways 3 can reach the 4 is 3 -> 4 is the only possible way . In this case we can just append source to get the number of ways to reach the destination from source via 3 i.e., 0 -> 3 -> 4 . This is one more possible path .
The number of ways 2 can reach the 4 is 2 -> 3 -> 4 is the only possible way . Now we can just append the source to get the path from source to destination i.e., 0 -> 2 -> 3 -> 4 .
We have found 3 possible ways to reach the destination from source . But we can see there are some overlapping of sub – problems i.e., when we are computing the answer for 2 we are exploring the path of 3 which we have already computed . In order to avoid this we can just store the result of every vertex ones we have computed the answer to it , So that it will help us to avoid computing the solution of similar sub – problems again and again . There comes the intuition of dynamic programming .
Approach :
- If we are already at the destination we have found one valid path .
- If we have not reached the destination , then the number of ways to reach the destination from the current vertex depends on the number of ways the neighbours can reach the destination . We sum all the ways and store it in the dp array .
- If we have already computed the result of any vertex we return the answer directly . In order to identify that we have not computed the answer for any vertex we initialise the dp array with -1 ( indicates we have not computed the answer for that vertex ) .
- If the neighbours of any vertex are unable to reach the destination we return -1 to indicate that there is no path .
- If the number of ways are really very large we can module it with 10^9 + 7 and store the result .
Below is the C++ implementation
C++
#include <bits/stdc++.h>
using namespace std;
long dp[10006];
long mod = 1e9 + 7;
long countPaths(vector<vector< long > >& arr, long s, long d)
{
if (s == d)
return 1;
if (dp[s] != -1)
return dp[s];
long c = 0;
for ( long & neigh : arr[s]) {
long x = countPaths(arr, neigh, d);
if (x != -1) {
c = (c % mod + x % mod) % mod;
}
}
return (dp[s] = (c == 0) ? -1 : c);
}
long Possible_Paths(vector<vector< long > >& arr, long n,
long s, long d)
{
memset (dp, -1, sizeof dp);
long c = countPaths(arr, s, d);
if (c == -1)
return 0;
return c;
}
int main()
{
long n, m, s, d;
n = 5, m = 6, s = 0, d = 4;
vector<vector< long > > arr(n + 1);
arr[0].push_back(1);
arr[0].push_back(2);
arr[0].push_back(3);
arr[0].push_back(4);
arr[2].push_back(3);
arr[3].push_back(4);
cout << Possible_Paths(arr, n, s, d) << endl;
return 0;
}
|
Java
import java.util.*;
public class Main {
static long dp[] = new long [ 10006 ];
static long mod = ( long )1e9 + 7 ;
public static long countPaths(List<List<Long>> arr, long s, long d) {
if (s == d)
return 1 ;
if (dp[( int )s] != - 1 )
return dp[( int )s];
long c = 0 ;
for ( long neigh : arr.get(( int )s)) {
long x = countPaths(arr, neigh, d);
if (x != - 1 ) {
c = (c % mod + x % mod) % mod;
}
}
return (dp[( int )s] = (c == 0 ) ? - 1 : c);
}
public static long Possible_Paths(List<List<Long>> arr, long n,
long s, long d) {
Arrays.fill(dp, - 1 );
long c = countPaths(arr, s, d);
if (c == - 1 )
return 0 ;
return c;
}
public static void main(String[] args) {
long n, m, s, d;
n = 5 ;
m = 6 ;
s = 0 ;
d = 4 ;
List<List<Long>> arr = new ArrayList<>();
for ( int i = 0 ; i <= n; i++) {
arr.add( new ArrayList<>());
}
arr.get( 0 ).add(1l);
arr.get( 0 ).add(2l);
arr.get( 0 ).add(3l);
arr.get( 0 ).add(4l);
arr.get( 2 ).add(3l);
arr.get( 3 ).add(4l);
System.out.println(Possible_Paths(arr, n, s, d));
}
}
|
Python3
dp = [ 0 ] * 10006
mod = 1e9 + 7
def countPaths(arr,s,d):
if (s = = d):
return 1
if (dp[s]! = - 1 ):
return dp[s]
c = 0
for neigh in arr[s]:
x = countPaths(arr,neigh,d)
if (x! = - 1 ):
c = (c % mod + x % mod) % mod
dp[s] = - 1 if (c = = 0 ) else int (c)
return dp[s]
def Possible_Paths(arr,n,s,d):
for i in range ( len (dp)):
dp[i] = - 1
c = countPaths(arr,s,d)
if (c = = - 1 ):
return 0
else :
return c
n,m,s,d = 5 , 6 , 0 , 4
arr = [[] for i in range (n + 1 )]
arr[ 0 ].append( 1 )
arr[ 0 ].append( 2 )
arr[ 0 ].append( 3 )
arr[ 0 ].append( 4 )
arr[ 2 ].append( 3 )
arr[ 3 ].append( 4 )
print (Possible_Paths(arr,n,s,d))
|
C#
using System;
using System.Linq;
using System.Collections.Generic;
class MainClass {
static long mod = ( long )Math.Pow(10, 9) + 7;
static long [] dp = new long [10006];
static long CountPaths(List<List< long >> arr, long s, long d) {
if (s == d) {
return 1;
}
if (dp[s] != -1) {
return dp[s];
}
long c = 0;
foreach ( long neigh in arr[( int )s]) {
long x = CountPaths(arr, neigh, d);
if (x != -1) {
c = (c % mod + x % mod) % mod;
}
}
return (dp[s] = (c == 0) ? -1 : c);
}
static long PossiblePaths(List<List< long >> arr, long n, long s, long d) {
for ( int i = 0; i < 10006; i++) {
dp[i] = -1;
}
long c = CountPaths(arr, s, d);
if (c == -1) {
return 0;
}
return c;
}
public static void Main( string [] args) {
long n, m, s, d;
n = 5; m = 6; s = 0; d = 4;
List<List< long >> arr = Enumerable.Range(0, ( int )n + 1).Select(i => new List< long >()).ToList();
arr[0].Add(1);
arr[0].Add(2);
arr[0].Add(3);
arr[0].Add(4);
arr[2].Add(3);
arr[3].Add(4);
Console.WriteLine(PossiblePaths(arr, n, s, d));
}
}
|
Javascript
let dp = new Array(10006);
let mod = 1e9 + 7;
function countPaths(arr, s, d)
{
if (s == d)
return 1;
if (dp[s] != -1)
return dp[s];
let c = 0;
for (let neigh in arr[s]) {
let x = countPaths(arr, arr[s][neigh], d);
if (x != -1) {
c = (c % mod + x % mod) % mod;
}
}
return (dp[s] = (c == 0) ? -1 : c);
}
function Possible_Paths(arr, n, s, d)
{
for (let i=0;i<dp.length;i++)
{
dp[i]=-1;
}
let c = countPaths(arr, s, d);
if (c == -1)
return 0;
return c;
}
let n = 5;
let m = 6;
let s = 0;
let d = 4;
let arr=[];
for (let i = 0; i < n; i++) {
arr[i] = [];
}
arr[0].push(1);
arr[0].push(2);
arr[0].push(3);
arr[0].push(4);
arr[2].push(3);
arr[3].push(4);
console.log(Possible_Paths(arr, n, s, d));
|
Time complexity : O ( V + E ) where V are the vertices and E are the edges .
Space complexity : O ( V + E + V ) where O ( V + E ) for adjacency list and O ( V ) for dp array .
Like Article
Suggest improvement
Share your thoughts in the comments
Please Login to comment...