Carrera y Preparación

Preguntas de Entrevista sobre Ordenación Topológica

Nadie te pide ordenar un DAG. Te dan cursos, compilaciones, tareas o un diccionario en un alfabeto desconocido, y la prueba es si detectas el grafo de dependencias y pones los arcos en la dirección correcta. Ocho preguntas que aparecen una y otra vez, cada una con la solución, la pregunta de seguimiento que hace después el entrevistador y el error que te cuesta la oferta.

18 Min de lectura Actualizado: Septiembre 2026 Nivel intermedio
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

1. Qué evalúa realmente una pregunta de ordenación topológica

Las palabras «ordenación topológica» casi nunca aparecen en la pregunta. Te dan cursos con requisitos, objetivos de compilación, listas de tareas, una receta o un diccionario en un alfabeto alienígena, y la entrevista está atenta a tres cosas.

¿Ves el grafo? Todo lo que se formula como «X debe ir antes que Y» es una arista dirigida, y la respuesta es un orden de los vértices. ¿Pones los arcos en el sentido correcto? Es el fallo más común con diferencia, y produce código que se ejecuta, devuelve un orden y está al revés. ¿Sabes que la comprobación de ciclos y la ordenación son el mismo cálculo? «¿Se puede planificar?» y «dame un plan» son un solo algoritmo con dos return distintos.

Después de eso, todas las variantes son el mismo barrido llevando algo extra: un número de nivel, una duración, un conteo, un segundo grafo. Una vez que la plantilla es automática, la parte interesante de cada uno de estos problemas es el modelado, no el código. La mecánica en sí se trata en la guía de ordenación topológica; esta página trata de las ocho preguntas que de verdad se hacen.

Cada ejemplo resuelto de abajo se ejecutó con un script antes de escribirse.

2. Las dos plantillas, y cuándo gana cada una

Hay exactamente dos implementaciones que vale la pena conocer, y un entrevistador aceptará cualquiera. Escribe la que puedas producir sin dudar y sé capaz de decir por qué podrías querer la otra.

El algoritmo de Kahn, de su artículo de 1962, es el iterativo. Cuenta cuántos requisitos le quedan a cada vértice, guarda en una cola los que están a cero y emítelos.

from collections import deque

def kahn(n, edges):                  # edges guarda (u, v): u va antes que v
    adj = [[] for _ in range(n)]
    indeg = [0] * n
    for u, v in edges:
        adj[u].append(v)
        indeg[v] += 1                # contar arcos HACIA v
    q = deque(v for v in range(n) if indeg[v] == 0)
    order = []
    while q:
        u = q.popleft()
        order.append(u)
        for v in adj[u]:
            indeg[v] -= 1            # u terminó, a v le falta uno menos
            if indeg[v] == 0:
                q.append(v)
    return order if len(order) == n else []      # salida corta significa ciclo

La última línea lleva toda la prueba de ciclo. Si algunos vértices nunca llegan a grado de entrada cero, se están esperando entre sí, y len(order) < n es la prueba. Fíjate en que no hay ningún conjunto visited en ninguna parte: el contador de grado de entrada ya garantiza que cada vértice se emite exactamente una vez.

Un grafo dirigido acíclico de ocho vértices con los arcos 0 a 3, 1 a 3, 1 a 4, 2 a 0, 2 a 5, 3 a 6, 4 a 6, 5 a 7 y 6 a 7. Cada vértice lleva una insignia con su grado de entrada: los vértices 1 y 2 están en verde con cero, y el resto en azul con uno o dos. Un panel lateral sigue la cola extracción a extracción, desde la cola inicial 1, 2 hasta que queda vacía, y una franja inferior da el orden emitido 1, 2, 4, 0, 5, 3, 6, 7 con una nota de que salieron los ocho vértices, así que el grafo no tiene ciclos.
El ejemplo de este artículo. Fíjate en el vértice 3: espera hasta que se han emitido tanto 0 como 1, que es exactamente lo que significa «todos los requisitos primero».

