Skip to Content

Algoritmo húngaro para resolver el problema de asignación

Enunciado del problema de asignación

Hay varias formulaciones estándar del problema de asignación (assignment problem) (todas esencialmente equivalentes). Aquí hay algunas de ellas:

  • Hay nn trabajos y nn trabajadores. Cada trabajador especifica la cantidad de dinero que espera por un trabajo particular. Cada trabajador puede asignarse a solo un trabajo. El objetivo es asignar trabajos a trabajadores de forma que se minimice el costo total.

  • Dada una matriz n×nn \times n AA, la tarea es seleccionar un número de cada fila de modo que se elija exactamente un número de cada columna, y la suma de los números seleccionados se minimice.

  • Dada una matriz n×nn \times n AA, la tarea es encontrar una permutación pp de longitud nn tal que el valor A[i][p[i]]\sum A[i]\left[p[i]\right] se minimice.

  • Consideremos un grafo bipartito completo con nn vértices por parte, donde a cada arista se le asigna un peso. El objetivo es encontrar un matching perfecto con el peso total mínimo.

Es importante notar que todos los escenarios de arriba son problemas “cuadrados”, lo que significa que ambas dimensiones son siempre iguales a nn. En la práctica, a menudo se encuentran formulaciones “rectangulares” similares, donde nn no es igual a mm, y la tarea es seleccionar min(n,m)\min(n,m) elementos. Sin embargo, se puede observar que un problema “rectangular” siempre se puede transformar en un problema “cuadrado” agregando filas o columnas con valores cero o infinitos, respectivamente.

También notamos que, por analogía con la búsqueda de una solución mínima, también se puede plantear el problema de encontrar una solución máxima. Sin embargo, estos dos problemas son equivalentes entre sí: basta con multiplicar todos los pesos por 1-1.

Algoritmo húngaro

Referencia histórica

El algoritmo fue desarrollado y publicado por Harold Kuhn en 1955. El propio Kuhn le dio el nombre “húngaro” porque se basaba en el trabajo anterior de los matemáticos húngaros Dénes Kőnig y Jenő Egerváry.
En 1957, James Munkres mostró que este algoritmo corre en tiempo polinómico (estrictamente), independiente del costo.
Por lo tanto, en la literatura, este algoritmo se conoce no solo como el “húngaro”, sino también como el “algoritmo de Kuhn-Munkres” o “algoritmo de Munkres”.
Sin embargo, recientemente se descubrió en 2006 que el mismo algoritmo fue inventado un siglo antes que Kuhn por el matemático alemán Carl Gustav Jacobi. Su trabajo, About the research of the order of a system of arbitrary ordinary differential equations, que se publicó de forma póstuma en 1890, contenía, entre otros hallazgos, un algoritmo polinómico para resolver el problema de asignación. Desafortunadamente, como la publicación estaba en latín, pasó desapercibida entre los matemáticos.

También vale la pena notar que el algoritmo original de Kuhn tenía una complejidad asintótica de O(n4)\mathcal{O}(n^4), y solo más tarde Jack Edmonds y Richard Karp (e independientemente Tomizawa) mostraron cómo mejorarlo a una complejidad asintótica de O(n3)\mathcal{O}(n^3).

El algoritmo O(n4)\mathcal{O}(n^4)

Para evitar ambigüedad, notamos de inmediato que nos ocupamos principalmente del problema de asignación en una formulación matricial (es decir, dada una matriz AA, hay que seleccionar nn celdas de ella que estén en distintas filas y columnas). Indexamos los arreglos empezando en 11, es decir, por ejemplo, una matriz AA tiene índices A[1n][1n]A[1 \dots n][1 \dots n].

También asumiremos que todos los números de la matriz A son no negativos (si no es el caso, siempre se puede hacer la matriz no negativa sumando alguna constante a todos los números).

Llamemos potencial a dos arreglos arbitrarios de números u[1n]u[1 \ldots n] y v[1n]v[1 \ldots n], tales que se cumple la siguiente condición:

u[i]+v[j]A[i][j],i=1n, j=1nu[i]+v[j]\leq A[i][j],\quad i=1\dots n,\ j=1\dots n

(Como se puede ver, u[i]u[i] corresponde a la ii-ésima fila, y v[j]v[j] corresponde a la jj-ésima columna de la matriz).

