Flujo de costo mínimo - algoritmo de caminos más cortos sucesivos
Dada una red que consiste de vértices y aristas. Para cada arista (en general, aristas orientadas, pero véase abajo), se dan la capacidad (un entero no negativo) y el costo por unidad de flujo a lo largo de esta arista (algún entero). También están marcados la fuente y el sumidero .
Para un valor dado , hay que encontrar un flujo de esta cantidad, y entre todos los flujos de esta cantidad hay que elegir el flujo con el menor costo. Esta tarea se llama problema de flujo de costo mínimo (minimum-cost flow).
A veces la tarea se da un poco distinto: se quiere encontrar el flujo máximo, y entre todos los flujos máximos queremos encontrar el de menor costo. Esto se llama el problema de flujo máximo de costo mínimo (minimum-cost maximum-flow).
Ambos problemas se pueden resolver de forma efectiva con el algoritmo de caminos más cortos sucesivos.
Algoritmo
Este algoritmo es muy similar al de Edmonds-Karp para calcular el flujo máximo.
Caso más simple
Primero solo consideramos el caso más simple, donde el grafo es orientado, y hay a lo sumo una arista entre cualquier par de vértices (p. ej. si es una arista del grafo, entonces no puede formar parte de él también).
Sea la capacidad de una arista si esta arista existe. Y sea el costo por unidad de flujo a lo largo de esta arista . Y finalmente sea el flujo a lo largo de la arista . Inicialmente todos los valores de flujo son cero.
Modificamos la red de la siguiente forma: para cada arista agregamos la arista inversa a la red con la capacidad y el costo . Como, según nuestras restricciones, la arista no estaba en la red antes, todavía tenemos una red que no es un multigrafo (grafo con aristas múltiples). Además siempre mantendremos la condición cierta durante los pasos del algoritmo.
Definimos la red residual para algún flujo fijo de la siguiente forma (igual que en el algoritmo de Ford-Fulkerson): la red residual contiene solo aristas no saturadas (es decir, aristas en las que ), y la capacidad residual de cada una de esas aristas es .
Ahora podemos hablar de los algoritmos para calcular el flujo de costo mínimo. En cada iteración del algoritmo encontramos el camino más corto en el grafo residual de a . A diferencia de Edmonds-Karp, buscamos el camino más corto en términos del costo del camino en lugar del número de aristas. Si ya no existe un camino, entonces el algoritmo termina, y el flujo es el deseado. Si se encontró un camino, aumentamos el flujo a lo largo de él tanto como sea posible (es decir, encontramos la capacidad residual mínima del camino, y aumentamos el flujo en ella, y reducimos las aristas inversas en la misma cantidad). Si en algún momento el flujo alcanza el valor , entonces detenemos el algoritmo (nótese que en la última iteración del algoritmo es necesario aumentar el flujo solo en una cantidad tal que el valor final del flujo no supere ).
No es difícil ver que si ponemos en infinito, entonces el algoritmo encontrará el flujo máximo de costo mínimo. Así que ambas variaciones del problema se pueden resolver con el mismo algoritmo.
Grafos no dirigidos / multigrafos
El caso de un grafo no dirigido o un multigrafo no difiere conceptualmente del algoritmo de arriba. El algoritmo también funcionará en estos grafos. Sin embargo se vuelve un poco más difícil de implementar.
Una arista no dirigida es de hecho lo mismo que dos aristas orientadas y con la misma capacidad y valores. Como el algoritmo de flujo de costo mínimo descrito arriba genera una arista inversa para cada arista dirigida, parte la arista no dirigida en aristas dirigidas, y de hecho obtenemos un multigrafo.
¿Cómo lidiar con aristas múltiples? Primero el flujo de cada una de las aristas múltiples debe guardarse por separado. Segundo, al buscar el camino más corto, es necesario tener en cuenta que es importante cuál de las aristas múltiples se usa en el camino. Así, en lugar del arreglo usual de ancestros además debemos guardar el número de arista desde la que vinimos junto con el ancestro. Tercero, al aumentar el flujo a lo largo de una cierta arista, es necesario reducir el flujo a lo largo de la arista inversa. Como tenemos aristas múltiples, hay que guardar el número de arista de la arista inversa para cada arista.
No hay otras obstrucciones con grafos no dirigidos o multigrafos.
Complejidad
El algoritmo aquí es en general exponencial en el tamaño de la entrada. Para ser más específicos, en el peor caso puede empujar solo unidad de flujo en cada iteración, tomando iteraciones para encontrar un flujo de costo mínimo de tamaño , haciendo que el tiempo de ejecución total sea , donde es el tiempo requerido para encontrar el camino más corto de la fuente al sumidero.
Si se usa el algoritmo de Bellman-Ford para esto, el tiempo de ejecución es . También es posible modificar el algoritmo de Dijkstra, de modo que necesite de preprocesamiento como paso inicial y luego funcione en por iteración, haciendo que el tiempo de ejecución total sea . Aquí hay un generador de un grafo en el que tal algoritmo requeriría tiempo .
El algoritmo de Dijkstra modificado usa los llamados potenciales del algoritmo de Johnson . Es posible combinar las ideas de este algoritmo y el algoritmo de Dinic para reducir el número de iteraciones de a , donde es el costo máximo encontrado entre las aristas. Se puede leer más sobre potenciales y su combinación con el algoritmo de Dinic aquí .
Implementación
Aquí hay una implementación usando el algoritmo SPFA para el caso más simple.
struct Edge
{
int from, to, capacity, cost;
};
vector<vector<int>> adj, cost, capacity;
const int INF = 1e9;
void shortest_paths(int n, int v0, vector<int>& d, vector<int>& p) {
d.assign(n, INF);
d[v0] = 0;
vector<bool> inq(n, false);
queue<int> q;
q.push(v0);
p.assign(n, -1);
while (!q.empty()) {
int u = q.front();
q.pop();
inq[u] = false;
for (int v : adj[u]) {
if (capacity[u][v] > 0 && d[v] > d[u] + cost[u][v]) {
d[v] = d[u] + cost[u][v];
p[v] = u;
if (!inq[v]) {
inq[v] = true;
q.push(v);
}
}
}
}
}
int min_cost_flow(int N, vector<Edge> edges, int K, int s, int t) {
adj.assign(N, vector<int>());
cost.assign(N, vector<int>(N, 0));
capacity.assign(N, vector<int>(N, 0));
for (Edge e : edges) {
adj[e.from].push_back(e.to);
adj[e.to].push_back(e.from);
cost[e.from][e.to] = e.cost;
cost[e.to][e.from] = -e.cost;
capacity[e.from][e.to] = e.capacity;
}
int flow = 0;
int cost = 0;
vector<int> d, p;
while (flow < K) {
shortest_paths(N, s, d, p);
if (d[t] == INF)
break;
// find max flow on that path
int f = K - flow;
int cur = t;
while (cur != s) {
f = min(f, capacity[p[cur]][cur]);
cur = p[cur];
}
// apply flow
flow += f;
cost += f * d[t];
cur = t;
while (cur != s) {
capacity[p[cur]][cur] -= f;
capacity[cur][p[cur]] += f;
cur = p[cur];
}
}
if (flow < K)
return -1;
else
return cost;
}