En este grafo la cola empieza como [1, 2], y el orden emitido es 1, 2, 4, 0, 5, 3, 6, 7. Salen los ocho vértices, así que no hay ciclo.

La versión DFS es la otra plantilla. Ejecuta una búsqueda en profundidad y añade cada vértice a una lista cuando termina, y luego invierte la lista. La sutileza que examinan los entrevistadores es el coloreado.

WHITE, GREY, BLACK = 0, 1, 2      # sin ver, en la pila, terminado

def dfs_topo(n, adj):
    colour = [WHITE] * n
    out = []
    def visit(u):
        colour[u] = GREY
        for v in adj[u]:
            if colour[v] == GREY:          # arista de retroceso: hay un ciclo
                return False
            if colour[v] == WHITE and not visit(v):
                return False
        colour[u] = BLACK
        out.append(u)                      # añadir al SALIR, no al entrar
        return True
    for v in range(n):
        if colour[v] == WHITE and not visit(v):
            return []
    return out[::-1]                       # postorden invertido

Tres colores, no un conjunto de visitados. Un simple conjunto visited no puede distinguir un arco de vuelta a la pila de recursión actual, que es un ciclo, de un arco hacia una rama ya terminada, que no lo es. Di esa frase en la entrevista y la pregunta de seguimiento sobre detección de ciclos ya está respondida.

¿Cuál usar? Kahn si el problema pide niveles, conteos, orden lexicográfico o cualquier cosa que se beneficie de procesar las fuentes por oleadas. DFS si ya estás escribiendo una búsqueda en profundidad por otro motivo, o si quieres el postorden inverso para una pasada de componentes fuertemente conexas. Ambos son O(V + E). La única diferencia práctica: el DFS recursivo necesita una profundidad de pila proporcional a la cadena más larga, que con una entrada adversa de cien mil tareas encadenadas alcanzará el límite de recursión por defecto de Python, y Kahn no.

3. Course schedule: ¿se pueden terminar todos los cursos?

La pregunta. Hay n cursos y una lista de pares [a, b] que significa «para cursar a primero debes cursar b». ¿Puedes terminarlos todos?

El paso de modelado es toda la pregunta, y es donde la mayoría de los candidatos la pierde. El par [a, b] dice b antes que a, así que el arco va b → a, y lo que aumenta es indeg[a]. Hacerlo al revés sigue produciendo una ordenación topológica válida de otro grafo, así que nada falla y la respuesta es incorrecta en silencio en cualquier caso de prueba asimétrico.

def can_finish(n, prerequisites):
    edges = [(b, a) for a, b in prerequisites]   # b antes que a
    return len(kahn(n, edges)) == n

Eso es todo: ejecuta la ordenación y compara el conteo. Di en voz alta que existe un plan exactamente cuando el grafo de requisitos es acíclico, porque un ciclo es un conjunto de cursos que se esperan unos a otros.

El mismo grafo de ocho vértices con un arco extra de 7 de vuelta a 2, dibujado en rojo, que cierra el ciclo 2 a 0 a 3 a 6 a 7 a 2. Los vértices 1 y 4 están en verde, marcados como emitidos antes de que la cola se vacíe; los vértices 0, 2, 3, 5, 6 y 7 están en rojo, marcados como vértices que nunca llegan a grado de entrada cero. Un panel lateral da las dos pruebas de ciclo: para Kahn, un conteo emitido menor que V, y para DFS, un arco hacia un vértice gris, con una nota de que un simple conjunto de visitados no distingue una arista de retroceso de una de cruce.
Un arco más, y Kahn emite 2 vértices en lugar de 8. Los seis que no se mueven son exactamente el ciclo y todo lo que está detrás de él.

Añade el arco 7 → 2 al grafo de ejemplo y la cola empieza solo con el vértice 1, emite 1 y 4 y luego se vacía. Seis vértices quedan atascados, y son precisamente el ciclo 2 → 0 → 3 → 6 → 7 → 2 junto con el vértice 5, que está detrás de él.

