Número de rutas desde el origen hasta el destino en un gráfico acíclico dirigido

Dado un Grafo Acíclico Dirigido con n vértices y m aristas. La tarea es encontrar el número de caminos diferentes que existen desde un vértice de origen hasta el vértice de destino. 

Ejemplos: 

Entrada: origen = 0, destino = 4 
Salida:
Explicación: 
0 -> 2 -> 3 -> 4 
0 -> 3 -> 4 
0 -> 4 

Entrada: fuente = 0, destino = 1 
Salida:
Explicación: Solo existe una ruta 0->1  

Enfoque: Sea f(u) el número de formas en que uno puede viajar desde el Node u hasta el vértice de destino . Por lo tanto, f (fuente) es una respuesta obligatoria. Como f (destino) = 1 aquí, solo hay un camino desde el destino hasta sí mismo. Se puede observar que f(u) no depende de nada más que de los valores f de todos los Nodes que son posibles de viajar desde u. Tiene sentido porque el número de caminos diferentes desde u hasta el destino es la suma de todos los caminos diferentes desde v1, v2, v3… vn hasta el vértice de destino donde v1 a vn son todos los vértices que tienen un camino directo desde el vértice u. Este enfoque, sin embargo, es demasiado lento para ser útil. Cada llamada de función se ramifica en más llamadas, y eso se ramifica en más llamadas, hasta que todas y cada una de las rutas se exploran una vez. 

El problema con este enfoque es el cálculo de f(u) una y otra vez cada vez que se llama a la función con el argumento u. Dado que este problema exhibe tanto subproblemas superpuestos como una subestructura óptima , aquí se aplica la programación dinámica . Para evaluar f(u) para cada u solo una vez, evalúe f(v) para todos los v que se pueden visitar desde u antes de evaluar f(u). Esta condición se cumple mediante el orden clasificado topológico inverso de los Nodes del gráfico. 

A continuación se muestra la implementación del enfoque anterior:  

C++

// C++ program for Number of paths
// from one vertex to another vertex
//  in a Directed Acyclic Graph
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1000005
 
// to make graph
vector<int> v[MAXN];
 
// function to add edge in graph
void add_edge(int a, int b, int fre[])
{
    // there is path from a to b.
    v[a].push_back(b);
    fre[b]++;
}
 
// function to make topological sorting
vector<int> topological_sorting(int fre[], int n)
{
    queue<int> q;
 
    // insert all vertices which
    // don't have any parent.
    for (int i = 0; i < n; i++)
        if (!fre[i])
            q.push(i);
 
    vector<int> l;
 
    // using kahn's algorithm
    // for topological sorting
    while (!q.empty()) {
        int u = q.front();
        q.pop();
 
        // insert front element of queue to vector
        l.push_back(u);
 
        // go through all it's childs
        for (int i = 0; i < v[u].size(); i++) {
            fre[v[u][i]]--;
 
            // whenever the frequency is zero then add
            // this vertex to queue.
            if (!fre[v[u][i]])
                q.push(v[u][i]);
        }
    }
    return l;
}
 
// Function that returns the number of paths
int numberofPaths(int source, int destination, int n, int fre[])
{
 
// make topological sorting
    vector<int> s = topological_sorting(fre, n);
 
    // to store required answer.
    int dp[n] = { 0 };
 
    // answer from destination
    // to destination is 1.
    dp[destination] = 1;
 
    // traverse in reverse order
    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;
}
 
// Driver code
int main()
{
 
    // here vertices are numbered from 0 to n-1.
    int n = 5;
    int source = 0, destination = 4;
 
    // to count number of vertex which don't
    // have any parents.
    int fre[n] = { 0 };
 
    // to add all edges of graph
    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);
 
    // Function that returns the number of paths
    cout << numberofPaths(source, destination, n, fre);
}

Python3

# Python3 program for Number of paths
# from one vertex to another vertex
# in a Directed Acyclic Graph
from collections import deque
MAXN = 1000005
 
# to make graph
v = [[] for i in range(MAXN)]
 
# function to add edge in graph
def add_edge(a, b, fre):
   
    # there is path from a to b.
    v[a].append(b)
    fre[b] += 1
 
