Clase 11 · Cierre de grafos y comienzo de algoritmos voraces
Alcance de la sesión
La clase cerró la unidad de grafos con dos problemas relacionados pero distintos: decidir si todo un digrafo es fuertemente conexo mediante dos BFS y encontrar todas sus componentes fuertemente conexas mediante dos DFS (algoritmo de Kosaraju–Sharir). Después comenzó la unidad de algoritmos voraces con el problema de cambio de monedas.
Pregunta central
¿Cómo aprovechamos recorridos lineales para revelar la estructura de un grafo dirigido y qué hace falta demostrar para que una elección local —como tomar siempre la moneda más grande posible— produzca una solución globalmente óptima?
Idea de la clase
Un solo recorrido desde únicamente comprueba qué vértices alcanza . Para certificar conectividad fuerte se ejecuta también el recorrido en el grafo transpuesto, con lo que se comprueba quiénes pueden llegar a . Para obtener todas las componentes fuertes, Kosaraju ordena los vértices por tiempos de finalización de un DFS en y recorre en ese orden. En ambos casos el costo es . El cambio de monedas introduce el paradigma voraz: tomar la mayor moneda factible funciona para las denominaciones estadounidenses, pero no para cualquier sistema de monedas.
1. Recordatorio: alcanzabilidad y conectividad fuerte
En un digrafo escribimos cuando existe un camino dirigido de a .
Dos vértices y son mutuamente alcanzables si
El grafo es fuertemente conexo si esto se cumple para todo par de vértices.
1.1 Lema del vértice testigo
Sea cualquier vértice de . Entonces es fuertemente conexo si y solo si:
- todos los vértices son alcanzables desde ; y
- es alcanzable desde todos los vértices.
Demostración. La implicación directa viene de la definición. Para la recíproca, sean . Por la segunda condición existe y por la primera existe ; al concatenarlos obtenemos . Intercambiando y obtenemos .
2. Prueba completa de conectividad fuerte con dos BFS
El grafo transpuesto de es
Invertir las aristas convierte la pregunta “¿quién puede llegar a en ?” en “¿a quién alcanza en ?”.
def es_fuertemente_conexo(G):
s = un_vertice_cualquiera(G)
alcanzados_ida = BFS(G, s)
if len(alcanzados_ida) != len(G.V):
return False
GT = transponer(G)
alcanzados_vuelta = BFS(GT, s)
return len(alcanzados_vuelta) == len(G.V)2.1 Corrección
- Si el primer BFS no visita todo , existe algún tal que ; por tanto, no es fuertemente conexo.
- Si el segundo BFS no visita todo , existe algún tal que en , equivalente a en ; tampoco hay conectividad fuerte.
- Si ambos recorridos visitan todo , se satisfacen las dos condiciones del lema del vértice testigo; por tanto, sí es fuertemente conexo.
2.2 Complejidad
Con listas de adyacencia:
| Operación | Tiempo |
|---|---|
| BFS en | $O( |
| Construcción de | $O( |
| BFS en | $O( |
| Total | **$O( |
Cada vértice y cada arista se procesa un número constante de veces.
Qué resuelve y qué no
Los dos BFS responden una pregunta de decisión: “¿todo es fuertemente conexo?”. Si la respuesta es no, todavía no indican por sí solos cuál es la partición completa de en componentes fuertes. Para eso se usa el algoritmo de la siguiente sección.
3. Componentes fuertemente conexas y condensación
Una componente fuertemente conexa (strongly connected component, SCC) es un subconjunto maximal cuyos vértices son mutuamente alcanzables.
La maximalidad importa: si se puede agregar otro vértice y conservar la alcanzabilidad mutua, el conjunto todavía no era una componente completa.
Las SCC forman una partición
Al contraer cada a un solo supervértice se obtiene el grafo de condensación . Existe una arista si alguna arista de sale de un vértice de y entra a uno de .
Propiedad estructural
siempre es un DAG. Si hubiera un ciclo entre varias componentes, todas serían mutuamente alcanzables y su unión sería una componente fuerte mayor, contradiciendo su maximalidad.
Esta propiedad es la razón profunda por la que los tiempos de finalización de DFS permiten recuperar las componentes en un orden seguro.
4. Algoritmo de Kosaraju–Sharir: todas las SCC con dos DFS
Las fotografías del pizarrón registran el siguiente procedimiento bajo el nombre Comp.Fuertes(G).
4.1 Algoritmo
- Crear una lista vacía .
- Ejecutar
DFS(G). Cuando termineDFS-Visit(u)y se fije , insertar al frente de . - Construir el grafo transpuesto .
- Ejecutar
DFS(G^T), eligiendo los vértices en el orden de , es decir, en orden decreciente de tiempo de finalización en el primer DFS. - Los vértices descubiertos dentro de un mismo árbol del segundo bosque DFS forman una componente fuertemente conexa.
def componentes_fuertes(G):
L = []
def al_finalizar(u):
L.insert(0, u) # orden decreciente de u.f
DFS(G, al_finalizar=al_finalizar)
GT = transponer(G)
componentes = []
visitado = set()
for u in L:
if u not in visitado:
C = DFS_desde(GT, u, visitado)
componentes.append(C)
return componentesImplementación eficiente de
En vez de insertar al frente de un arreglo —lo cual costaría por inserción— se puede apilar cada vértice al finalizar y después recorrer la pila al revés, o usar una lista enlazada con inserción al frente en .
4.2 Por qué funciona
Para una componente , definamos
Si en el grafo de condensación existe una arista , entonces el primer DFS satisface :
- si DFS descubre primero , puede alcanzar ; la exploración de termina antes de que termine la de ;
- si descubre primero , como es un DAG no hay camino de regreso de a ; termina antes de que sea descubierto.
Por ello, la componente con mayor tiempo de finalización es una fuente de . Al transponer el grafo se convierte en un sumidero de . Un DFS iniciado allí:
- puede alcanzar todos los vértices de su propia SCC, porque dentro de ella existen caminos en ambos sentidos; y
- no puede escapar hacia otra SCC todavía no procesada, porque la componente es sumidero en la condensación transpuesta.
Así, el primer árbol del segundo DFS recupera exactamente una SCC. Al marcarla como visitada y repetir el argumento, se obtienen todas las demás.
4.3 Complejidad
El primer término corresponde al primer DFS, el segundo a construir y el tercero al segundo DFS. La memoria adicional también es si se almacena explícitamente el transpuesto.
4.4 BFS frente a Kosaraju
| Pregunta | Herramienta | Resultado |
|---|---|---|
| ¿Todo es fuertemente conexo? | BFS en y BFS en | Sí/no |
| ¿Cuáles son todas las SCC de ? | DFS en y DFS en en orden decreciente de | Partición |
| ¿Cómo se relacionan las SCC? | Condensación | Un DAG de componentes |
5. Lema del pizarrón: agregar una arista a un árbol crea un ciclo
Sea un árbol y sea una arista que no pertenece a . Como en un árbol existe un único camino simple entre y , al agregar aparece el ciclo
Además, es el único ciclo nuevo: cualquier ciclo que use tendría que completar la conexión entre y con un camino en , y ese camino es único.
Este lema explica por qué una gráfica conexa con más de aristas no puede ser un árbol y aparece como herramienta natural al comparar árboles producidos por BFS y DFS con la gráfica original.
Ejercicio visto en pantalla
Sea una gráfica no dirigida y conexa. Un BFS desde produce un árbol y un DFS produce un bosque . Tras omitir la orientación de sus aristas, demostrar que si , entonces .
6. Laboratorio interactivo
El laboratorio permite comparar la prueba de conectividad fuerte con dos BFS, seguir las fases de Kosaraju y experimentar con sistemas de monedas donde la regla voraz acierta o falla.
Si el recurso no carga
- Dos BFS: el recorrido en verifica para todo ; el recorrido en verifica .
- Kosaraju: el primer DFS ordena por tiempos de finalización; el segundo DFS, sobre , separa un árbol por cada SCC.
- Monedas: el algoritmo toma la mayor denominación que no rebasa el resto; debe compararse con un óptimo para saber si el sistema es canónico.
7. Inicio de algoritmos voraces
Un algoritmo voraz construye una solución mediante una secuencia de elecciones localmente mejores y no reconsidera decisiones anteriores.
El esquema general es:
- identificar las elecciones factibles;
- aplicar una regla local para elegir una de ellas;
- reducir el problema a una instancia más pequeña;
- repetir hasta terminar.
La rapidez o naturalidad de la regla no basta para demostrar corrección. Cada problema necesita un argumento —por ejemplo, intercambio, inducción o greedy stays ahead— que conecte las elecciones locales con un óptimo global.
8. Problema de cambio de monedas
Dadas denominaciones positivas
y una cantidad , buscamos multiplicidades enteras tales que
y que minimicen el número total de monedas
8.1 Algoritmo del cajero
En cada iteración se toma la moneda de mayor valor que no rebase la cantidad restante.
def cambio_voraz(x, monedas):
monedas = sorted(monedas)
S = []
while x > 0:
candidatas = [c for c in monedas if c <= x]
if not candidatas:
return None # no encontró una solución
c = max(candidatas)
S.append(c)
x -= c
return SSi las denominaciones ya están ordenadas, puede implementarse avanzando de la moneda mayor a la menor. El tiempo depende del número de monedas devueltas; una implementación que toma cocientes usa y procesa cada denominación una sola vez.
8.2 Ejemplos con monedas estadounidenses
Para centavos:
El algoritmo devuelve monedas.
Para \2.89$:
por lo que devuelve monedas.
9. La regla voraz no es universal
9.1 Puede devolver demasiadas monedas
Con las denominaciones postales
y , el algoritmo del cajero produce
con monedas. Sin embargo, la solución óptima es
con solo monedas.
9.2 Incluso puede quedarse atascada
Con y , la regla elige primero y deja un residuo de , que no puede pagarse. No obstante,
sí es una solución factible.
Lección
Tener una moneda de valor garantiza que la estrategia siempre termina con una representación, pero no garantiza que use el menor número de monedas. Sin moneda de valor , la estrategia puede fracasar aun cuando exista una solución.
10. Por qué sí funciona para las monedas estadounidenses
Consideremos . Toda solución óptima satisface estas restricciones:
| Restricción | Justificación por mejora local |
|---|---|
| Cinco monedas de se reemplazan por una de . | |
| Dos monedas de se reemplazan por una de . | |
| Cuatro monedas de se reemplazan por una de . | |
| Tres monedas de se reemplazan por ; dos de y una de se reemplazan por . |
Aquí denotan el número de monedas de centavos, respectivamente.
10.1 Consecuencia de las restricciones
En una solución óptima que todavía no usa la siguiente denominación, el máximo valor posible es:
| Moneda que elige greedy | Máximo con monedas menores en un óptimo | Conclusión |
|---|---|---|
| Si , algún es obligatorio. | ||
| Si , algún es obligatorio. | ||
| Si , algún es obligatorio. | ||
| Si , algún es obligatorio. |
10.2 Prueba inductiva de optimalidad
Sea la mayor denominación tal que . El algoritmo voraz elige .
Las cotas anteriores demuestran que cualquier solución óptima para también debe contener al menos una moneda : usando solo denominaciones menores no podría alcanzarse sin violar una de las restricciones de optimalidad.
Después de fijar esa moneda, queda la subinstancia . Por hipótesis inductiva, el algoritmo resuelve óptimamente ese resto. Por tanto, la solución completa también es óptima.
Alcance de la prueba
La demostración depende de relaciones aritméticas específicas entre y . No prueba que la estrategia funcione para cualquier conjunto de denominaciones.
11. Errores conceptuales frecuentes
| Error | Corrección |
|---|---|
| Un BFS desde basta para comprobar conectividad fuerte. | Solo prueba que alcanza a todos; falta verificar que todos alcanzan a . |
| Invertir aristas cambia las SCC. | No: dentro de una SCC siguen existiendo caminos en ambos sentidos. Lo que se invierte es el DAG de condensación. |
| Cada ejecución de DFS en produce una SCC sin importar el orden. | El orden decreciente de finalización del primer DFS es esencial. |
| Todo algoritmo voraz es óptimo. | Una regla voraz solo es una heurística hasta que se demuestra su propiedad de elección segura. |
| Si existe una moneda de , greedy es óptimo. | La moneda de garantiza factibilidad, no optimalidad. |
12. Preguntas de autoevaluación
- ¿Por qué un recorrido en desde representa a los vértices que pueden alcanzar en ?
- ¿Qué diferencia de salida hay entre la prueba con dos BFS y Kosaraju–Sharir?
- ¿Por qué el segundo DFS de Kosaraju debe procesar vértices en orden decreciente de ?
- Para y , ¿qué devuelve greedy y cuál es el óptimo?
- ¿Qué parte de la prueba para monedas estadounidenses deja de ser válida al cambiar las denominaciones?
Respuestas breves
- Cada camino en se convierte, al invertir sus aristas, en un camino en .
- Dos BFS devuelven una decisión global; Kosaraju devuelve la partición completa en SCC.
- Ese orden hace que cada nuevo DFS comience en una componente que no puede escapar hacia otra componente no procesada en .
- Greedy: (3 monedas). Óptimo: (2 monedas).
- Las cotas de reemplazo entre monedas menores y la siguiente denominación dependen de los valores concretos del sistema estadounidense.
Conexiones
- Clase anterior: TopoSort con DFS, SCC y condensación.
- Antecedente de BFS: Bipartición y conectividad en grafos dirigidos.
- Recurso interactivo: Dos BFS, Kosaraju y cambio de monedas.
- Siguiente bloque de la presentación: selección de intervalos por tiempo de finalización más temprano.
Bibliografía
- Kleinberg, J. y Tardos, É. (2006). Algorithm Design. Pearson/Addison-Wesley. Cap. 3, conectividad en grafos dirigidos; cap. 4, algoritmos voraces.
- Wayne, K. Graphs (
03Graphs.pdf), diapositivas 40–43: alcanzabilidad dirigida, conectividad fuerte y componentes fuertes. - Wayne, K. (2020). Greedy Algorithms I (
04GreedyAlgorithmsI.pdf), diapositivas 1–8: cambio de monedas, contraejemplos y optimalidad para denominaciones estadounidenses. - Cormen, T. H., Leiserson, C. E., Rivest, R. L. y Stein, C. (2009). Introduction to Algorithms, 3.ª ed. MIT Press. Secciones 22.2 y 22.5.
- Sharir, M. (1981). “A strong-connectivity algorithm and its applications in data flow analysis”. Computers & Mathematics with Applications, 7(1), 67–72.
- Tarjan, R. E. (1972). “Depth-First Search and Linear Graph Algorithms”. SIAM Journal on Computing, 1(2), 146–160.
Resumen después de clase
La conectividad fuerte puede certificarse en tiempo lineal con un recorrido en y otro en . Para descomponer un digrafo arbitrario, Kosaraju combina los tiempos de finalización de un primer DFS con un segundo DFS sobre el transpuesto, obteniendo una SCC por árbol. La condensación de esas componentes siempre es un DAG. El cambio de monedas mostró la idea y el riesgo central de greedy: una elección local debe justificarse con propiedades específicas del problema; no basta con que parezca natural.
Acciones adicionales
- Resolver formalmente el ejercicio de la Tarea 3 sobre la coincidencia entre los árboles de BFS y DFS.
- Practicar una traza manual de Kosaraju indicando , , y los árboles del segundo DFS.
- Buscar otro sistema de monedas no canónico y construir un contraejemplo mínimo para greedy.