El seguimiento: ¿qué cursos son el problema? Los restantes con grado de entrada distinto de cero son los vértices sobre un ciclo o detrás de él, que suele ser la respuesta que buscan. Si insisten en el ciclo en sí y no en todo lo que bloquea, necesitas la versión DFS: cuando encuentras un vértice gris, la pila de recursión actual desde ese vértice en adelante es el ciclo.

La trampa. Invertir los arcos. Lee el par en voz alta como «a depende de b, así que b va primero» antes de teclear, y confirma la dirección con el entrevistador con un ejemplo de dos elementos.

4. Course schedule II: devolver un orden

La pregunta. La misma entrada, pero devuelve un orden válido, o una lista vacía si no existe ninguno.

Esto es kahn sin cambios, y por eso las dos preguntas suelen hacerse una detrás de otra. La única idea nueva es una que debes mencionar por tu cuenta: el orden no es único, y el corrector acepta cualquiera válido.

El mismo DAG de ocho vértices dibujado dos veces. A la izquierda, etiquetado como Kahn BFS, el orden emitido es 1, 2, 4, 0, 5, 3, 6, 7. A la derecha, etiquetado como postorden DFS, el postorden es 7, 6, 3, 0, 4, 1, 5, 2 y al invertirlo se obtiene el orden 2, 5, 1, 4, 0, 3, 6, 7. Una franja inferior indica que este DAG tiene 49 órdenes topológicos válidos distintos.
Dos plantillas, dos respuestas distintas, ambas correctas. Contándolos por fuerza bruta, este grafo de ocho vértices admite 49 órdenes válidos.

Kahn devuelve 1, 2, 4, 0, 5, 3, 6, 7 y el DFS devuelve 2, 5, 1, 4, 0, 3, 6, 7. Ninguno es más correcto que el otro, y un conteo exhaustivo dice que este grafo tiene 49 órdenes válidos distintos. Si tu solución se compara con una única respuesta esperada, lo que está mal es la prueba, no la solución.

El seguimiento: devuelve el orden lexicográficamente más pequeño. Sustituye la cola por un montículo de mínimos. En cada paso extraes el vértice disponible más pequeño en lugar del que se encoló antes, lo que fija de forma voraz el menor valor posible en cada posición. El coste pasa de O(V + E) a O(V + E log V), y saber enunciar ese compromiso es el sentido del seguimiento. En el grafo de ejemplo el orden más pequeño es 1, 2, 0, 3, 4, 5, 6, 7.

La trampa. Devolver order sin la comprobación de longitud. Con una entrada cíclica devuelves un plan parcial que parece totalmente plausible, y todas las pruebas automáticas con un ciclo fallan mientras tu ejecución local del caso feliz pasa.

5. Alien dictionary: recuperar un alfabeto

La pregunta. Te dan palabras ordenadas según un alfabeto desconocido. Recupera un orden de las letras coherente con esa ordenación, o indica que no existe ninguno.

Nada aquí parece un grafo hasta que te fijas en lo que te dice «ordenadas». Compara dos palabras adyacentes, busca la primera posición en la que difieren y habrás aprendido exactamente un hecho: esa letra de la primera palabra va antes que esa letra de la segunda. Todo lo que hay después de la primera diferencia no te dice nada. Luego ordena las letras topológicamente.

def alien_order(words):
    adj = {c: set() for w in words for c in w}
    indeg = {c: 0 for c in adj}
    for w1, w2 in zip(words, words[1:]):
        if len(w1) > len(w2) and w1.startswith(w2):
            return ""                       # «abc» antes que «ab» es imposible
        for a, b in zip(w1, w2):
            if a != b:
                if b not in adj[a]:         # no contar dos veces un duplicado
                    adj[a].add(b)
                    indeg[b] += 1
                break                       # solo cuenta la PRIMERA diferencia
    ...                                     # luego Kahn sobre las letras

Tres detalles en seis líneas, y los entrevistadores comprueban los tres. Solo pares adyacentes. Comparar todos los pares de palabras añade aristas que la entrada no justifica. Solo la primera posición que difiere, y luego break. La regla del prefijo: si una palabra es un prefijo estricto de la anterior, la entrada se contradice y la respuesta es la cadena vacía, sin construir ningún grafo.