# function to make topological sorting
def topological_sorting(fre, n):
    q = deque()
 
    # insert all vertices which
    # don't have any parent.
    for i in range(n):
        if (not fre[i]):
            q.append(i)
 
    l = []
 
    # using kahn's algorithm
    # for topological sorting
    while (len(q) > 0):
        u = q.popleft()
        #q.pop()
 
        # insert front element of queue to vector
        l.append(u)
 
        # go through all it's childs
        for i in range(len(v[u])):
            fre[v[u][i]] -= 1
 
            # whenever the frequency is zero then add
            # this vertex to queue.
            if (not fre[v[u][i]]):
                q.append(v[u][i])
    return l
 
# Function that returns the number of paths
def numberofPaths(source, destination, n, fre):
 
# make topological sorting
    s = topological_sorting(fre, n)
 
    # to store required answer.
    dp = [0]*n
 
    # answer from destination
    # to destination is 1.
    dp[destination] = 1
 
    # traverse in reverse order
    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
 
# Driver code
if __name__ == '__main__':
 
    # here vertices are numbered from 0 to n-1.
    n = 5
    source, destination = 0, 4
 
    # to count number of vertex which don't
    # have any parents.
    fre = [0]*n
 
    # to add all edges of graph
    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)
 
    # Function that returns the number of paths
    print (numberofPaths(source, destination, n, fre))
 
# This code is contributed by mohit kumar 29.

Java

// Java program for Number of paths
// from one vertex to another vertex
//  in a Directed Acyclic Graph
 
import java.util.*;
 
class GFG{
static final int MAXN = 1000005;
 
// to make graph
static Vector<Integer> []v = new Vector[MAXN];
static {
    for (int i = 0; i < v.length; i++)
        v[i] = new Vector<Integer>();
}
// function to add edge in graph
static void add_edge(int a, int b, int fre[])
{
    // there is path from a to b.
    v[a].add(b);
    fre[b]++;
}
 
// function to make topological sorting
static Vector<Integer> topological_sorting(int fre[], int n)
{
    Queue<Integer> q = new LinkedList<Integer>();
 
    // insert all vertices which
    // don't have any parent.
    for (int i = 0; i < n; i++)
        if (fre[i]==0)
            q.add(i);
 
    Vector<Integer> l = new Vector<Integer>();
 
    // using kahn's algorithm
    // for topological sorting
    while (!q.isEmpty()) {
        int u = q.peek();
        q.remove();
 
        // insert front element of queue to vector
        l.add(u);
 
        // go through all it's childs
        for (int i = 0; i < v[u].size(); i++) {
            fre[v[u].get(i)]--;
 
            // whenever the frequency is zero then add
            // this vertex to queue.
            if (fre[v[u].get(i)]==0)
                q.add(v[u].get(i));
        }
    }
    return l;
}
 
// Function that returns the number of paths
static int numberofPaths(int source, int destination, int n, int fre[])
{
 
// make topological sorting
    Vector<Integer> s = topological_sorting(fre, n);
 
    // to store required answer.
    int dp[] = new int[n];
 
    // answer from destination
    // to destination is 1.
    dp[destination] = 1;
 
    // traverse in reverse order
    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;
}
 
// Driver code
public static void main(String[] args)
{
 
    // here vertices are numbered from 0 to n-1.
    int n = 5;
    int source = 0, destination = 4;
 
    // to count number of vertex which don't
    // have any parents.
    int fre[] = new int[n];
 
    // to add all edges of graph
    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);
 
    // Function that returns the number of paths
    System.out.print(numberofPaths(source, destination, n, fre));
}
}
// This code contributed by shikhasingrajput
Producción

3

Método 2: (De arriba hacia abajo dp)

Consideremos el siguiente gráfico

Consideremos el origen como 0 ( cero ) y el destino como 4 . Ahora necesitamos encontrar el número de formas de llegar a 4 desde la fuente, es decir, 0. Una de las intuiciones básicas es que si ya estamos en el destino hemos encontrado 1 camino válido. Consideremos que nuestro origen y destino son diferentes a partir de ahora no sabemos de cuantas maneras podemos llegar de origen a destino. Pero si existen algunos vecinos para la fuente, entonces si los vecinos pueden llegar al destino a través de alguna ruta, entonces en todas estas rutas podemos agregar la fuente para obtener la cantidad de formas de llegar al destino desde la fuente.