Llamemos valor ff del potencial a la suma de sus elementos:

f=i=1nu[i]+j=1nv[j].f=\sum_{i=1}^{n} u[i] + \sum_{j=1}^{n} v[j].

Por un lado, es fácil ver que el costo de la solución deseada solsol no es menor que el valor de cualquier potencial.

Información

Lema. solf.sol\geq f.

Demostración

La solución deseada del problema consiste de nn celdas de la matriz AA, así que u[i]+v[j]A[i][j]u[i]+v[j]\leq A[i][j] para cada una de ellas. Como todos los elementos de solsol están en distintas filas y columnas, al sumar estas desigualdades sobre todos los A[i][j]A[i][j] seleccionados, se obtiene ff en el lado izquierdo de la desigualdad, y solsol en el lado derecho.

Por otro lado, resulta que siempre hay una solución y un potencial que convierte esta desigualdad en igualdad. El algoritmo húngaro descrito abajo será una demostración constructiva de este hecho. Por ahora, solo prestemos atención al hecho de que si cualquier solución tiene un costo igual a cualquier potencial, entonces esta solución es óptima.

Fijemos algún potencial. Llamemos a una arista (i,j)(i,j) rígida si u[i]+v[j]=A[i][j].u[i]+v[j]=A[i][j].

Recordemos una formulación alternativa del problema de asignación, usando un grafo bipartito. Denotemos con HH un grafo bipartito compuesto solo de aristas rígidas. El algoritmo húngaro mantendrá, para el potencial actual, el matching de máximo número de aristas MM del grafo HH. En cuanto MM contenga nn aristas, entonces la solución al problema será simplemente MM (después de todo, será una solución cuyo costo coincide con el valor de un potencial).

Pasemos directamente a la descripción del algoritmo.

Paso 1. Al principio, se asume que el potencial es cero (u[i]=v[i]=0u[i]=v[i]=0 para todo ii), y se asume que el matching MM está vacío.

Paso 2. Luego, en cada paso del algoritmo, intentamos, sin cambiar el potencial, aumentar la cardinalidad del matching actual MM en uno (recordemos que el matching se busca en el grafo de aristas rígidas HH). Para ello se usa el algoritmo de Kuhn usual para encontrar el matching máximo en grafos bipartitos. Recordemos el algoritmo aquí. Todas las aristas del matching MM se orientan en la dirección de la parte derecha a la izquierda, y todas las demás aristas del grafo HH se orientan en la dirección opuesta.

Recordemos (de la terminología de búsqueda de matchings) que un vértice se llama saturado si una arista del matching actual es adyacente a él. Un vértice que no es adyacente a ninguna arista del matching actual se llama no saturado. Un camino de longitud impar, en el que la primera arista no pertenece al matching, y para todas las aristas posteriores hay una pertenencia alternada al matching (pertenece/no pertenece) se llama camino aumentante. Desde todos los vértices no saturados de la parte izquierda se inicia un recorrido en profundidad o en anchura. Si, como resultado de la búsqueda, se pudo alcanzar un vértice no saturado de la parte derecha, hemos encontrado un camino aumentante de la parte izquierda a la derecha. Si incluimos las aristas impares del camino y quitamos las pares en el matching (es decir, incluimos la primera arista en el matching, excluimos la segunda, incluimos la tercera, etc.), entonces aumentaremos la cardinalidad del matching en uno.

Si no había camino aumentante, entonces el matching actual MM es maximal en el grafo HH.

Paso 3. Si en el paso actual no es posible aumentar la cardinalidad del matching actual, entonces se realiza un recálculo del potencial de tal forma que, en los siguientes pasos, habrá más oportunidades de aumentar el matching.

Denotemos por Z1Z_1 el conjunto de vértices de la parte izquierda que se visitaron durante el último recorrido del algoritmo de Kuhn, y por Z2Z_2 el conjunto de vértices visitados de la parte derecha.

Calculemos el valor Δ\Delta:

Δ=miniZ1, jZ2A[i][j]u[i]v[j].\Delta = \min_{i\in Z_1,\ j\notin Z_2} A[i][j]-u[i]-v[j].

Información

Lema. Δ>0.\Delta > 0.

Demostración