Con la entrada clásica ["wrt", "wrf", "er", "ett", "rftt"] las comparaciones dan t → f, w → e, r → t y e → r, y la ordenación devuelve "wertf". Con ["abc", "ab"] se activa la regla del prefijo y devuelve "". Con ["z", "x", "z"] las aristas z → x y x → z forman un ciclo, así que la comprobación de longitud también devuelve "".

El seguimiento: ¿es el alfabeto que devolviste el único? Esa es la cuestión de unicidad de la sección 7: el orden está forzado exactamente cuando la cola contiene una sola letra en cada paso. Cualquier letra que nunca aparece en una comparación flota libre y su posición es arbitraria.

La trampa. Construir el grafo a partir de las letras que aparecen en comparaciones en lugar de a partir de todas las letras de todas las palabras. Las letras que nunca se comparan tienen que aparecer igualmente en la salida, y omitirlas es el fallo que detecta una prueba oculta y no la tuya.

6. Cursos en paralelo: el número mínimo de semestres

La pregunta. Puedes cursar cualquier número de cursos a la vez, siempre que todos sus requisitos estén ya hechos. ¿Cuál es el menor número de semestres necesario?

La respuesta es el número de niveles del DAG, y el nivel de un vértice es uno más que el mayor nivel entre sus predecesores. Lleva ese número a lo largo del mismo barrido.

def min_semesters(n, edges):
    order = kahn(n, edges)
    if len(order) != n:
        return -1                          # un ciclo: nunca termina
    level = [1] * n
    for u in order:                        # todo predecesor de u ya es definitivo
        for v in adj[u]:
            level[v] = max(level[v], level[u] + 1)
    return max(level)

Como el bucle se ejecuta en orden topológico, cada predecesor de u ya ha contribuido antes de que se lea u, y esa es la propiedad que hace que baste una pasada. En el grafo de ejemplo los niveles son {1, 2}, luego {0, 4, 5}, luego {3}, luego {6}, luego {7}, así que la respuesta es 5 semestres.

Dos paneles sobre el mismo DAG de ocho vértices. A la izquierda, el mínimo de semestres: cinco columnas con los vértices 1 y 2, luego 0, 4 y 5, luego 3, luego 6 y luego 7, que dan cinco semestres. A la derecha, el camino crítico: cada vértice lleva una duración en días y un tiempo de finalización más temprano, la cadena 2 a 0 a 3 a 6 a 7 está resaltada en rojo con finalizaciones 4, 7, 12, 18 y 21, y la duración del proyecto es de 21 días. Una nota explica que el camino más largo es NP-difícil en un grafo general pero lineal en un DAG.
El mismo barrido hacia delante responde a ambas preguntas. Lleva un contador y obtienes semestres; lleva una duración y obtienes el plazo.

El seguimiento: ¿y si solo puedes cursar como mucho k cursos por semestre? La respuesta fácil se viene abajo. El paralelismo ilimitado es lineal porque tomar con voracidad todo lo disponible es óptimo; limitar la anchura lo convierte en planificación con restricciones de precedencia en k máquinas, que es NP-difícil en general. Dos máquinas idénticas con tareas unitarias es el caso tratable clásico, resuelto por Coffman y Graham en 1972. Reconocer que el seguimiento cambia de clase de complejidad, en lugar de intentar parchear el bucle, es lo que el entrevistador quiere oír.

La trampa. Asignar un nivel la primera vez que se alcanza un vértice, como si fuera un BFS ordinario desde las fuentes. Un vértice debe esperar a su predecesor más lento, así que el nivel es un máximo, no una primera llegada. La variante BFS por oleadas solo funciona si extraes una capa entera cada vez y nunca miras un vértice antes de que su grado de entrada llegue a cero.

7. ¿Es único el orden? Reconstrucción de secuencias

La pregunta. Dado un DAG, decide si tiene exactamente un orden topológico válido. El envoltorio habitual es la reconstrucción de secuencias: te dan una secuencia y un conjunto de subsecuencias y te preguntan si la secuencia es la única coherente con ellas.

La prueba es una línea dentro del bucle de Kahn.

    while q:
        if len(q) > 1:
            return False              # hubo elección: el orden no es forzado
        u = q.popleft()
        ...

Si la cola llega a contener dos vértices, ambos están disponibles y cualquiera puede ir a continuación, así que existen al menos dos órdenes válidos. Si contiene exactamente uno en cada paso, nunca hubo que elegir y el orden está forzado.

Hay una segunda forma equivalente de decirlo que impresiona: el orden es único exactamente cuando los vértices consecutivos están unidos por un arco, es decir, cuando el orden topológico es un camino hamiltoniano en el DAG. Ambas formulaciones se comprueban en O(V + E), y citar la versión del camino hamiltoniano demuestra que entiendes por qué la unicidad es una propiedad estructural y no un accidente de la cola.

En el grafo de ejemplo, los tamaños de la cola en las ocho extracciones son 2, 2, 3, 2, 2, 1, 1, 1. El primer paso ya ofrece elegir entre 1 y 2, así que el orden no es único, lo que confirman los 49 órdenes válidos contados en la sección 4. En la cadena 0 → 1 → 2 → 3 los tamaños son 1, 1, 1, 1 y el orden está forzado.

El seguimiento: la propia reconstrucción de secuencias. Construye el grafo a partir de los pares consecutivos de cada subsecuencia, ejecuta la comprobación anterior y verifica además que el orden emitido es igual a la secuencia dada. Hacen falta ambas condiciones: un orden único distinto de la secuencia que te dieron sigue siendo un «no».

La trampa. Comprobar el tamaño de la cola solo una vez, al principio. Un grafo puede empezar con una sola fuente y ramificarse tres pasos después, así que la comparación tiene que hacerse en cada iteración. Conviene saberlo: «cada nivel contiene exactamente un vértice» es una prueba equivalente, porque un orden forzado convierte los niveles en una cadena estricta, así que razonar por niveles no está mal, solo te cuesta una segunda pasada para calcularlos.

8. El camino más largo y el camino crítico

La pregunta. Cada tarea dura un número conocido de días y no puede empezar hasta que sus requisitos estén hechos. ¿Cuándo termina el proyecto y qué tareas son las que lo deciden?

Es el problema del camino más largo, y en un grafo general es NP-difícil. En un DAG es lineal, y la razón es el orden topológico: cada predecesor de un vértice es definitivo antes de leer ese vértice, así que una sola pasada hacia delante lo resuelve.

def critical_path(n, edges, dur):
    order = kahn(n, edges)
    finish = list(dur)                     # fin más temprano si nada lo bloquea
    prev = [-1] * n
    for u in order:
        for v in adj[u]:
            if finish[u] + dur[v] > finish[v]:
                finish[v] = finish[u] + dur[v]
                prev[v] = u                # recordar quién forzó el retraso
    end = max(range(n), key=lambda v: finish[v])
    path = []
    while end != -1:
        path.append(end); end = prev[end]
    return max(finish), path[::-1]

Asigna al grafo de ejemplo las duraciones 3, 2, 4, 5, 1, 2, 6, 3 para los vértices 0 a 7 y los tiempos de finalización más tempranos salen 7, 2, 4, 12, 3, 6, 18, 21. El proyecto dura 21 días y el camino crítico es 2 → 0 → 3 → 6 → 7, cuyas duraciones suman exactamente 21. Esa cadena es lo que un gestor llama «el camino crítico»: si cualquier tarea de ella se retrasa un día, todo el proyecto se retrasa un día, mientras que la tarea 5 tiene holgura y puede desplazarse días sin que nadie lo note. Es el método de Kelley y Walker de 1959, y decir su nombre en voz alta no cuesta nada.

