35. Emparejamientos en grafos

Un emparejamiento selecciona aristas sin compartir extremos. Este modelo permite asignar personas a tareas, estudiantes a proyectos o recursos a solicitudes sin utilizar dos veces el mismo elemento.

35.1 Introducción

Muchos problemas consisten en formar parejas compatibles: trabajadores con puestos, máquinas con operaciones o participantes con horarios. Un grafo representa las compatibilidades y un emparejamiento representa una selección sin conflictos.

El objetivo más común es encontrar la mayor cantidad de parejas simultáneas, aunque también existen variantes con pesos, preferencias o requisitos de estabilidad.

35.2 Definición de emparejamiento

Un emparejamiento M es un subconjunto de aristas tal que ningún vértice aparece en más de una arista seleccionada.

Si e₁, e₂ ∈ M y e₁ ≠ e₂, entonces e₁ y e₂ no comparten extremos.

El tamaño |M| es la cantidad de aristas seleccionadas, equivalente a la cantidad de parejas formadas.

35.3 Vértices libres y emparejados

Un vértice está emparejado si alguna arista de M incide en él. En caso contrario está libre o no saturado.

Cada arista del emparejamiento satura exactamente dos vértices. Por eso un emparejamiento de tamaño k contiene 2k vértices emparejados.

35.4 Máximo no significa maximal

Un emparejamiento maximal no admite agregar directamente otra arista. Un emparejamiento máximo posee la mayor cantidad posible de aristas.

máximo ⇒ maximal, pero maximal ⇏ máximo

Una selección voraz puede quedar bloqueada en un resultado maximal pequeño aunque una reasignación permita formar más parejas.

35.5 Emparejamiento perfecto

Un emparejamiento es perfecto cuando cubre todos los vértices. Solo puede existir si la cantidad total de vértices es par.

En un grafo bipartito, ambas particiones deben tener la misma cantidad de vértices para que exista un emparejamiento perfecto, aunque esa condición por sí sola no es suficiente.

35.6 Simulación de caminos aumentantes

Avanza para incorporar cada vértice del conjunto izquierdo. La segunda iteración reasigna A mediante el camino alternante B–1–A–2 y aumenta el tamaño del emparejamiento.

Parejas actuales

Ninguna pareja

Camino aumentante

Acción actual

Emparejamiento vacío.
Hay vértices libres por procesar.
Iteración0 / 4
Vértice izquierdo
Parejas0
ResultadoEn proceso

Celeste indica el emparejamiento actual y verde el camino aumentante utilizado en la última iteración.

35.7 Grafos bipartitos

Un grafo bipartito divide sus vértices en dos conjuntos L y R, y todas las aristas conectan un vértice de L con uno de R.

V = L ∪ R, L ∩ R = ∅ y E ⊆ L × R

Esta estructura representa naturalmente dos tipos de entidades y permite algoritmos de emparejamiento más sencillos que los necesarios en grafos generales.

35.8 Caminos alternantes

Un camino alternante intercala aristas que no pertenecen al emparejamiento con aristas que sí pertenecen.

fuera de M → dentro de M → fuera de M → ...

Recorrer una arista emparejada significa considerar la liberación de su pareja actual para buscarle otra opción.

35.9 Caminos aumentantes

Un camino aumentante es un camino alternante cuyos dos extremos están libres. Comienza y termina con aristas que no pertenecen a M.

Al invertir la pertenencia de todas sus aristas, se eliminan k aristas emparejadas y se agregan k + 1. El tamaño aumenta exactamente en uno.

35.10 Teorema de Berge

El teorema de Berge caracteriza completamente la optimalidad:

Un emparejamiento M es máximo si y solo si no existe un camino aumentante respecto de M.

Por tanto, buscar caminos aumentantes no es solo una estrategia de mejora: su ausencia certifica que el resultado es máximo.

35.11 Algoritmo de Kuhn

En un grafo bipartito, el algoritmo de Kuhn procesa los vértices de L. Para cada uno ejecuta DFS tratando de encontrar un vértice libre en R o de reasignar recursivamente al ocupante actual.