Supongamos Δ=0\Delta=0. Entonces existe una arista rígida (i,j)(i,j) con iZ1i\in Z_1 y jZ2j\notin Z_2. Se sigue que la arista (i,j)(i,j) debe estar orientada de la parte derecha a la izquierda, es decir, (i,j)(i,j) debe estar incluida en el matching MM. Sin embargo, esto es imposible, porque no podríamos llegar al vértice saturado ii excepto yendo a lo largo de la arista de j a i. Así que Δ>0\Delta > 0.

Ahora recalculemos el potencial de esta forma:

  • para todos los vértices iZ1i\in Z_1, hacer u[i]u[i]+Δu[i] \gets u[i]+\Delta,

  • para todos los vértices jZ2j\in Z_2, hacer v[j]v[j]Δv[j] \gets v[j]-\Delta.

Información

Lema. El potencial resultante sigue siendo un potencial correcto.

Demostración

Mostraremos que, después del recálculo, u[i]+v[j]A[i][j]u[i]+v[j]\leq A[i][j] para todo i,ji,j. Para todos los elementos de AA con iZ1i\in Z_1 y jZ2j\in Z_2, la suma u[i]+v[j]u[i]+v[j] no cambia, así que la desigualdad sigue siendo cierta. Para todos los elementos con iZ1i\notin Z_1 y jZ2j\in Z_2, la suma u[i]+v[j]u[i]+v[j] disminuye en Δ\Delta, así que la desigualdad sigue siendo cierta. Para los demás elementos cuyos iZ1i\in Z_1 y jZ2j\notin Z_2, la suma aumenta, pero la desigualdad se preserva, ya que el valor Δ\Delta es, por definición, el aumento máximo que no cambia la desigualdad.

Información

Lema. El matching viejo MM de aristas rígidas es válido, es decir, todas las aristas del matching seguirán siendo rígidas.

Demostración

Para que alguna arista rígida (i,j)(i,j) deje de ser rígida como resultado de un cambio de potencial, es necesario que la igualdad u[i]+v[j]=A[i][j]u[i] + v[j] = A[i][j] se convierta en la desigualdad u[i]+v[j]<A[i][j]u[i] + v[j] < A[i][j]. Sin embargo, esto solo puede ocurrir cuando iZ1i \notin Z_1 y jZ2j \in Z_2. Pero iZ1i \notin Z_1 implica que la arista (i,j)(i,j) no podría ser una arista del matching.

Información

Lema. Después de cada recálculo del potencial, el número de vértices alcanzables por el recorrido, es decir, Z1+Z2|Z_1|+|Z_2|, aumenta de forma estricta.

Demostración

Primero, notemos que cualquier vértice que era alcanzable antes del recálculo sigue siendo alcanzable. En efecto, si algún vértice es alcanzable, entonces hay algún camino de vértices alcanzables hasta él, empezando desde el vértice no saturado de la parte izquierda; como para aristas de la forma (i,j), iZ1, jZ2(i,j),\ i\in Z_1,\ j\in Z_2 la suma u[i]+v[j]u[i]+v[j] no cambia, todo este camino se preservará después de cambiar el potencial. Segundo, mostramos que después de un recálculo, al menos un vértice nuevo será alcanzable. Esto se sigue de la definición de Δ\Delta: la arista (i,j)(i,j) a la que se refiere Δ\Delta se volverá rígida, así que el vértice jj será alcanzable desde el vértice ii.

Por el último lema, no pueden ocurrir más de nn recálculos de potencial antes de que se encuentre un camino aumentante y se aumente la cardinalidad del matching de MM. Así, tarde o temprano, se encontrará un potencial que corresponde a un matching perfecto MM^, y MM^ será la respuesta al problema. Si hablamos de la complejidad del algoritmo, entonces es O(n4)\mathcal{O}(n^4): en total debe haber a lo sumo nn aumentos del matching, antes de cada uno de los cuales hay no más de nn recálculos de potencial, cada uno de los cuales se realiza en tiempo O(n2)\mathcal{O}(n^2).

No daremos aquí la implementación del algoritmo O(n4)\mathcal{O}(n^4), ya que no resultará más corta que la implementación del de O(n3)\mathcal{O}(n^3), descrita abajo.

El algoritmo O(n3)\mathcal{O}(n^3)

Ahora aprendamos cómo implementar el mismo algoritmo en O(n3)\mathcal{O}(n^3) (para problemas rectangulares n×mn \times m, O(n2m)\mathcal{O}(n^2m)).

La idea clave es considerar las filas de la matriz una por una, y no todas a la vez. Así, el algoritmo descrito arriba tomará la siguiente forma:

  1. Considerar la siguiente fila de la matriz AA.

  2. Mientras no haya un camino aumentante que empiece en esta fila, recalcular el potencial.

  3. En cuanto se encuentre un camino aumentante, propagar el matching a lo largo de él (incluyendo así la última arista en el matching), y reiniciar desde el paso 1 (para considerar la siguiente línea).

Para alcanzar la complejidad requerida, es necesario implementar los pasos 2-3, que se realizan para cada fila de la matriz, en tiempo O(n2)\mathcal{O}(n^2) (para problemas rectangulares en O(nm)\mathcal{O}(nm)).

Para ello, recordemos dos hechos demostrados arriba:

  • Con un cambio en el potencial, los vértices que eran alcanzables por el recorrido de Kuhn seguirán siendo alcanzables.

  • En total, solo podían ocurrir O(n)\mathcal{O}(n) recálculos del potencial antes de que se encontrara un camino aumentante.

De esto se siguen estas ideas clave que nos permiten alcanzar la complejidad requerida:

  • Para comprobar la presencia de un camino aumentante, no hay necesidad de iniciar el recorrido de Kuhn otra vez después de cada recálculo de potencial. En su lugar, se puede hacer el recorrido de Kuhn en forma iterativa: después de cada recálculo del potencial, mirar las aristas rígidas agregadas y, si sus extremos izquierdos eran alcanzables, marcar sus extremos derechos como alcanzables también y continuar el recorrido desde ellos.

  • Desarrollando esta idea más, podemos presentar el algoritmo de la siguiente forma: en cada paso del bucle, se recalcula el potencial. Posteriormente, se identifica una columna que se ha vuelto alcanzable (que siempre existirá ya que emergen vértices alcanzables nuevos después de cada recálculo de potencial). Si la columna no está saturada, se descubre una cadena aumentante. Por el contrario, si la columna está saturada, la fila del matching también se vuelve alcanzable.

  • Para recalcular el potencial de forma rápida (más rápido que la versión naive O(n2)\mathcal{O}(n^2)), hay que mantener mínimos auxiliares para cada una de las columnas:


    minv[j]=miniZ1A[i][j]u[i]v[j].minv[j]=\min_{i\in Z_1} A[i][j]-u[i]-v[j].

    Es fácil ver que el valor deseado Δ\Delta se expresa en términos de ellos de la siguiente forma:


    Δ=minjZ2minv[j].\Delta=\min_{j\notin Z_2} minv[j].

    Así, encontrar Δ\Delta ahora se puede hacer en O(n)\mathcal{O}(n).

    Es necesario actualizar el arreglo minvminv cuando aparecen filas visitadas nuevas. Esto se puede hacer en O(n)\mathcal{O}(n) para la fila agregada (lo que suma sobre todas las filas a O(n2)\mathcal{O}(n^2)). También es necesario actualizar el arreglo minvminv al recalcular el potencial, lo que también se hace en tiempo O(n)\mathcal{O}(n) (minvminv cambia solo para las columnas que aún no se han alcanzado: a saber, disminuye en Δ\Delta).

Así, el algoritmo toma la siguiente forma: en el bucle externo, consideramos las filas de la matriz una por una. Cada fila se procesa en tiempo O(n2)\mathcal{O}(n^2), ya que solo podían ocurrir O(n)\mathcal{O}(n) recálculos de potencial (cada uno en tiempo O(n)\mathcal{O}(n)), y el arreglo minvminv se mantiene en tiempo O(n2)\mathcal{O}(n^2); el algoritmo de Kuhn funcionará en tiempo O(n2)\mathcal{O}(n^2) (ya que se presenta en forma de O(n)\mathcal{O}(n) iteraciones, cada una de las cuales visita una columna nueva).

La complejidad resultante es O(n3)\mathcal{O}(n^3) o, si el problema es rectangular, O(n2m)\mathcal{O}(n^2m).

Implementación del algoritmo húngaro

La implementación de abajo fue desarrollada por Andrey Lopatin hace varios años. Se distingue por una concisión asombrosa: todo el algoritmo consiste de 30 líneas de código.

La implementación encuentra una solución para la matriz rectangular A[1n][1m]A[1\dots n][1\dots m], donde nmn\leq m. La matriz es 1-based por conveniencia y brevedad del código: esta implementación introduce una fila cero ficticia y una columna cero, lo que nos permite escribir muchos ciclos de forma general, sin comprobaciones adicionales.

Los arreglos u[0n]u[0 \ldots n] y v[0m]v[0 \ldots m] guardan el potencial. Inicialmente, se ponen en cero, lo cual es consistente con una matriz de cero filas (Nótese que para esta implementación no es importante si la matriz AA contiene o no números negativos).

El arreglo p[0m]p[0 \ldots m] contiene un matching: para cada columna j=1mj = 1 \ldots m, guarda el número p[j]p[j] de la fila seleccionada (o 00 si aún no se ha seleccionado nada). Por conveniencia de la implementación, se asume que p[0]p[0] es igual al número de la fila actual.

El arreglo minv[1m]minv[1 \ldots m] contiene, para cada columna jj, los mínimos auxiliares necesarios para un recálculo rápido del potencial, como se describió arriba.

El arreglo way[1m]way[1 \ldots m] contiene información sobre dónde se alcanzan estos mínimos para que luego podamos reconstruir el camino aumentante. Nótese que, para reconstruir el camino, basta con guardar solo valores de columna, ya que los números de fila se pueden tomar del matching (es decir, del arreglo pp). Así, way[j]way[j], para cada columna jj, contiene el número de la columna anterior en el camino (o 00 si no hay ninguna).

El algoritmo mismo es un bucle externo a través de las filas de la matriz, dentro del cual se considera la ii-ésima fila de la matriz. El primer bucle do-while corre hasta que se encuentra una columna libre j0j0. Cada iteración del bucle marca como visitada una columna nueva con el número j0j0 (calculado en la última iteración; e inicialmente igual a cero, es decir, empezamos desde una columna ficticia), así como una fila nueva i0i0 adyacente a ella en el matching (es decir, p[j0]p[j0]; e inicialmente cuando j0=0j0=0 se toma la ii-ésima fila). Debido a la aparición de una fila visitada nueva i0i0, hay que recalcular el arreglo minvminv y Δ\Delta en consecuencia. Si Δ\Delta se actualiza, entonces la columna j1j1 se vuelve el mínimo que se ha alcanzado (nótese que con tal implementación Δ\Delta podría resultar igual a cero, lo que significa que el potencial no se puede cambiar en el paso actual: ya hay una columna alcanzable nueva). Después de eso, se recalculan el potencial y el arreglo minvminv. Al final del bucle “do-while”, encontramos un camino aumentante que termina en una columna j0j0 que se puede “desenrollar” usando el arreglo de ancestros wayway.

La constante INF es “infinito”, es decir, algún número, obviamente mayor que todos los números posibles de la matriz de entrada AA.

vector<int> u (n+1), v (m+1), p (m+1), way (m+1); for (int i=1; i<=n; ++i) { p[0] = i; int j0 = 0; vector<int> minv (m+1, INF); vector<bool> used (m+1, false); do { used[j0] = true; int i0 = p[j0], delta = INF, j1; for (int j=1; j<=m; ++j) if (!used[j]) { int cur = A[i0][j]-u[i0]-v[j]; if (cur < minv[j]) minv[j] = cur, way[j] = j0; if (minv[j] < delta) delta = minv[j], j1 = j; } for (int j=0; j<=m; ++j) if (used[j]) u[p[j]] += delta, v[j] -= delta; else minv[j] -= delta; j0 = j1; } while (p[j0] != 0); do { int j1 = way[j0]; p[j0] = p[j1]; j0 = j1; } while (j0); }

Para restaurar la respuesta en una forma más familiar, es decir, encontrar para cada fila i=1ni = 1 \ldots n el número ans[i]ans[i] de la columna seleccionada en ella, se puede hacer de la siguiente forma:

vector<int> ans (n+1); for (int j=1; j<=m; ++j) ans[p[j]] = j;

El costo del matching se puede tomar simplemente como el potencial de la columna cero (tomado con el signo opuesto). En efecto, como se puede ver del código, v[0]-v[0] contiene la suma de todos los valores de Δ\Delta, es decir, el cambio total en el potencial. Aunque varios valores de u[i]u[i] y v[j]v[j] podrían cambiar a la vez, el cambio total en el potencial es exactamente igual a Δ\Delta, ya que hasta que no hay un camino aumentante, el número de filas alcanzables es exactamente uno más que el número de las columnas alcanzables (solo la fila actual ii no tiene un “par” en forma de una columna visitada):

int cost = -v[0];

Conexión con el algoritmo de caminos más cortos sucesivos

El algoritmo húngaro se puede ver como el algoritmo de caminos más cortos sucesivos (Successive Shortest Path Algorithm), adaptado para el problema de asignación. Sin entrar en los detalles, demos una intuición respecto de la conexión entre ellos.

El algoritmo de caminos sucesivos usa una versión modificada del algoritmo de Johnson como técnica de reponderación. Esta se divide en cuatro pasos:

  • Usar el algoritmo de Bellman-Ford, empezando desde el sumidero ss y, para cada nodo, encontrar el peso mínimo h(v)h(v) de un camino de ss a vv.

Para cada paso del algoritmo principal:

  • Reponderar las aristas del grafo original de esta forma: w(u,v)w(u,v)+h(u)h(v)w(u,v) \gets w(u,v)+h(u)-h(v).
  • Usar el algoritmo de Dijkstra para encontrar el subgrafo de caminos más cortos de la red original.
  • Actualizar los potenciales para la siguiente iteración.

Dada esta descripción, podemos observar que hay una analogía fuerte entre h(v)h(v) y los potenciales: se puede comprobar que son iguales salvo un desplazamiento constante. Además, se puede mostrar que, después de reponderar, el conjunto de todas las aristas de peso cero representa el subgrafo de caminos más cortos donde el algoritmo principal intenta aumentar el flujo. Esto también ocurre en el algoritmo húngaro: creamos un subgrafo hecho de aristas rígidas (aquellas para las que la cantidad A[i][j]u[i]v[j]A[i][j]-u[i]-v[j] es cero), e intentamos aumentar el tamaño del matching.

En el paso 4, todos los h(v)h(v) se actualizan: cada vez que modificamos la red de flujo, debemos garantizar que las distancias desde la fuente son correctas (de lo contrario, en la siguiente iteración, el algoritmo de Dijkstra podría fallar). Esto suena como la actualización realizada sobre los potenciales, pero en este caso, no se incrementan de forma igual.

Para profundizar la comprensión de los potenciales, consultar este artículo .

Ejemplos de tareas