El seguimiento: el camino más corto en su lugar. Cambia la comparación a < y tienes caminos más cortos desde un origen en un DAG, en O(V + E), y funciona con pesos negativos, algo que Dijkstra no puede hacer. Siempre que un entrevistador mencione aristas negativas en un grafo acíclico, esta es la respuesta, no Bellman-Ford. Compara con el caso general en algoritmos de camino más corto.

La trampa. Relajar en el orden equivocado. Iterar sobre los vértices de 0 a n-1 en lugar de sobre el orden topológico da un valor que depende del etiquetado: en este grafo informa en silencio de 17 en lugar de 21, porque el vértice 0 se lee antes de que el vértice 2 haya contribuido a él. Todo el sentido del orden topológico es que hace que baste una pasada.

9. Estados finalmente seguros: ordenar el grafo inverso

La pregunta. Un nodo es seguro si todos los caminos que salen de él llegan a un nodo terminal, de modo que nunca puedes quedarte atrapado en un ciclo. Devuelve todos los nodos seguros en orden ascendente.

Formulado hacia delante esto es incómodo. Invierte todos los arcos y se convierte en una ordenación topológica: quita los nodos con grado de salida cero, que son los terminales, y cada vez que el grado de salida restante de un nodo llega a cero, todos sus sucesores eran seguros, así que él también lo es.

def safe_nodes(graph):
    n = len(graph)
    rev = [[] for _ in range(n)]
    outdeg = [len(graph[u]) for u in range(n)]
    for u in range(n):
        for v in graph[u]:
            rev[v].append(u)
    q = deque(v for v in range(n) if outdeg[v] == 0)   # terminales
    safe = []
    while q:
        u = q.popleft()
        safe.append(u)
        for p in rev[u]:
            outdeg[p] -= 1
            if outdeg[p] == 0:
                q.append(p)
    return sorted(safe)

Es el algoritmo de Kahn con el grado de entrada sustituido por el de salida y los arcos invertidos, y conviene decirlo explícitamente porque demuestra que reconoces la plantilla bajo un disfraz. En el ejemplo estándar [[1,2], [2,3], [5], [0,5], [5], [], []] la respuesta es [2, 4, 5, 6]: los nodos 5 y 6 son terminales, 2 y 4 solo llevan a ellos, y 0, 1 y 3 están en el ciclo 0 → 1 → 3 → 0.

El seguimiento: hazlo con DFS. Otra vez tres colores. Un nodo es seguro si ningún arco desde él alcanza un vértice gris, y puedes memoizar el resultado por nodo para que todo siga siendo lineal. Los entrevistadores suelen querer oír ambas, porque la versión del grafo inverso es la que los candidatos rara vez encuentran por sí solos.

La trampa. Responder «los nodos que no están en un ciclo». Según esa lectura, el nodo 3 no está en ningún ciclo propio, pero tiene un arco hacia el ciclo que pasa por 0, así que no es seguro. La seguridad trata de todos los caminos desde el nodo, no del nodo en sí.

10. Ordenar elementos por grupo: dos niveles a la vez

La pregunta. Los elementos pertenecen a grupos, algunos elementos deben ir antes que otros y los elementos del mismo grupo deben quedar contiguos en la salida. Devuelve un orden válido o una lista vacía.

Esta es la variante difícil, y la idea es pequeña: ejecuta dos ordenaciones topológicas. Una sobre los grupos, con un arco entre grupos cada vez que un elemento de uno debe ir antes que un elemento de otro, y otra sobre los elementos dentro de cada grupo. Luego concatena los grupos en el orden de grupos, cada uno relleno con sus propios elementos ordenados.

La única parte delicada son los elementos sin grupo. Un elemento con grupo -1 no está restringido por ninguna agrupación, así que dale a cada uno un grupo nuevo y propio. Juntarlos todos en un único grupo es la respuesta errónea clásica: obliga a elementos sin relación a ser contiguos y puede convertir un caso resoluble en uno sin solución.

Si cualquiera de las dos ordenaciones falla, falla todo el caso, así que la comprobación de longitud se hace dos veces. La complejidad sigue siendo O(V + E) en ambas pasadas, ya que cada elemento y cada dependencia se tocan un número constante de veces.

El seguimiento: ¿es un curso requisito de otro? Eso es Course Schedule IV, y pide alcanzabilidad en lugar de un orden. Procesa los vértices en orden topológico y une el conjunto alcanzable de cada vértice en sus sucesores, usando bitsets: O(V × E / 64) en la práctica, y el orden topológico es lo que garantiza que un conjunto está completo antes de copiarse hacia delante.

La trampa. Ordenar los grupos pero olvidar que un grupo también puede formar un ciclo consigo mismo a través de dos elementos de grupos distintos. Construye el grafo de grupos a partir de las dependencias de elementos solo cuando los dos grupos difieren, o crearás bucles que hacen fallar la ordenación sin motivo.

11. Las respuestas de complejidad

Tenlas preparadas, porque se preguntan tal cual y la respuesta es corta.

VarianteTiempoEspacioPor qué
Kahn o DFSO(V + E)O(V + E)Cada vértice se emite una vez, cada arco se relaja una vez
Lexicográficamente menorO(V + E log V)O(V + E)La cola pasa a ser un montículo
Niveles o camino más largoO(V + E)O(V + E)Un array extra llevado en el mismo barrido
Comprobación de unicidadO(V + E)O(V + E)Una comparación por extracción
Alcanzabilidad entre todos los paresO(V × E / 64)O(V2 / 64)Unión de bitsets en orden topológico

Dos cosas que añadir sin que te lo pidan. Primero, el grafo normalmente no te lo dan como lista de adyacencia: llega como una lista de pares, y construir la lista también cuesta O(V + E), así que citar una cota que ignore la construcción es incorrecto. Segundo, una ordenación topológica no es una ordenación por comparación y no está limitada por O(n log n): es lineal precisamente porque la entrada ya aporta las restricciones de orden en lugar de obligarte a descubrirlas.

La versión DFS recursiva también usa O(V) de profundidad de pila en el peor caso, que es un límite real y no teórico. Una cadena de 100.000 tareas agota el límite de recursión por defecto de Python, de 1000, mucho antes de agotar la memoria.

12. Errores que suspenden la entrevista

Ordenados según su frecuencia; los dos primeros explican la mayoría de las soluciones rechazadas.

El hábito que evita la mayoría de estos errores: antes de escribir nada, di en qué dirección van los arcos y cuál es la respuesta cuando la ordenación se queda corta. Si no puedes decir ambas cosas en una frase, aún no estás listo para teclear.

13. Preguntas frecuentes

¿Qué es una ordenación topológica, en términos sencillos?

+

Es un orden de los vértices de un grafo dirigido en el que todos los arcos apuntan hacia delante, de modo que nada aparece antes de algo de lo que depende. Cursos después de sus requisitos, objetivos de compilación después de sus entradas, tareas después de las tareas que las bloquean. Existe si y solo si el grafo no tiene ciclos dirigidos, y encontrarlo cuesta O(V + E).

¿Kahn o DFS: cuál debería escribir en una entrevista?

+

El que puedas escribir sin dudar, ya que ambos son O(V + E) y ambos se aceptan. Kahn es la mejor opción por defecto: es iterativo, así que no hay límite de recursión, su prueba de ciclo es una comparación de longitud en lugar de un argumento de colores, y se extiende de forma natural a niveles, al orden lexicográfico con un montículo y a cualquier cosa que se procese por oleadas. Recurre a DFS cuando ya necesitas una búsqueda en profundidad para otra parte del problema, o cuando quieres el postorden inverso para una pasada de componentes fuertemente conexas.

¿Cómo detecto un ciclo con una ordenación topológica?

+

Con Kahn, cuenta lo que sale: si se emiten menos de V vértices, los que se quedan atrás nunca llegaron a grado de entrada cero y son exactamente los vértices sobre un ciclo o detrás de él. Con DFS, colorea los vértices de blanco, gris y negro, donde gris significa que está en la pila de recursión; un arco hacia un vértice gris es una arista de retroceso, y una arista de retroceso es un ciclo. Un conjunto de visitados de dos estados no puede hacer esa distinción y detectará ciclos que no existen.

¿Es único el orden topológico?

+

Casi nunca. El grafo de ocho vértices usado en todo este artículo tiene 49 órdenes válidos. El orden es único exactamente cuando la cola de Kahn contiene un solo vértice en cada paso, lo que equivale a decir que los vértices consecutivos del orden están unidos por un arco, así que el orden es un camino hamiltoniano en el DAG. Si un enunciado espera una respuesta concreta, normalmente pide la lexicográficamente menor, que se obtiene sustituyendo la cola por un montículo de mínimos.

¿Se puede ordenar topológicamente un grafo no dirigido?

+

No, y conviene responder con cuidado porque a veces es una prueba. Una arista no dirigida no impone ningún orden entre sus extremos, así que no hay nada que ordenar. Si un problema te da un grafo no dirigido y pide un orden, o la dirección está implícita en algún lugar del enunciado y tienes que recuperarla, o la técnica buscada es otra, como ir quitando hojas para los árboles de altura mínima.

¿Por qué el camino más largo es fácil en un DAG pero difícil en general?

+

Porque un orden topológico permite fijar cada vértice una sola vez. Todos los predecesores de un vértice tienen su valor definitivo antes de leer ese vértice, así que basta una única pasada hacia delante y el coste es O(V + E). En un grafo con ciclos no existe ese orden, un camino no puede repetir vértices y el problema del camino simple más largo es NP-difícil. Por eso la planificación de proyectos, que es el camino más largo con duraciones, es en la práctica un cálculo en tiempo lineal.

¿Qué problemas de entrevista son en secreto ordenación topológica?

+

Course Schedule I y II, Alien Dictionary, Parallel Courses, Sequence Reconstruction, Find Eventual Safe States, Sort Items by Group, Course Schedule IV, Minimum Time to Complete All Tasks, y cualquier pregunta de orden de compilación, planificación de tareas o resolución de dependencias. La pista es la frase «debe ir antes», o una entrada de pares cuyos dos elementos no son simétricos.

14. Referencias

Los artículos que introdujeron estas técnicas y los libros que las analizan, en orden cronológico.

  1. Kelley, J. E. y Walker, M. R. (1959). “Critical-path planning and scheduling.” Proceedings of the Eastern Joint Computer Conference, 160–173.
  2. Kahn, A. B. (1962). “Topological sorting of large networks.” Communications of the ACM, 5(11), 558–562.
  3. Knuth, D. E. (1968). The Art of Computer Programming, Volume 1: Fundamental Algorithms, sección 2.2.3. Addison-Wesley.
  4. Coffman, E. G. y Graham, R. L. (1972). “Optimal scheduling for two-processor systems.” Acta Informatica, 1(3), 200–213.
  5. Tarjan, R. E. (1972). “Depth-first search and linear graph algorithms.” SIAM Journal on Computing, 1(2), 146–160.
  6. Tarjan, R. E. (1976). “Edge-disjoint spanning trees and depth-first search.” Acta Informatica, 6(2), 171–185.
  7. Cormen, T. H., Leiserson, C. E., Rivest, R. L. y Stein, C. (2009). Introduction to Algorithms, 3.ª edición, sección 22.4. MIT Press.
  8. Sedgewick, R. y Wayne, K. (2011). Algorithms, 4.ª edición, sección 4.2. Addison-Wesley.
  9. McDowell, G. L. (2015). Cracking the Coding Interview, 6.ª edición. CareerCup.
  10. Skiena, S. S. (2020). The Algorithm Design Manual, 3.ª edición, sección 5.10. Springer.

Mira cómo se vacía la cola

Construye el DAG de ocho vértices de la sección 2 y recórrelo paso a paso. Ver cómo el vértice 3 espera con grado de entrada uno hasta que se han emitido tanto 0 como 1 es la forma más rápida de entender por qué el conteo es la prueba de ciclo.

Abrir el visualizador de ordenación topológica