function aumentar(u, grafo, parejaDerecha, visitado) {
  if (visitado.has(u)) return false;
  visitado.add(u);

  for (const v of grafo[u]) {
    if (parejaDerecha[v] === undefined ||
        aumentar(parejaDerecha[v], grafo, parejaDerecha, visitado)) {
      parejaDerecha[v] = u;
      return true;
    }
  }
  return false;
}

35.12 Implementación completa

function emparejamientoMaximo(grafo, izquierda) {
  const parejaDerecha = {};
  let cantidad = 0;

  for (const u of izquierda) {
    const visitado = new Set();
    if (aumentar(u, grafo, parejaDerecha, visitado)) cantidad++;
  }

  const parejas = Object.entries(parejaDerecha).map(
    ([derecha, izquierda]) => ({ izquierda, derecha })
  );
  return { cantidad, parejas };
}

El conjunto de visitados debe reiniciarse para cada nuevo vértice izquierdo, pero se comparte durante toda su búsqueda recursiva.

35.13 Relación con flujo máximo

El emparejamiento bipartito puede convertirse en una red de flujo:

  1. Conectar una fuente s con cada vértice de L, capacidad 1.
  2. Orientar cada arista de L hacia R, capacidad 1.
  3. Conectar cada vértice de R con un sumidero t, capacidad 1.

Cada unidad de flujo representa una pareja. Las capacidades unitarias impiden utilizar dos veces un mismo vértice.

35.14 Teorema de Hall

Un grafo bipartito posee un emparejamiento que cubre todo L si y solo si cada subconjunto S de L tiene al menos |S| vecinos diferentes.

Para todo S ⊆ L: |N(S)| ≥ |S|

Si tres solicitantes solo pueden acceder entre todos a dos recursos, es imposible asignar un recurso distinto a cada uno.

35.15 Hopcroft-Karp

Kuhn encuentra un camino aumentante por vez. Hopcroft-Karp usa BFS para organizar el grafo en capas y DFS para encontrar varios caminos aumentantes disjuntos dentro de una misma fase.

AlgoritmoIdeaComplejidad
KuhnDFS desde cada vértice izquierdoO(VE)
Hopcroft-KarpVarios caminos mínimos por faseO(E√V)

35.16 Grafos no bipartitos

En grafos generales pueden aparecer ciclos impares que complican la búsqueda de caminos aumentantes. El algoritmo de Edmonds contrae temporalmente ciertas estructuras llamadas flores o blossoms.

No debe aplicarse Kuhn directamente a un grafo no bipartito: su recursión depende de que las aristas siempre crucen entre L y R.

35.17 Emparejamientos ponderados

Si cada pareja tiene un beneficio o costo, maximizar la cantidad puede no ser suficiente. El emparejamiento de peso máximo busca maximizar la suma de pesos.

El problema de asignación bipartita ponderada puede resolverse con el algoritmo húngaro o mediante flujo de costo mínimo. Es una variante distinta del emparejamiento máximo no ponderado.

35.18 Aplicaciones y errores comunes

  • Asignar trabajadores a puestos y estudiantes a proyectos.
  • Relacionar solicitudes con servidores disponibles.
  • Programar tareas unitarias en franjas horarias.
  • No distinguir emparejamiento maximal de máximo.
  • Olvidar reiniciar los visitados para cada búsqueda.
  • Cambiar una pareja sin reasignar correctamente al ocupante anterior.
  • Usar Kuhn sin verificar que el grafo sea bipartito.

35.19 Qué debes recordar de este tema

  • Las aristas de un emparejamiento no comparten extremos.
  • Máximo y maximal son conceptos diferentes.
  • Un camino aumentante incrementa el tamaño en uno.
  • La ausencia de caminos aumentantes certifica optimalidad.
  • Kuhn resuelve emparejamiento máximo bipartito mediante DFS.
  • El problema también puede modelarse como flujo máximo.
  • Hopcroft-Karp mejora la complejidad a O(E√V).

35.20 Conclusión

Los emparejamientos convierten restricciones de exclusividad en una estructura combinatoria precisa. Los caminos aumentantes permiten revisar asignaciones anteriores hasta obtener una solución máxima.

En el próximo tema estudiaremos coloración de grafos, donde asignaremos categorías a vértices adyacentes sin que entren en conflicto.