Ordenación topológica
Ordena los nodos de un grafo dirigido para que cada uno vaya después de todos los que necesita. El algoritmo de Kahn quita nodos sin dependencias pendientes y detecta los ciclos.
nivel intermedioTambién: topological sort, algoritmo de Kahn, orden de dependencias, grafo acíclico dirigido, DAG
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Ordenación topológica
Escribe las dependencias de un grafo dirigido («A>C»: A va antes que C) y el algoritmo de Kahn sacará un orden que las respete todas.
- esperando (le llegan flechas)
Paso 1
Primero se cuenta el grado de entrada de cada nodo: cuántas flechas le llegan, es decir, cuántas cosas tienen que ir antes que él.
1static List<String> ordenTopologico(Map<String, List<String>> g) {
2 Map<String, Integer> entrada = new LinkedHashMap<>();
3 for (String u : g.keySet()) entrada.put(u, 0);
4 for (List<String> vs : g.values())
5 for (String v : vs) entrada.merge(v, 1, Integer::sum); // entrada = A=0 B=0 C=2 D=1 E=2 F=1 G=2, cola = []
6 Deque<String> cola = new ArrayDeque<>();
7 for (String u : g.keySet()) if (entrada.get(u) == 0) cola.add(u);
8 List<String> orden = new ArrayList<>();
9 while (!cola.isEmpty()) {
10 String u = cola.poll();
11 orden.add(u);
12 for (String v : g.get(u))
13 if (entrada.merge(v, -1, Integer::sum) == 0) cola.add(v);
14 }
15 return orden.size() == g.size() ? orden : null;
16}Variables
- entrada
- A=0 B=0 C=2 D=1 E=2 F=1 G=2
- cola
- []
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Muchos problemas son listas de dependencias: una asignatura exige haber aprobado otras, un módulo no compila sin los que importa, una tarea no puede empezar hasta que acaben otras. Se representan con un grafo dirigido en el que la flecha A → B significa «A va antes que B». Una ordenación topológica es un orden de todos los nodos en el que todas las flechas apuntan hacia delante.
Solo existe si el grafo no tiene ciclos (es un DAG, *directed acyclic graph*): si A necesita B y B necesita A, ninguno puede ir primero. Y casi nunca es única: si dos tareas no dependen la una de la otra, cualquiera puede ir antes.
El algoritmo de Kahn lo calcula como lo haría una persona: cuenta cuántas flechas le llegan a cada nodo (su grado de entrada), pone en una cola los que tienen 0 (no necesitan nada) y repite: saca uno, lo añade al orden y quita sus flechas salientes. Los nodos que se quedan sin flechas entrantes pasan a la cola. Si al final no se han colocado todos, los que faltan forman un ciclo o dependen de uno.
La otra forma es con DFS: un nodo se añade al principio de la lista cuando termina su recorrido, es decir, después de todos los que van detrás de él. Kahn tiene la ventaja de detectar el ciclo de forma natural y de poder elegir entre los disponibles (por ejemplo, el menor alfabéticamente con una cola de prioridad). Además, los nodos que están libres a la vez se pueden hacer en paralelo.
Cuándo usarlo
- Orden de compilación o de construcción de módulos (Maven, Gradle, npm, Make).
- Planes de estudios con requisitos y planificación de proyectos (con duraciones: el camino crítico).
- Orden de creación de tablas con claves ajenas, de migraciones o de arranque de servicios que dependen unos de otros.
- Hojas de cálculo: recalcular cada celda después de las que usa.
Cuándo no
- Si el grafo tiene ciclos que no se pueden romper: no hay orden posible (Kahn lo detecta, pero no lo arregla).
- Si las relaciones no tienen dirección (amistades, carreteras de doble sentido): no tiene sentido.
- Si solo quieres saber si B depende de A, basta un recorrido desde A.
Paso a paso
- Contar grados de entrada. Para cada nodo, cuántas flechas le llegan: cuántas cosas tienen que ir antes.
- Cola de libres. Mete en una cola los nodos con grado 0: no necesitan nada.
- Sacar y liberar. Saca uno, añádelo al orden y resta 1 al grado de cada nodo al que apunta; el que llegue a 0 entra en la cola.
- Comprobar. Si el orden tiene todos los nodos, es una ordenación topológica. Si faltan, hay un ciclo entre los que quedan (o dependen de uno).
El código
Kahn: en qué orden construir los módulos
Además del orden se calcula la tanda de cada módulo (una más que la de los que necesita): los de la misma tanda se pueden construir en paralelo. Después se añade una dependencia circular.
1import java.util.*;
2
3public class Main {
4 static String ordenar(List<String> nodos, String[][] flechas) {
5 Map<String, List<String>> sale = new LinkedHashMap<>();
6 Map<String, Integer> entrada = new LinkedHashMap<>();
7 Map<String, Integer> tanda = new HashMap<>();
8 for (String n : nodos) {
9 sale.put(n, new ArrayList<>());
10 entrada.put(n, 0);
11 tanda.put(n, 1);
12 }
13 for (String[] f : flechas) { // {antes, después}
14 sale.get(f[0]).add(f[1]);
15 entrada.merge(f[1], 1, Integer::sum);
16 }
17 Deque<String> cola = new ArrayDeque<>();
18 for (String n : nodos) if (entrada.get(n) == 0) cola.add(n);
19 List<String> orden = new ArrayList<>();
20 while (!cola.isEmpty()) {
21 String u = cola.poll();
22 orden.add(u);
23 for (String v : sale.get(u)) {
24 tanda.put(v, Math.max(tanda.get(v), tanda.get(u) + 1)); // después de todo lo que necesita
25 if (entrada.merge(v, -1, Integer::sum) == 0) cola.add(v);
26 }
27 }
28 if (orden.size() < nodos.size()) {
29 List<String> atascados = new ArrayList<>(nodos);
30 atascados.removeAll(orden);
31 return "Hay un ciclo: " + String.join(", ", atascados) + " nunca quedan libres";
32 }
33 StringBuilder sb = new StringBuilder("Orden: " + String.join(" → ", orden));
34 for (int t = 1; t <= Collections.max(tanda.values()); t++) {
35 List<String> juntos = new ArrayList<>();
36 for (String n : orden) if (tanda.get(n) == t) juntos.add(n);
37 sb.append("\n tanda ").append(t).append(": ").append(String.join(", ", juntos));
38 }
39 return sb.toString();
40 }
41
42 public static void main(String[] args) {
43 List<String> modulos = List.of("util", "config", "modelo", "datos", "servicio", "web", "tests");
44 String[][] deps = {{"util", "modelo"}, {"modelo", "datos"}, {"util", "datos"}, {"datos", "servicio"},
45 {"modelo", "servicio"}, {"servicio", "web"}, {"servicio", "tests"}, {"util", "tests"}, {"config", "web"}};
46 System.out.println(ordenar(modulos, deps));
47 String[][] conCiclo = Arrays.copyOf(deps, deps.length + 1);
48 conCiclo[deps.length] = new String[]{"tests", "util"}; // los tests no pueden ir antes que util
49 System.out.println(ordenar(modulos, conCiclo));
50 }
51}from collections import deque
def ordenar(nodos, flechas):
sale = {n: [] for n in nodos}
entrada = {n: 0 for n in nodos}
tanda = {n: 1 for n in nodos}
for antes, despues in flechas:
sale[antes].append(despues)
entrada[despues] += 1
cola = deque(n for n in nodos if entrada[n] == 0)
orden = []
while cola:
u = cola.popleft()
orden.append(u)
for v in sale[u]:
tanda[v] = max(tanda[v], tanda[u] + 1) # después de todo lo que necesita
entrada[v] -= 1
if entrada[v] == 0:
cola.append(v)
if len(orden) < len(nodos):
atascados = [n for n in nodos if n not in orden]
return "Hay un ciclo: " + ", ".join(atascados) + " nunca quedan libres"
lineas = ["Orden: " + " → ".join(orden)]
for t in range(1, max(tanda.values()) + 1):
lineas.append(f" tanda {t}: " + ", ".join(n for n in orden if tanda[n] == t))
return "\n".join(lineas)
modulos = ["util", "config", "modelo", "datos", "servicio", "web", "tests"]
deps = [("util", "modelo"), ("modelo", "datos"), ("util", "datos"), ("datos", "servicio"),
("modelo", "servicio"), ("servicio", "web"), ("servicio", "tests"), ("util", "tests"), ("config", "web")]
print(ordenar(modulos, deps))
print(ordenar(modulos, deps + [("tests", "util")])) # los tests no pueden ir antes que utilfunction ordenar(nodos, flechas) {
const sale = new Map(nodos.map((n) => [n, []]));
const entrada = new Map(nodos.map((n) => [n, 0]));
const tanda = new Map(nodos.map((n) => [n, 1]));
for (const [antes, despues] of flechas) {
sale.get(antes).push(despues);
entrada.set(despues, entrada.get(despues) + 1);
}
const cola = nodos.filter((n) => entrada.get(n) === 0);
const orden = [];
for (let i = 0; i < cola.length; i++) { // un índice que avanza hace de cola
const u = cola[i];
orden.push(u);
for (const v of sale.get(u)) {
tanda.set(v, Math.max(tanda.get(v), tanda.get(u) + 1)); // después de todo lo que necesita
entrada.set(v, entrada.get(v) - 1);
if (entrada.get(v) === 0) cola.push(v);
}
}
if (orden.length < nodos.length) {
const atascados = nodos.filter((n) => !orden.includes(n));
return `Hay un ciclo: ${atascados.join(", ")} nunca quedan libres`;
}
const lineas = ["Orden: " + orden.join(" → ")];
for (let t = 1; t <= Math.max(...tanda.values()); t++)
lineas.push(` tanda ${t}: ` + orden.filter((n) => tanda.get(n) === t).join(", "));
return lineas.join("\n");
}
const modulos = ["util", "config", "modelo", "datos", "servicio", "web", "tests"];
const deps = [["util", "modelo"], ["modelo", "datos"], ["util", "datos"], ["datos", "servicio"],
["modelo", "servicio"], ["servicio", "web"], ["servicio", "tests"], ["util", "tests"], ["config", "web"]];
console.log(ordenar(modulos, deps));
console.log(ordenar(modulos, [...deps, ["tests", "util"]])); // los tests no pueden ir antes que utilusing System;
using System.Collections.Generic;
using System.Linq;
class Program {
static string Ordenar(List<string> nodos, List<(string Antes, string Despues)> flechas) {
var sale = nodos.ToDictionary(n => n, _ => new List<string>());
var entrada = nodos.ToDictionary(n => n, _ => 0);
var tanda = nodos.ToDictionary(n => n, _ => 1);
foreach (var (antes, despues) in flechas) {
sale[antes].Add(despues);
entrada[despues]++;
}
var cola = new Queue<string>(nodos.Where(n => entrada[n] == 0));
var orden = new List<string>();
while (cola.Count > 0) {
string u = cola.Dequeue();
orden.Add(u);
foreach (string v in sale[u]) {
tanda[v] = Math.Max(tanda[v], tanda[u] + 1); // después de todo lo que necesita
if (--entrada[v] == 0) cola.Enqueue(v);
}
}
if (orden.Count < nodos.Count)
return "Hay un ciclo: " + string.Join(", ", nodos.Except(orden)) + " nunca quedan libres";
var lineas = new List<string> { "Orden: " + string.Join(" → ", orden) };
for (int t = 1; t <= tanda.Values.Max(); t++)
lineas.Add(quot; tanda {t}: " + string.Join(", ", orden.Where(n => tanda[n] == t)));
return string.Join("\n", lineas);
}
static void Main() {
var modulos = new List<string> { "util", "config", "modelo", "datos", "servicio", "web", "tests" };
var deps = new List<(string, string)> { ("util", "modelo"), ("modelo", "datos"), ("util", "datos"), ("datos", "servicio"),
("modelo", "servicio"), ("servicio", "web"), ("servicio", "tests"), ("util", "tests"), ("config", "web") };
Console.WriteLine(Ordenar(modulos, deps));
Console.WriteLine(Ordenar(modulos, deps.Append(("tests", "util")).ToList())); // los tests no pueden ir antes que util
}
}<?php
function ordenar(array $nodos, array $flechas): string {
$sale = array_fill_keys($nodos, []);
$entrada = array_fill_keys($nodos, 0);
$tanda = array_fill_keys($nodos, 1);
foreach ($flechas as [$antes, $despues]) {
$sale[$antes][] = $despues;
$entrada[$despues]++;
}
$cola = new SplQueue();
foreach ($nodos as $n) if ($entrada[$n] === 0) $cola->enqueue($n);
$orden = [];
while (!$cola->isEmpty()) {
$u = $cola->dequeue();
$orden[] = $u;
foreach ($sale[$u] as $v) {
$tanda[$v] = max($tanda[$v], $tanda[$u] + 1); // después de todo lo que necesita
if (--$entrada[$v] === 0) $cola->enqueue($v);
}
}
if (count($orden) < count($nodos))
return "Hay un ciclo: " . implode(", ", array_diff($nodos, $orden)) . " nunca quedan libres";
$lineas = ["Orden: " . implode(" → ", $orden)];
for ($t = 1; $t <= max($tanda); $t++)
$lineas[] = " tanda $t: " . implode(", ", array_filter($orden, fn($n) => $tanda[$n] === $t));
return implode("\n", $lineas);
}
$modulos = ["util", "config", "modelo", "datos", "servicio", "web", "tests"];
$deps = [["util", "modelo"], ["modelo", "datos"], ["util", "datos"], ["datos", "servicio"],
["modelo", "servicio"], ["servicio", "web"], ["servicio", "tests"], ["util", "tests"], ["config", "web"]];
echo ordenar($modulos, $deps), "\n";
echo ordenar($modulos, [...$deps, ["tests", "util"]]), "\n"; // los tests no pueden ir antes que utilSalida al ejecutarlo (la misma en los 5 lenguajes)
Orden: util → config → modelo → datos → servicio → web → tests tanda 1: util, config tanda 2: modelo tanda 3: datos tanda 4: servicio tanda 5: web, tests Hay un ciclo: util, modelo, datos, servicio, web, tests nunca quedan libres
La otra forma: DFS en postorden
Cada módulo se añade al principio de la lista cuando terminan todos los que van detrás de él. Sale otro orden, también válido: lo comprueba la última línea.
1import java.util.*;
2
3public class Main {
4 static final Map<String, List<String>> sale = new LinkedHashMap<>();
5 static final Set<String> vistos = new HashSet<>();
6 static final LinkedList<String> orden = new LinkedList<>();
7
8 static void visitar(String u) {
9 vistos.add(u);
10 for (String v : sale.get(u))
11 if (!vistos.contains(v)) visitar(v);
12 orden.addFirst(u); // al TERMINAR: todo lo que va después ya está en la lista
13 }
14
15 public static void main(String[] args) {
16 List<String> modulos = List.of("util", "config", "modelo", "datos", "servicio", "web", "tests");
17 String[][] deps = {{"util", "modelo"}, {"modelo", "datos"}, {"util", "datos"}, {"datos", "servicio"},
18 {"modelo", "servicio"}, {"servicio", "web"}, {"servicio", "tests"}, {"util", "tests"}, {"config", "web"}};
19 for (String m : modulos) sale.put(m, new ArrayList<>());
20 for (String[] d : deps) sale.get(d[0]).add(d[1]);
21 for (String m : modulos) if (!vistos.contains(m)) visitar(m);
22 System.out.println("Orden con DFS: " + String.join(" → ", orden));
23
24 Map<String, Integer> pos = new HashMap<>();
25 for (int i = 0; i < orden.size(); i++) pos.put(orden.get(i), i);
26 boolean valido = true;
27 for (String[] d : deps)
28 if (pos.get(d[0]) > pos.get(d[1])) valido = false; // una flecha hacia atrás lo estropearía
29 System.out.println(valido ? "Válido: todas las flechas van hacia delante" : "No válido");
30 }
31}sale = {}
vistos = set()
orden = []
def visitar(u):
vistos.add(u)
for v in sale[u]:
if v not in vistos:
visitar(v)
orden.insert(0, u) # al TERMINAR: todo lo que va después ya está en la lista
modulos = ["util", "config", "modelo", "datos", "servicio", "web", "tests"]
deps = [("util", "modelo"), ("modelo", "datos"), ("util", "datos"), ("datos", "servicio"),
("modelo", "servicio"), ("servicio", "web"), ("servicio", "tests"), ("util", "tests"), ("config", "web")]
for m in modulos:
sale[m] = []
for antes, despues in deps:
sale[antes].append(despues)
for m in modulos:
if m not in vistos:
visitar(m)
print("Orden con DFS: " + " → ".join(orden))
pos = {m: i for i, m in enumerate(orden)}
valido = all(pos[a] < pos[b] for a, b in deps) # una flecha hacia atrás lo estropearía
print("Válido: todas las flechas van hacia delante" if valido else "No válido")const sale = new Map();
const vistos = new Set();
const orden = [];
function visitar(u) {
vistos.add(u);
for (const v of sale.get(u)) if (!vistos.has(v)) visitar(v);
orden.unshift(u); // al TERMINAR: todo lo que va después ya está en la lista
}
const modulos = ["util", "config", "modelo", "datos", "servicio", "web", "tests"];
const deps = [["util", "modelo"], ["modelo", "datos"], ["util", "datos"], ["datos", "servicio"],
["modelo", "servicio"], ["servicio", "web"], ["servicio", "tests"], ["util", "tests"], ["config", "web"]];
for (const m of modulos) sale.set(m, []);
for (const [antes, despues] of deps) sale.get(antes).push(despues);
for (const m of modulos) if (!vistos.has(m)) visitar(m);
console.log("Orden con DFS: " + orden.join(" → "));
const pos = new Map(orden.map((m, i) => [m, i]));
const valido = deps.every(([a, b]) => pos.get(a) < pos.get(b)); // una flecha hacia atrás lo estropearía
console.log(valido ? "Válido: todas las flechas van hacia delante" : "No válido");using System;
using System.Collections.Generic;
using System.Linq;
class Program {
static readonly Dictionary<string, List<string>> sale = new();
static readonly HashSet<string> vistos = new();
static readonly LinkedList<string> orden = new();
static void Visitar(string u) {
vistos.Add(u);
foreach (string v in sale[u])
if (!vistos.Contains(v)) Visitar(v);
orden.AddFirst(u); // al TERMINAR: todo lo que va después ya está en la lista
}
static void Main() {
string[] modulos = { "util", "config", "modelo", "datos", "servicio", "web", "tests" };
var deps = new[] { ("util", "modelo"), ("modelo", "datos"), ("util", "datos"), ("datos", "servicio"),
("modelo", "servicio"), ("servicio", "web"), ("servicio", "tests"), ("util", "tests"), ("config", "web") };
foreach (string m in modulos) sale[m] = new List<string>();
foreach (var (antes, despues) in deps) sale[antes].Add(despues);
foreach (string m in modulos) if (!vistos.Contains(m)) Visitar(m);
Console.WriteLine("Orden con DFS: " + string.Join(" → ", orden));
var pos = orden.Select((m, i) => (m, i)).ToDictionary(x => x.m, x => x.i);
bool valido = deps.All(d => pos[d.Item1] < pos[d.Item2]); // una flecha hacia atrás lo estropearía
Console.WriteLine(valido ? "Válido: todas las flechas van hacia delante" : "No válido");
}
}<?php
$sale = [];
$vistos = [];
$orden = [];
function visitar(string $u): void {
global $sale, $vistos, $orden;
$vistos[$u] = true;
foreach ($sale[$u] as $v)
if (!isset($vistos[$v])) visitar($v);
array_unshift($orden, $u); // al TERMINAR: todo lo que va después ya está en la lista
}
$modulos = ["util", "config", "modelo", "datos", "servicio", "web", "tests"];
$deps = [["util", "modelo"], ["modelo", "datos"], ["util", "datos"], ["datos", "servicio"],
["modelo", "servicio"], ["servicio", "web"], ["servicio", "tests"], ["util", "tests"], ["config", "web"]];
foreach ($modulos as $m) $sale[$m] = [];
foreach ($deps as [$antes, $despues]) $sale[$antes][] = $despues;
foreach ($modulos as $m) if (!isset($vistos[$m])) visitar($m);
echo "Orden con DFS: " . implode(" → ", $orden) . "\n";
$pos = array_flip($orden);
$valido = true;
foreach ($deps as [$a, $b]) if ($pos[$a] > $pos[$b]) $valido = false; // una flecha hacia atrás lo estropearía
echo ($valido ? "Válido: todas las flechas van hacia delante" : "No válido") . "\n";Salida al ejecutarlo (la misma en los 5 lenguajes)
Orden con DFS: config → util → modelo → datos → servicio → tests → web Válido: todas las flechas van hacia delante
Traza: Kahn con las dependencias del visualizador
| Sale | Flechas que se quitan | Quedan libres | Cola después |
|---|---|---|---|
| A | A→C | — | B |
| B | B→C, B→D | C, D | C D |
| C | C→E | — | D |
| D | D→E, D→F | E, F | E F |
| E | E→G | — | F |
| F | F→G | G | G |
| G | — | — | vacía |
A y B empiezan libres. C necesita a A y a B, así que no entra en la cola hasta que se quita la segunda flecha (B→C).
Complejidad
| Algoritmo | Tiempo | Memoria |
|---|---|---|
| Kahn (cola normal) | O(V + E) | O(V) |
| Kahn con cola de prioridad (el menor primero) | O(V log V + E) | O(V) |
| DFS en postorden | O(V + E) | O(V) |
| Comprobar que un orden es válido | O(V + E) | O(V) |
Contar los grados recorre todas las aristas una vez; después, cada nodo sale de la cola una vez y cada arista se quita una vez.
- Mejor caso: O(n)
- Caso medio: O(n)
- Peor caso: O(n)
O(V + E): contar grados recorre las aristas una vez y cada nodo sale de la cola una vez, quitando sus flechas. Las curvas grises son las demás clases, para comparar.
En la práctica
- Maven y Gradle ordenan los módulos de un proyecto y fallan si hay dependencias circulares.
- Los gestores de paquetes (npm, pip, apt) instalan las dependencias antes que lo que las usa.
- Las hojas de cálculo recalculan las celdas en orden topológico; una «referencia circular» es un ciclo.
- Los orquestadores de tareas (Airflow, los *pipelines* de integración continua) describen los trabajos como un DAG.
- Al crear las tablas de una base de datos, las que son referenciadas por claves ajenas van antes.
Errores típicos
- No detectar el ciclo y devolver un orden incompleto como si fuera bueno.
- Contar mal los grados con flechas repetidas (contarlas en un sitio y no en el otro).
- Olvidar los nodos aislados, que no aparecen en ninguna flecha pero también van en el orden.
- Confundir el sentido de la flecha: «A depende de B» es B → A.
- Esperar un único orden correcto: suele haber muchos; si se quiere uno concreto hay que fijar el criterio (por ejemplo, alfabético).
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. Plan de estudios
Cada línea A > B dice que la asignatura A hay que cursarla antes que B; una línea con un solo nombre es una asignatura sin requisitos. Escribe un orden en el que cursarlas todas: cuando haya varias disponibles a la vez, primero la menor alfabéticamente. El main ya cuenta los requisitos de cada asignatura: completa ordenar.
- Entrada:
PRO > AD,BD > AD,FOL… (los espacios alrededor de>no importan). Una línea repetida cuenta una vez. - Salida:
1. BD,2. FOL,3. PRO… una por línea. - Con requisitos circulares:
Imposible: hay requisitos circulares. Sin ordenar: AD, DI(las que no se han podido colocar, en orden alfabético). Línea con dos>o sin nombre:Línea no válida: «…».
Ejemplo
PRO > AD BD > AD PRO > PSP PRO > DI LM > DI AD > PMDM SI FOL
1. BD 2. FOL 3. LM 4. PRO 5. AD 6. DI 7. PMDM 8. PSP 9. SI
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 /** Para cada asignatura, las que la necesitan (las que van después). */
5 static final Map<String, List<String>> despues = new TreeMap<>();
6 /** Cuántas asignaturas necesita cada una. */
7 static final Map<String, Integer> requisitos = new TreeMap<>();
8
9 /** Un orden en el que cada asignatura va después de las que necesita; entre las que se pueden cursar a
10 la vez, primero la menor alfabéticamente. Si hay requisitos circulares, solo las que se han podido ordenar. */
11 static List<String> ordenar() {
12 Map<String, Integer> faltan = new HashMap<>(requisitos);
13 PriorityQueue<String> libres = new PriorityQueue<>(); // saca la menor alfabéticamente
14 for (String a : faltan.keySet()) if (faltan.get(a) == 0) libres.add(a);
15 List<String> orden = new ArrayList<>();
16 while (!libres.isEmpty()) {
17 String a = libres.poll();
18 orden.add(a);
19 for (String b : despues.get(a))
20 if (faltan.merge(b, -1, Integer::sum) == 0) libres.add(b);
21 }
22 return orden;
23 }
24
25 public static void main(String[] args) {
26 Scanner sc = new Scanner(System.in);
27 while (sc.hasNextLine()) {
28 String linea = sc.nextLine().trim();
29 if (linea.isEmpty()) continue;
30 String[] p = linea.split(">", -1);
31 if (p.length > 2 || p[0].isBlank() || (p.length == 2 && p[1].isBlank())) {
32 System.out.println("Línea no válida: «" + linea + "»");
33 continue;
34 }
35 String a = p[0].trim();
36 despues.putIfAbsent(a, new ArrayList<>());
37 requisitos.putIfAbsent(a, 0);
38 if (p.length == 2) {
39 String b = p[1].trim();
40 despues.putIfAbsent(b, new ArrayList<>());
41 requisitos.putIfAbsent(b, 0);
42 if (!despues.get(a).contains(b)) { // las líneas repetidas no cuentan dos veces
43 despues.get(a).add(b);
44 requisitos.merge(b, 1, Integer::sum);
45 }
46 }
47 }
48 List<String> orden = ordenar();
49 if (orden.size() < requisitos.size()) {
50 List<String> resto = new ArrayList<>(requisitos.keySet());
51 resto.removeAll(orden);
52 System.out.println("Imposible: hay requisitos circulares. Sin ordenar: " + String.join(", ", resto));
53 return;
54 }
55 for (int i = 0; i < orden.size(); i++) System.out.println((i + 1) + ". " + orden.get(i));
56 }
57}Con una cola normal el orden también sería válido, pero dependería del orden de llegada. La cola de prioridad fija el criterio y da siempre el mismo resultado: el orden topológico lexicográficamente menor.
El ciclo no hace falta buscarlo aparte: si algo nunca llega a 0 requisitos, no se coloca, y el orden sale más corto que la lista de asignaturas.
2. ¿Cuánto dura el proyecto?
Cada línea es una tarea con su duración en días y las tareas que tienen que haber acabado antes de empezarla. Las tareas sin dependencias entre sí se hacen en paralelo. Calcula el primer día en que puede empezar cada tarea y la duración total del proyecto. El main ya lee y valida las tareas: completa calcular con un orden topológico.
- Entrada:
tejado 4 muros(nombre, días y dependencias separadas por espacios). Las dependencias pueden declararse más abajo. - Salida, en el orden de la entrada:
tejado: del día 12 al 16, y al finalDuración total: 18 días. - Errores (y se termina):
Línea no válida: «…»,Tarea repetida: x,Dependencia desconocida: x (la necesita y)oImposible: hay dependencias circulares.
Ejemplo
cimientos 5 muros 7 cimientos tejado 4 muros electricidad 3 muros fontaneria 2 muros pintura 2 tejado electricidad fontaneria jardin 6
cimientos: del día 0 al 5 muros: del día 5 al 12 tejado: del día 12 al 16 electricidad: del día 12 al 15 fontaneria: del día 12 al 14 pintura: del día 16 al 18 jardin: del día 0 al 6 Duración total: 18 días
Ver la solución explicada
1import java.util.*;
2
3public class Main {
4 static final Map<String, Integer> duracion = new LinkedHashMap<>(); // en el orden de la entrada
5 static final Map<String, List<String>> necesita = new HashMap<>(); // las que tienen que acabar antes
6 static final Map<String, Integer> inicio = new HashMap<>(); // el primer día en que puede empezar
7
8 /** Rellena inicio con el día más temprano en que puede empezar cada tarea (las que no necesitan nada,
9 el día 0). Devuelve false si hay dependencias circulares. */
10 static boolean calcular() {
11 Map<String, Integer> faltan = new HashMap<>();
12 Map<String, List<String>> desbloquea = new HashMap<>();
13 for (String t : duracion.keySet()) {
14 faltan.put(t, necesita.get(t).size());
15 desbloquea.put(t, new ArrayList<>());
16 inicio.put(t, 0);
17 }
18 for (String t : duracion.keySet())
19 for (String d : necesita.get(t)) desbloquea.get(d).add(t);
20 Deque<String> cola = new ArrayDeque<>();
21 for (String t : duracion.keySet()) if (faltan.get(t) == 0) cola.add(t);
22 int hechas = 0;
23 while (!cola.isEmpty()) {
24 String t = cola.poll();
25 hechas++;
26 int fin = inicio.get(t) + duracion.get(t);
27 for (String s : desbloquea.get(t)) {
28 inicio.put(s, Math.max(inicio.get(s), fin)); // empieza cuando acaba la ÚLTIMA que necesita
29 if (faltan.merge(s, -1, Integer::sum) == 0) cola.add(s);
30 }
31 }
32 return hechas == duracion.size();
33 }
34
35 public static void main(String[] args) {
36 Scanner sc = new Scanner(System.in);
37 while (sc.hasNextLine()) {
38 String linea = sc.nextLine().trim();
39 if (linea.isEmpty()) continue;
40 String[] p = linea.split("\\s+");
41 if (p.length < 2 || !p[1].matches("\\d{1,4}")) {
42 System.out.println("Línea no válida: «" + linea + "»");
43 return;
44 }
45 if (duracion.containsKey(p[0])) {
46 System.out.println("Tarea repetida: " + p[0]);
47 return;
48 }
49 duracion.put(p[0], Integer.parseInt(p[1]));
50 necesita.put(p[0], new ArrayList<>(Arrays.asList(p).subList(2, p.length)));
51 }
52 for (String t : duracion.keySet())
53 for (String d : necesita.get(t))
54 if (!duracion.containsKey(d)) {
55 System.out.println("Dependencia desconocida: " + d + " (la necesita " + t + ")");
56 return;
57 }
58 if (!calcular()) {
59 System.out.println("Imposible: hay dependencias circulares");
60 return;
61 }
62 int total = 0;
63 for (String t : duracion.keySet()) {
64 int fin = inicio.get(t) + duracion.get(t);
65 total = Math.max(total, fin);
66 System.out.println(t + ": del día " + inicio.get(t) + " al " + fin);
67 }
68 System.out.println("Duración total: " + total + " días");
69 }
70}El inicio de una tarea solo depende de los finales de las que necesita, así que basta con calcularlas en un orden en el que esas ya estén hechas: un orden topológico. Es el método del camino crítico (CPM) de la gestión de proyectos.
La duración total es el final más tardío. Las tareas de la cadena que llega a ese final forman el camino crítico: si cualquiera se retrasa, se retrasa todo el proyecto.
Test
Test: Ordenación topológica
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.¿Cuándo existe una ordenación topológica de un grafo dirigido?
2.En el algoritmo de Kahn, ¿qué nodos entran en la cola al principio?
3.Kahn termina y el orden tiene menos nodos que el grafo. ¿Qué significa?
4.Con las flechas A → C y B → C, ¿cuántas ordenaciones topológicas hay?
5.¿Qué coste tiene el algoritmo de Kahn?