Aquí hay unos pocos ejemplos relacionados con el problema de asignación, desde tareas muy triviales hasta menos obvias:

  • Dado un grafo bipartito, se requiere encontrar en él el matching máximo con el peso mínimo (es decir, en primer lugar se maximiza el tamaño del matching, y en segundo lugar se minimiza su costo).
    Para resolverlo, simplemente construimos un problema de asignación, poniendo el número “infinito” en lugar de las aristas que faltan. Después de eso, resolvemos el problema con el algoritmo húngaro, y quitamos las aristas de peso infinito de la respuesta (podrían entrar en la respuesta si el problema no tiene una solución en forma de matching perfecto).

  • Dado un grafo bipartito, se requiere encontrar en él el matching máximo con el peso máximo.
    La solución es otra vez obvia: todos los pesos deben multiplicarse por menos uno.

  • La tarea de detectar objetos en movimiento en imágenes: se tomaron dos imágenes, como resultado de lo cual se obtuvieron dos conjuntos de coordenadas. Se requiere correlacionar los objetos de la primera y la segunda imagen, es decir, determinar para cada punto de la segunda imagen a qué punto de la primera imagen correspondía. En este caso, se requiere minimizar la suma de distancias entre los puntos comparados (es decir, buscamos una solución en la que los objetos han tomado el camino más corto en total).
    Para resolverlo, simplemente construimos y resolvemos un problema de asignación, donde los pesos de las aristas son las distancias euclidianas entre puntos.

  • La tarea de detectar objetos en movimiento por localizadores: hay dos localizadores que no pueden determinar la posición de un objeto en el espacio, sino solo su dirección. Ambos localizadores (ubicados en puntos distintos) recibieron información en forma de nn de tales direcciones. Se requiere determinar la posición de los objetos, es decir, determinar las posiciones esperadas de los objetos y sus pares correspondientes de direcciones de tal forma que se minimice la suma de distancias de los objetos a los rayos de dirección.
    Solución: otra vez, simplemente construimos y resolvemos el problema de asignación, donde los vértices de la parte izquierda son las nn direcciones del primer localizador, los vértices de la parte derecha son las nn direcciones del segundo localizador, y los pesos de las aristas son las distancias entre los rayos correspondientes.

  • Cubrir un grafo dirigido acíclico con caminos: dado un grafo dirigido acíclico, se requiere encontrar el menor número de caminos (si hay empate, con el menor peso total) de modo que cada vértice del grafo yazca en exactamente un camino.
    La solución es construir el grafo bipartito correspondiente a partir del grafo dado y encontrar el matching máximo de peso mínimo en él. Véase un artículo separado para más detalles.

  • Libro para colorear un árbol. Dado un árbol en el que cada vértice, excepto las hojas, tiene exactamente k1k-1 hijos. Se requiere elegir para cada vértice uno de los kk colores disponibles de modo que no haya dos vértices adyacentes con el mismo color. Además, para cada vértice y cada color se conoce el costo de pintar este vértice con este color, y se requiere minimizar el costo total.
    Para resolver este problema, usamos programación dinámica. A saber, aprendamos a calcular el valor d[v][c]d[v][c], donde vv es el número de vértice, cc es el número de color, y el valor d[v][c]d[v][c] mismo es el costo mínimo necesario para colorear todos los vértices del subárbol enraizado en vv, y el vértice vv mismo con color cc. Para calcular tal valor d[v][c]d[v][c], es necesario distribuir los k1k-1 colores restantes entre los hijos del vértice vv, y para ello es necesario construir y resolver el problema de asignación (en el que los vértices de la parte izquierda son colores, los vértices de la parte derecha son hijos, y los pesos de las aristas son los valores correspondientes de dd).
    Así, cada valor d[v][c]d[v][c] se calcula usando la solución del problema de asignación, lo que al final da la asintótica O(nk4)\mathcal{O}(nk^4).

  • Si, en el problema de asignación, los pesos no están en las aristas, sino en los vértices, y solo en los vértices de la misma parte, entonces no es necesario usar el algoritmo húngaro: solo hay que ordenar los vértices por peso y ejecutar el algoritmo de Kuhn usual (para más detalles, véase un artículo separado ).

  • Consideremos el siguiente caso especial. Sea que a cada vértice de la parte izquierda se le asigna algún número α[i]\alpha[i], y a cada vértice de la parte derecha β[j]\beta[j]. Sea el peso de cualquier arista (i,j)(i,j) igual a α[i]β[j]\alpha[i]\cdot \beta[j] (los números α[i]\alpha[i] y β[j]\beta[j] son conocidos). Resolver el problema de asignación.
    Para resolverlo sin el algoritmo húngaro, primero consideramos el caso cuando ambas partes tienen dos vértices. En este caso, como se puede ver fácilmente, es mejor conectar los vértices en el orden inverso: conectar el vértice con el menor α[i]\alpha[i] al vértice con el mayor β[j]\beta[j]. Esta regla se puede generalizar fácilmente a un número arbitrario de vértices: hay que ordenar los vértices de la primera parte en orden creciente de los valores α[i]\alpha[i], la segunda parte en orden decreciente de los valores β[j]\beta[j], y conectar los vértices en pares en ese orden. Así, obtenemos una solución con complejidad O(nlogn)\mathcal{O}(n\log n).

  • El problema de los potenciales. Dada una matriz A[1n][1m]A[1 \ldots n][1 \ldots m], se requiere encontrar dos arreglos u[1n]u[1 \ldots n] y v[1m]v[1 \ldots m] tales que, para cualquier ii y jj, u[i]+v[j]a[i][j]u[i] + v[j] \leq a[i][j] y la suma de elementos de los arreglos uu y vv sea máxima.
    Conociendo el algoritmo húngaro, la solución a este problema no será difícil: el algoritmo húngaro justamente encuentra tal potencial u,vu, v que satisface la condición del problema. Por otro lado, sin conocimiento del algoritmo húngaro, parece casi imposible resolver tal problema.

Observación

Esta tarea también se llama el problema dual del problema de asignación: minimizar el costo total de la asignación es equivalente a maximizar la suma de los potenciales.

Literatura

Problemas de práctica