Si podemos visualizarlo: 

Para calcular el número de formas de llegar desde el origen al destino, es decir, del origen al destino. Si los vecinos de la fuente, es decir, 0, pueden llegar al destino (4) a través de alguna ruta, entonces podemos agregar la fuente para obtener la cantidad de formas en que la fuente puede llegar al destino.

fuente ( 0 ) vecinos son 4 , 3 , 2

4 es el destino, por lo que hemos encontrado 1 ruta válida. Entonces, para obtener la ruta desde la fuente, podemos agregar la fuente delante del destino, es decir, 0 -> 4.

El número de formas en que 3 puede llegar a 4 es 3 -> 4 es la única forma posible. En este caso, solo podemos agregar la fuente para obtener la cantidad de formas de llegar al destino desde la fuente a través de 3, es decir, 0 -> 3 -> 4. Este es un camino más posible.

El número de formas en que 2 puede llegar al 4 es 2 -> 3 -> 4 es la única forma posible. Ahora podemos agregar la fuente para obtener la ruta de la fuente al destino, es decir, 0 -> 2 -> 3 -> 4.

Hemos encontrado 3 formas posibles de llegar al destino desde el origen. Pero podemos ver que hay cierta superposición de subproblemas, es decir, cuando estamos calculando la respuesta para 2 estamos explorando el camino de 3 que ya hemos calculado. Para evitar esto, podemos simplemente almacenar el resultado de cada vértice en el que hemos calculado la respuesta, de modo que nos ayude a evitar calcular la solución de subproblemas similares una y otra vez. Ahí viene la intuición de la programación dinámica.

Acercarse : 

  • Si ya estamos en el destino, hemos encontrado una ruta válida.
  • Si no hemos llegado al destino, entonces el número de formas de llegar al destino desde el vértice actual depende del número de formas en que los vecinos pueden llegar al destino. Sumamos todas las formas y las almacenamos en el arreglo dp.
  • Si ya hemos calculado el resultado de cualquier vértice, devolvemos la respuesta directamente. Para identificar que no hemos calculado la respuesta para ningún vértice, inicializamos la array dp con -1 (indica que no hemos calculado la respuesta para ese vértice).
  • Si los vecinos de algún vértice no pueden llegar al destino devolvemos -1 para indicar que no hay camino.
  • Si el número de vías es realmente muy grande, podemos modularlo con 10^9 + 7 y almacenar el resultado.

A continuación se muestra la implementación de C++

C++

#include <bits/stdc++.h>
using namespace std;
 
long dp[10006];
long mod = 1e9 + 7;
 
/* function which returns the number
of ways to reach from source to destination */
long countPaths(vector<vector<long> >& arr, long s, long d)
{
    /*
            if we are already at the
            destination we have found 1 valid
            path
    */
    if (s == d)
        return 1;
    /*
            if we have already computed the
            number of ways to reach the destination
            via this vertex return the answer
    */
    if (dp[s] != -1)
        return dp[s];
 
    long c = 0;
    for (long& neigh : arr[s]) {
        /*
                since the number of ways to reach the
           destination from the current vertex depends on
           the number of ways the neighbours can reach the
           destination so get all the possible ways that the
           neighbours can reach the destination
        */
        long x = countPaths(arr, neigh, d);
 
        // if we reached the destination than add it to the
        // result
        if (x != -1) {
            c = (c % mod + x % mod) % mod;
        }
    }
    /*
            if c is equal to zero that means there are no
       paths from the current vertex to the destination so
       return -1 to indicate there is no path or else store
       the c and return it
    */
    return (dp[s] = (c == 0) ? -1 : c);
}
long Possible_Paths(vector<vector<long> >& arr, long n,
                    long s, long d)
{
    // initialise the dp array with -1
    // to indicate we haven't computed
    // the answer for any vertex
    memset(dp, -1, sizeof dp);
 
    long c = countPaths(arr, s, d);
 
    // if there are no paths return 0
    if (c == -1)
        return 0;
 
    // else return c
    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;
}
Producción

3

Complejidad temporal:  O (V+E) donde V son los vértices y E las aristas.

Complejidad espacial: O (V + E + V) donde O (V + E) para la lista de adyacencia y O (V) para la array dp.

Publicación traducida automáticamente

Artículo escrito por pawan_asipu y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *