Skip to Content

Algoritmo de D´Esopo-Pape

Dado un grafo con nn vértices y mm aristas con pesos wiw_i y un vértice de inicio v0v_0. La tarea es encontrar el camino más corto del vértice v0v_0 a cada otro vértice.

El algoritmo de D´Esopo-Pape suele trabajar más rápido que el algoritmo de Dijkstra y el algoritmo de Bellman-Ford en la mayoría de los casos, y también funciona con aristas negativas. Sin embargo no para ciclos negativos.

Descripción

Sea el arreglo dd el que contiene las longitudes de los caminos más cortos, es decir, did_i es la longitud actual del camino más corto del vértice v0v_0 al vértice ii. Inicialmente este arreglo se llena con infinito para cada vértice, excepto dv0=0d_{v_0} = 0. Cuando el algoritmo termina, este arreglo contendrá las distancias más cortas.

Sea el arreglo pp el que contiene los ancestros actuales, es decir, pip_i es el ancestro directo del vértice ii en el camino más corto actual de v0v_0 a ii. Al igual que el arreglo dd, el arreglo pp cambia gradualmente durante el algoritmo y al final toma sus valores finales.

Ahora al algoritmo. En cada paso se mantienen tres conjuntos de vértices:

  • M0M_0 - vértices para los cuales la distancia ya se calculó (aunque podría no ser la distancia final)
  • M1M_1 - vértices para los cuales la distancia se está calculando actualmente
  • M2M_2 - vértices para los cuales la distancia todavía no se calculó

Los vértices del conjunto M1M_1 se guardan en una cola bidireccional (deque).

En cada paso del algoritmo tomamos un vértice del conjunto M1M_1 (del frente de la cola). Sea uu el vértice seleccionado. Ponemos este vértice uu en el conjunto M0M_0. Después iteramos sobre todas las aristas que salen de este vértice. Sea vv el otro extremo de la arista actual, y ww su peso.

  • Si vv pertenece a M2M_2, entonces vv se inserta en el conjunto M1M_1 insertándolo al final de la cola. dvd_v se pone en du+wd_u + w.
  • Si vv pertenece a M1M_1, entonces intentamos mejorar el valor de dvd_v: dv=min(dv,du+w)d_v = \min(d_v, d_u + w). Como vv ya está en M1M_1, no hace falta insertarlo en M1M_1 ni en la cola.
  • Si vv pertenece a M0M_0, y si dvd_v se puede mejorar dv>du+wd_v > d_u + w, entonces mejoramos dvd_v e insertamos el vértice vv de vuelta al conjunto M1M_1, colocándolo al principio de la cola.

Y por supuesto, con cada actualización en el arreglo dd también hay que actualizar el elemento correspondiente en el arreglo pp.

Implementación

Usaremos un arreglo mm para guardar en qué conjunto está actualmente cada vértice.

struct Edge { int to, w; }; int n; vector<vector<Edge>> adj; const int INF = 1e9; void shortest_paths(int v0, vector<int>& d, vector<int>& p) { d.assign(n, INF); d[v0] = 0; vector<int> m(n, 2); deque<int> q; q.push_back(v0); p.assign(n, -1); while (!q.empty()) { int u = q.front(); q.pop_front(); m[u] = 0; for (Edge e : adj[u]) { if (d[e.to] > d[u] + e.w) { d[e.to] = d[u] + e.w; p[e.to] = u; if (m[e.to] == 2) { m[e.to] = 1; q.push_back(e.to); } else if (m[e.to] == 0) { m[e.to] = 1; q.push_front(e.to); } } } } }

Complejidad

El algoritmo suele ser bastante rápido: en la mayoría de los casos, incluso más rápido que el algoritmo de Dijkstra. Sin embargo existen casos para los cuales el algoritmo toma tiempo exponencial, lo que lo hace inadecuado en el peor caso. Ver discusiones en Stack Overflow  y Codeforces  como referencia.