Articulo de referencia

Algoritmo húngaro

El algoritmo húngaro o método húngaro es un algoritmo de optimización combinatoria que resuelve el problema de asignación en tiempo polinomial y que anticipó métodos primales-du...

El algoritmo húngaro o método húngaro es un algoritmo de optimización combinatoria que resuelve el problema de asignación en tiempo polinomial y que anticipó métodos primales-duales posteriores . Fue desarrollado y publicado en 1955 por Harold Kuhn , quien lo denominó "método húngaro" porque el algoritmo se basaba en gran medida en los trabajos previos de dos matemáticos húngaros, Dénes Kőnig y Jenő Egerváry . [ 1 ] [ 2 ] Sin embargo, en 2006 se descubrió que Carl Gustav Jacobi había resuelto el problema de asignación en el siglo XIX, y la solución se había publicado póstumamente en 1890 en latín. [ 3 ]

James Munkres revisó el algoritmo en 1957 y observó que es (fuertemente) polinomial . [ 4 ] Desde entonces, el algoritmo también se conoce como algoritmo de Kuhn-Munkres o algoritmo de asignación de Munkres . La complejidad temporal del algoritmo original fueO(norte4){\displaystyle O(n^{4})}Sin embargo, Edmonds y Karp , e independientemente Tomizawa, notaron que se puede modificar para lograr unO(norte3){\displaystyle O(n^{3})}tiempo de ejecución. [ 5 ] [ 6 ] Ford y Fulkerson extendieron el método a problemas generales de flujo máximo en forma del algoritmo de Ford-Fulkerson .

El problema

Ejemplo

En este sencillo ejemplo, hay tres trabajadores: Alice, Bob y Carol. Uno de ellos tiene que limpiar el baño, otro barrer los pisos y el tercero lavar las ventanas, pero cada uno exige un salario diferente por las distintas tareas. El problema consiste en encontrar la forma más económica de asignar los trabajos. El problema se puede representar mediante una matriz de los costos de los trabajadores que realizan las tareas. Por ejemplo:

El método húngaro, aplicado a la tabla anterior, daría el costo mínimo: $15, que se logra haciendo que Alice limpie el baño, Carol barra los pisos y Bob lave las ventanas. Esto se puede confirmar usando la fuerza bruta:

(La persona no asignada limpia las ventanas)

Formulación de la matriz

En la formulación matricial, se nos proporciona una matriz n × n , donde el elemento en la fila i y la columna j representa el costo de asignar el trabajo j al trabajador i . Debemos encontrar una asignación de los trabajos a los trabajadores, de manera que cada trabajo se asigne a un trabajador y cada trabajador a un trabajo, minimizando así el costo total de la asignación.

Esto se puede expresar como permutar las filas de una matriz de costos C para minimizar la traza de una matriz,

minPAGTran(PAGdo),{\displaystyle \min _{P}\operatorname {Tr} (PC)\;,}

donde P es una matriz de permutación . (De forma equivalente, las columnas se pueden permutar usando CP ).

Si el objetivo es encontrar la asignación que produzca el costo máximo , el problema se puede resolver negando la matriz de costos C.

Formulación de grafo bipartito

El algoritmo puede describirse de forma equivalente formulando el problema mediante un grafo bipartito. Disponemos de un grafo bipartito completo.GRAMO=(S,T;mi){\displaystyle G=(S,T;E)}con n vértices de trabajadores ( S ) y n vértices de trabajos ( T ), y las aristas ( E ) tienen cada una un costodo(i,j){\displaystyle c(i,j)}Queremos encontrar la combinación perfecta con el mínimo coste total.

El algoritmo en términos de grafos bipartitos

Llamemos a una funcióny:(ST)R{\displaystyle y:(S\cup T)\to \mathbb {R} }un potencial siy(i)+y(j)do(i,j){\displaystyle y(i)+y(j)\leq c(i,j)}para cadaiS,jT{\displaystyle i\in S,j\in T}.

El valor del potencial y es la suma del potencial sobre todos los vértices:

vSTy(v){\displaystyle \sum _{v\in S\cup T}y(v)}.

El coste de cada emparejamiento perfecto es al menos igual al valor de cada potencial. Esto se puede comprobar observando que el coste total del emparejamiento es la suma de los costes de todas las aristas que contiene. El coste de cada arista es al menos igual a la suma de los potenciales de sus extremos. Dado que el emparejamiento es perfecto, cada vértice es un extremo de una única arista. Por lo tanto, el coste total es al menos igual al potencial total.

El método húngaro encuentra un emparejamiento perfecto y un potencial tal que el costo del emparejamiento es igual al valor potencial. Esto demuestra que ambos son óptimos. De hecho, el método húngaro encuentra un emparejamiento perfecto de aristas ajustadas : una aristaij{\displaystyle ij}se llama ajustado para un potencial y siy(i)+y(j)=do(i,j){\displaystyle y(i)+y(j)=c(i,j)}Denotemos el subgrafo de aristas ajustadas porGRAMOy{\displaystyle G_{y}}. El costo de una combinación perfecta enGRAMOy{\displaystyle G_{y}}(si existe) es igual al valor de y .

Durante el algoritmo mantenemos un potencial y y una orientación deGRAMOy{\displaystyle G_{y}}(denotado porGRAMOy{\displaystyle {\overrightarrow {G_{y}}}}) que tiene la propiedad de que las aristas orientadas de T a S forman un emparejamiento M. Inicialmente, y es 0 en todas partes, y todas las aristas están orientadas de S a T (por lo que M está vacío). En cada paso, o bien modificamos y para que su valor aumente, o bien modificamos la orientación para obtener un emparejamiento con más aristas. Mantenemos la invariante de que todas las aristas de M son ajustadas. Hemos terminado si M es un emparejamiento perfecto.

En un paso general, dejemosRSS{\displaystyle R_{S}\subseteq S}yRTT{\displaystyle R_{T}\subsetequ T}sean los vértices no cubiertos por M (por lo tantoRS{\displaystyle R_{S}}consta de los vértices en S sin arista entrante yRT{\displaystyle R_{T}}consta de los vértices en T sin arista saliente). Sea Z el conjunto de vértices alcanzables enGRAMOy{\displaystyle {\overrightarrow {G_{y}}}}deRS{\displaystyle R_{S}}mediante una ruta dirigida. Esto se puede calcular mediante una búsqueda en anchura .

SiRTZ{\displaystyle R_{T}\cap Z}si no está vacío, entonces invierta la orientación de todos los bordes a lo largo de una ruta dirigida enGRAMOy{\displaystyle {\overrightarrow {G_{y}}}}deRS{\displaystyle R_{S}}aRT{\displaystyle R_{T}}. Por lo tanto, el tamaño del emparejamiento correspondiente aumenta en 1.

SiRTZ{\displaystyle R_{T}\cap Z}está vacío, entonces deja

Δ:=min{do(i,j)y(i)y(j):iZS,jTZ}.{\displaystyle \Delta :=\min\{c(i,j)-y(i)-y(j):i\in Z\cap S,j\in T\setminus Z\}.}

Δ está bien definido porque al menos uno de esos bordesij{\displaystyle ij}debe existir siempre que el emparejamiento aún no sea del tamaño máximo posible (véase la siguiente sección); es positivo porque no hay bordes estrechos entreZS{\displaystyle Z\cap S}yTZ{\displaystyle T\setminus Z}. Aumentar y en Δ en los vértices deZS{\displaystyle Z\cap S}y disminuir y en Δ en los vértices deZT{\displaystyle Z\cap T}. La y resultante sigue siendo un potencial, y aunque el gráficoGRAMOy{\displaystyle G_{y}}cambios, todavía contiene M (ver las siguientes subsecciones). Orientamos las nuevas aristas de S a T. Por definición de Δ, el conjunto Z de vértices alcanzables desdeRS{\displaystyle R_{S}}aumenta (tenga en cuenta que el número de aristas ajustadas no necesariamente aumenta). Si el vértice añadido aZT{\displaystyle Z\cap T}es inigualable (es decir, también está en RT{\displaystyle R_{T}}), entonces en la siguiente iteración el gráfico tendrá una ruta de aumento.

Repetimos estos pasos hasta que M sea una coincidencia perfecta, en cuyo caso proporciona una asignación de costo mínimo. El tiempo de ejecución de esta versión del método esO(norte4){\displaystyle O(n^{4})}: M se incrementa n veces, y en una fase donde M no cambia, hay como máximo n cambios potenciales (ya que Z aumenta cada vez). El tiempo suficiente para un cambio potencial esO(norte2){\displaystyle O(n^{2})}.

Prueba de que el algoritmo progresa

Debemos demostrar que, siempre que el emparejamiento no alcance el tamaño máximo posible, el algoritmo siempre podrá progresar, es decir, aumentar el número de aristas emparejadas o ajustar al menos una arista. Basta con demostrar que se cumple al menos una de las siguientes condiciones en cada paso:

  • M tiene el tamaño máximo posible.
  • GRAMOy{\displaystyle G_{y}}contiene una ruta de aumento.
  • G contiene un camino de cola suelta : un camino desde algún vértice enRS{\displaystyle R_{S}}a un vértice enTZ{\displaystyle T\setminus Z}que consiste en cualquier número (posiblemente cero) de aristas estrechas seguidas de una única arista suelta. La arista suelta final de un camino de cola suelta es, por lo tanto,ZS{\displaystyle Z\cap S}, garantizando que Δ esté bien definido.

Si M tiene el tamaño máximo posible, por supuesto hemos terminado. De lo contrario, según el lema de Berge , debe existir un camino de aumento P con respecto a M en el grafo subyacente G. Sin embargo, este camino puede no existir enGRAMOy{\displaystyle G_{y}}: Aunque cada arista par en P es ajustada por definición de M , las aristas impares pueden ser sueltas y, por lo tanto, estar ausentes deGRAMOy{\displaystyle G_{y}}. Un extremo de P está enRS{\displaystyle R_{S}}, el otro enRT{\displaystyle R_{T}}; wlog, supongamos que comienza enRS{\displaystyle R_{S}}. Si cada arista en P es estrecha, entonces sigue siendo un camino de aumento enGRAMOy{\displaystyle G_{y}}y hemos terminado. De lo contrario, dejav{\displaystyle uv}sea ​​el primer borde suelto en P. SivZ{\displaystyle v\notin Z}entonces hemos encontrado un camino de cola suelta y hemos terminado. De lo contrario, v es alcanzable desde algún otro camino Q de aristas estrechas desde un vértice enRS{\displaystyle R_{S}}. DejarPAGv{\displaystyle P_{v}}Sea el subcamino de P que comienza en v y continúa hasta el final, y seaPAG{\displaystyle P'}sea ​​el camino formado al viajar a lo largo de Q hasta un vértice enPAGv{\displaystyle P_{v}}se llega y luego continúa hasta el final dePAGv{\displaystyle P_{v}}. Observe quePAG{\displaystyle P'}es un camino de aumento en G con al menos una arista suelta menos que P. P puede ser reemplazado porPAG{\displaystyle P'}y este proceso de razonamiento se iteró (formalmente, usando inducción sobre el número de aristas sueltas) hasta que se encontró un camino de aumento enGRAMOy{\displaystyle G_{y}}o se encuentra una ruta de cola suelta en G.

Prueba de que ajustar el potencial y deja M sin cambios

Para demostrar que cada arista en M permanece después de ajustar y , basta con demostrar que para una arista arbitraria en M , o bien ambos de sus extremos, o ninguno de ellos, están en Z. Para ello, seav{\displaystyle vu}Sea v una arista en M desde T hasta S. Es fácil ver que si v está en Z , entonces u también debe estarlo, ya que toda arista en M es estrecha. Ahora supongamos, para contradicción, queZ{\displaystyle u\in Z}perovZ{\displaystyle v\notin Z}. u mismo no puede estar enRS{\displaystyle R_{S}}porque es el punto final de una arista coincidente, por lo que debe haber algún camino dirigido de aristas ajustadas desde un vértice enRS{\displaystyle R_{S}}a u . Este camino debe evitar v , ya que por suposición no está en Z , por lo que el vértice inmediatamente anterior a u en este camino es algún otro vértice.vT{\displaystyle v'\in T}.v{\displaystyle v'u}es una arista estrecha de T a S y por lo tanto está en M. Pero entonces M contiene dos aristas que comparten el vértice u , lo que contradice el hecho de que M es un emparejamiento. Por lo tanto , cada arista en M tiene ambos extremos o ninguno de los extremos en Z.

Prueba de que y sigue siendo un potencial

Para demostrar que y sigue siendo un potencial después de ser ajustado, basta con demostrar que ningún borde tiene su potencial total aumentado más allá de su coste. Esto ya está establecido para los bordes en M por el párrafo anterior, así que consideremos un borde arbitrario uv de S a T. Siy(){\displaystyle y(u)}si se incrementa en Δ , entonces o bienvZT{\displaystyle v\in Z\cap T}, en cuyo casoy(v){\displaystyle y(v)}se reduce en Δ , dejando el potencial total del borde sin cambios, ovTZ{\displaystyle v\en T\setminus Z}, en cuyo caso la definición de Δ garantiza quey()+y(v)+Δdo(,v){\displaystyle y(u)+y(v)+\Delta \leq c(u,v)}. Por lo tanto , y sigue siendo un potencial.

El algoritmo en tiempo O ( )

Supongamos que hayJ{\displaystyle J}empleos yW{\displaystyle W}trabajadores (JW{\displaystyle J\leq W}). Describimos cómo calcular para cada prefijo de trabajos el costo total mínimo para asignar cada uno de estos trabajos a trabajadores distintos. Específicamente, agregamos elj{\displaystyle j}el trabajo y actualizar el costo total a tiempoO(jW){\displaystyle O(jW)}, lo que resulta en una complejidad temporal general deO(j=1JjW)=O(J2W){\displaystyle O\left(\sum _{j=1}^{J}jW\right)=O(J^{2}W)}. Tenga en cuenta que esto es mejor queO(W3){\displaystyle O(W^{3})}cuando el número de puestos de trabajo es pequeño en relación con el número de trabajadores.

Agregar el j-ésimo trabajo en tiempo O ( jW )

Utilizamos la misma notación que en la sección anterior, aunque modificamos sus definiciones según sea necesario.Sj{\displaystyle S_{j}}denotamos el conjunto de los primerosj{\displaystyle j}empleos yT{\displaystyle T}denota el conjunto de todos los trabajadores.

Antes de laj{\displaystyle j}En el paso n del algoritmo, asumimos que tenemos una coincidencia enSj1T{\displaystyle S_{j-1}\cup T}que coincide con todos los trabajos enSj1{\displaystyle S_{j-1}}y potencialesy{\displaystyle y}que cumplen la siguiente condición: el emparejamiento es preciso con respecto a los potenciales, los potenciales de todos los trabajadores no emparejados son cero y los potenciales de todos los trabajadores emparejados son no positivos. Cabe destacar que dichos potenciales certifican la optimalidad del emparejamiento.

Durante elj{\displaystyle j}en el paso 1, agregamos elj{\displaystyle j}el trabajo aSj1{\displaystyle S_{j-1}}para formarSj{\displaystyle S_{j}}y inicializarZ={j}{\displaystyle Z=\{j\}}. En todo momento, cada vértice enZ{\displaystyle Z}será accesible desde elj{\displaystyle j}el trabajo enGRAMOy{\displaystyle G_{y}}. MientrasZ{\displaystyle Z}no contiene un trabajador al que no se le haya asignado un trabajo,

Δ:=min{do(j,w)y(j)y(w):jZSj,wTZ}{\displaystyle \Delta :=\min\{c(j,w)-y(j)-y(w):j\in Z\cap S_{j},w\in T\setminus Z\}}

ywpróximo{\displaystyle w_{\text{next}}}denota cualquierw{\displaystyle w}en el que se alcanza el mínimo. Después de ajustar los potenciales de la forma descrita en la sección anterior, ahora hay un borde estrecho desdeZ{\displaystyle Z}awpróximo{\displaystyle w_{\text{next}}}.

  • Siwpróximo{\displaystyle w_{\text{next}}}es no coincidente, entonces tenemos un camino de aumento en el subgrafo de aristas ajustadas dej{\displaystyle j}awpróximo{\displaystyle w_{\text{next}}}. Después de alternar la coincidencia a lo largo de este camino, ahora hemos hecho coincidir el primeroj{\displaystyle j}trabajos, y este procedimiento finaliza.
  • De lo contrario, añadimoswpróximo{\displaystyle w_{\text{next}}}y el trabajo que coincidía con elloZ{\displaystyle Z}.

Ajustar los potenciales requiereO(W){\displaystyle O(W)}tiempo. RecalcularΔ{\displaystyle \Delta }ywpróximo{\displaystyle w_{\text{next}}}después de cambiar los potenciales yZ{\displaystyle Z}también se puede hacer enO(W){\displaystyle O(W)}tiempo. El caso 1 puede ocurrir como máximoj1{\displaystyle j-1}tiempos antes de que ocurra el caso 2 y finalice el procedimiento, lo que da como resultado la complejidad temporal general deO(jW){\displaystyle O(jW)}.

Implementación en C++

Para facilitar la implementación, el siguiente código agrega un trabajador adicional.wW{\displaystyle w_{W}}de tal manera quey(wW){\displaystyle y(w_{W})}almacena la negación de la suma de todosΔ{\displaystyle \Delta }calculado hasta ahora. Después delj{\displaystyle j}Cuando se agrega el trabajo y se actualiza la coincidencia, el costo de la coincidencia actual es igual a la suma de todos losΔ{\displaystyle \Delta }calculado hasta ahora, oy(wW){\displaystyle -y(w_{W})}.

Este código está adaptado de e-maxx  :: algo. [ 7 ]

/** * Solución a https://open.kattis.com/problems/cordonbleu usando el algoritmo húngaro. */ import std ;using std :: integral ; using std :: istream ; using std :: numeric_limits ; using std :: pair ; using std :: stringstream ; using std :: string_view ; using std :: vector ;constexpr string_view SAMPLE_INPUT = R " ( 2 2 1 0 0 -1 -1 1 2 -1 0 0 ) " ;/** * @brief Realiza el algoritmo húngaro. * * Dados J trabajos y W trabajadores (J <= W), calcula el costo mínimo para asignar cada * prefijo de trabajos a trabajadores distintos. * * @tparam T un tipo lo suficientemente grande como para representar enteros del orden de J * * max(|C|) * @param C una matriz de dimensiones JxW tal que C[j][w] = costo para asignar el j-ésimo * trabajo al w-ésimo trabajador (posiblemente negativo) * * @return un vector de longitud J, con la j-ésima entrada igual al costo mínimo * para asignar los primeros j + 1 trabajos a trabajadores distintos */ template < integral T > vector < T > hungarian ( const vector < vector < T >>& C ) { auto lessThan = [] < integral T > ( T & a , const T & b ) -> bool { return b < a ? a = b , true : false ; } const int J = static_cast < int > ( C . size ()); const int W = static_cast < int > ( C [ 0 ]. size ()); contract_assert ( J <= W ); // job[w] = trabajo asignado al trabajador w, o -1 si no se ha asignado ningún trabajo // nota: se añadió un trabajador W por conveniencia vector < int > job ( W + 1 , -1 ); vector < T > ys ( J ); vector < T > yt ( W + 1 ); // potenciales // -yt[W] será igual a la suma de todos los deltas vector < T > answers ; const T inf = numeric_limits < T >::max (); para ( int jCur = 0 ; jCur < J ; ++ jCur ) { // asignar el trabajo jCur-ésimo int wCur = W ; trabajo [ wCur ] = jCur ; // costo reducido mínimo sobre aristas desde Z al trabajador w vector <T> minTo ( W + 1 , inf ); vector <int> prev ( W + 1 , -1 ); // trabajador anterior en ruta alterna vector <bool> inZ ( W + 1 ); // si el trabajador está en Z mientras ( trabajo [ wCur ] != -1 ) { // se ejecuta como máximo jCur + 1 veces inZ [ wCur ] = true ; const int j = trabajo [ wCur ] ; T delta = inf ; int wNext ; for ( int w = 0 ; w < W ; ++ w ) { if ( ! inZ [ w ]) { if ( ckmin ( minTo [ w ], C [ j ][ w ] - ys [ j ] - yt [ w ])) { prev [ w ] = wCur ; } if ( ckmin ( delta , minTo [ w ])) { wNext = w ; } } } // delta siempre será no negativo, // excepto posiblemente durante la primera vez que se ejecuta este bucle // si alguna entrada de C[jCur] es negativa for ( int w= 0 ; w <= W ; ++ w ) { if ( inZ [ w ]) { ys [ job [ w ]] += delta ; yt [ w ] -= delta ; } else { minTo [ w ] -= delta ; } } wCur = wNext ; } // actualizar asignaciones a lo largo de una ruta alterna para ( int w ; wCur != W ; wCur = w ) { job [ wCur ] = job [ w = prev [ wCur ]]; } answers . push_back ( - yt [ W ]); } return answers ; }/** * @brief resuelve https://open.kattis.com/problems/cordonbleu */ int cordonBleu ( istream & is ) { int N ; int M ; is >> N >> M ; vector < pair < ​​int , int >> B ( N ); vector < pair < ​​int , int >> C ( M ); vector < pair < ​​int , int >> bottle ( N ); vector < pair < ​​int , int >> couriers ( M ); for ( auto & [ a , b ] : bottle ) { is >> a >> b ; } for ( auto & [ c , d ] : couriers ) { is >> a >> d ; } pair < ​​int , int > rest ; std :: cin >> rest . first >> rest . second ; vector < vector < int >> costs ( N , vector < int > ( N + M - 1 )); auto dist = [ & ]( const pair < ​​int , int >& x , const pair < ​​int , int >& y ) -> int { return std :: abs ( x . first - y .primero ) +std :: abs ( x.second - y.second ); } ; for ( int b = 0 ; b < N ; ++ b ) { for ( int c = 0 ; c < M ; ++ c ) { // costo del mensajero -> botella -> restaurante [ b ][ c ] = dist ( couriers [ c ], bottle [ b ]) + dist ( bottle [ b ], rest ); } for ( int c = 0 ; c < N - 1 ; ++ c ) { // costo del restaurante -> botella -> restaurante [ b ][ c + M ] = 2 * dist ( bottle [ b ], rest ); } } return hungarian ( costs ) .back (); }/** * @brief Punto de entrada al programa. * * @return El código de retorno del programa. */ int main () { stringstream sampleInput1 ( SAMPLE_INPUT );std :: println ( stderr , "{}" , cordonBleu ( sampleInput1 )); // 5 std :: println ( "{}" , cordonBleu ( std :: cin )); } // https://godbolt.org/z/Wen35833G

Conexión a los caminos más cortos sucesivos

El algoritmo húngaro puede considerarse equivalente al algoritmo de caminos más cortos sucesivos para flujo de costo mínimo, [ 8 ] [ 9 ] donde se utiliza la técnica de reponderación del algoritmo de Johnson para encontrar los caminos más cortos. La implementación de la sección anterior se reescribe a continuación de manera que se enfatice esta conexión; se puede comprobar que los potencialesh{\displaystyle h}para los trabajadores0W1{\displaystyle 0\dots W-1}son iguales a los potencialesy{\displaystyle y}desde la solución anterior hasta un desplazamiento constante. Cuando el gráfico es disperso (solo hayMETRO{\displaystyle M}trabajo permitido, pares de trabajadores), es posible optimizar este algoritmo para que se ejecute enO(JMETRO+J2registroW){\displaystyle O(JM+J^{2}\log W)}tiempo utilizando un montón de Fibonacci para determinar wpróximo{\displaystyle w_{\text{next}}}en lugar de iterar sobre todosW{\displaystyle W}trabajadores para encontrar el que tenga la distancia mínima (aludido aquí ).

template < typename T > vector < T > hungarian ( const vector < vector < T >>& C ) { auto lessThan = [] < integral T > ( T & a , const T & b ) -> bool { return b < a ? a = b , true : false ; } const int J = static_cast < int > ( C . size ()); const int W = static_cast < int > ( C [ 0 ]. size ()); contract_assert ( J <= W ); // job[w] = trabajo asignado al w-ésimo trabajador, o -1 si no hay trabajo asignado // nota: se agregó un W-ésimo trabajador por conveniencia vector < int > job ( W + 1 , -1 ); vector < T > h ( W ); // Potenciales de Johnson vector < T > answers ; T ansCur = 0 ; const T inf = numeric_limits < T >:: max (); // Asignar el trabajo jCur-ésimo usando Dijkstra con potenciales para ( int jCur = 0 ; jCur < J ; ++ jCur ) { int wCur = W ; // trabajador no visitado con distancia mínima job [ wCur ] = jCur ; vector < T > dist ( W + 1 , inf ); // distancias reducidas de Johnson dist [ W] = 0 ; vector < bool > vis ( W + 1 ); // si ya se visitó vector < int > prev ( W + 1 , -1 ); // trabajador anterior en el camino más corto while ( job [ wCur ] != -1 ) { // Paso de Dijkstra: extraer el trabajador mínimo del montón T minDist = inf ; vis [ wCur ] = true ; int wNext = -1 ; // siguiente trabajador no visitado con distancia mínima // considerar extender el camino más corto por wCur -> job[wCur] -> w for ( int w = 0 ; w < W ; ++ w ) { if ( ! vis [ w ]) { // suma de pesos de aristas reducidos wCur -> job[wCur] -> w T edge = C [ job [ wCur ]][ w ] - h [ w ]; if ( wCur != W ) { edge -= C [ job [ wCur ]][ wCur ] - h [ wCur ]; contract_assert ( edge >= 0 ); // consecuencia de los potenciales de Johnson } if ( lessThan ( dist [ w ], dist [ wCur ] + edge )) { prev [ w ] = wCur ; } if ( lessThan ( minDist , dist [ w ])) { wNext = w ; } } } wCur = wNext ; } for ( int w =0 ; w < W ; ++ w ) { // actualizar potenciales lessThan ( dist [ w ], dist [ wCur ]); h [ w ] += dist [ w ]; } ansCur += h [ wCur ]; for ( int w ; wCur != W ; wCur = w ) { job [ wCur ] = job [ w = prev [ wCur ]]; } answers . push_back ( ansCur ); } return answers ; }

Interpretación de matrices

Esta variante del algoritmo sigue la formulación dada por Flood, [ 10 ] y posteriormente descrita más explícitamente por Munkres, quien demostró que se ejecuta enO(norte4){\displaystyle {\mathcal {O}}(n^{4})}tiempo. [ 4 ] En lugar de realizar un seguimiento de los potenciales de los vértices, el algoritmo opera únicamente sobre una matriz:

aij:=do(i,j)y(i)y(j){\displaystyle a_{ij}:=c(i,j)-y(i)-y(j)}

dóndedo(i,j){\displaystyle c(i,j)}es la matriz de costos original yy(i),y(j){\displaystyle y(i),y(j)}son los potenciales de la interpretación del gráfico. Cambiar los potenciales corresponde a sumar o restar de filas o columnas de esta matriz. El algoritmo comienza conaij=do(i,j){\displaystyle a_{ij}=c(i,j)}. Por lo tanto, puede considerarse como tomar la matriz de costos original y modificarla.

Dados n trabajadores y tareas, el problema se escribe en forma de una matriz de costos n × n.

donde a, b, c y d son trabajadores que tienen que realizar las tareas 1, 2, 3 y 4. a 1 , a 2 , a 3 , y a 4 denotan las penalizaciones incurridas cuando el trabajador "a" realiza la tarea 1, 2, 3 y 4 respectivamente.

El problema consiste en asignar a cada trabajador una tarea única de forma que se minimice la penalización total. Cabe destacar que cada tarea solo puede ser realizada por un trabajador.

Paso 1

Para cada fila, se resta su elemento mínimo a todos los demás elementos de esa fila. Esto hace que todos los elementos tengan valores no negativos. Por lo tanto, una asignación con una penalización total de 0 es, por definición, una asignación mínima.

Esto también resulta en al menos un cero en cada fila. Por lo tanto, un algoritmo voraz ingenuo puede intentar asignar a todos los trabajadores una tarea con una penalización de cero. Esto se ilustra a continuación.

Los ceros de arriba serían las tareas asignadas.

En el peor de los casos, hay n ! combinaciones para probar, ya que pueden aparecer varios ceros seguidos si varios elementos son mínimos. Por lo tanto, en algún momento este algoritmo ingenuo debería ser descartado.

Paso 2

En ocasiones, puede ocurrir que la matriz en esta etapa no se pueda utilizar para la asignación, como sucede con la matriz que se muestra a continuación.

Para superar esto, repetimos el procedimiento anterior para todas las columnas (es decir, se resta el elemento mínimo de cada columna a todos los elementos de esa columna ) y luego verificamos si es posible una asignación con penalización 0.

En la mayoría de los casos esto dará el resultado, pero si aún así no es posible, entonces debemos continuar.

Paso 3

Todos los ceros de la matriz deben cubrirse marcando la menor cantidad posible de filas y/o columnas. Los pasos 3 y 4 constituyen una forma de lograrlo.

Para cada fila, intente asignar un cero arbitrario. Las tareas asignadas se representan colocando un asterisco seguido de un cero. Tenga en cuenta que las asignaciones no pueden estar en la misma fila ni en la misma columna.

  • Asignamos el primer cero de la fila 1. El segundo cero de la fila 1 no se puede asignar.
  • Asignamos el primer cero de la fila 2. El segundo cero de la fila 2 no se puede asignar.
  • Los ceros de la fila 3 y la fila 4 no se pueden asignar porque están en la misma columna que el cero asignado en la fila 1.

Podríamos terminar con otra tarea si elegimos otro orden para las filas y columnas.

Paso 4

Cubra todas las columnas que contengan un cero (marcado con un asterisco).

Encuentra un cero no cubierto y márcalo con una prima (con el símbolo de prima ). Si no encuentras ningún cero de este tipo, es decir, si todos los ceros están cubiertos, pasa al paso 5.

  • Si el cero está en la misma fila que un cero con asterisco, cubra la fila correspondiente y descubra la columna del cero con asterisco.
  • Luego, vaya a "Encuentre un cero no cubierto y conviértalo en prima".
    • Aquí, el segundo cero de la fila 1 queda al descubierto. Como hay otro cero marcado con un asterisco en la fila 1, cubrimos la fila 1 y descubrimos la columna 1.
    • Luego, se descubre el segundo cero de la fila 2. Cubrimos la fila 2 y descubrimos la columna 2.
  • De lo contrario, el cero no cubierto no tiene asignado ningún cero en su fila. Creamos un camino a partir del cero realizando los siguientes pasos:
    1. Subpaso 1: Encuentra un cero marcado con un asterisco en la columna correspondiente. Si lo encuentras, ve al subpaso 2; de lo contrario, detente.
    2. Subpaso 2: Encuentra un cero inicial en la fila correspondiente (siempre debe haber uno). Ve al subpaso 1.

El cero de la fila 3 queda al descubierto. Añadimos al camino el primer cero de la fila 1, luego el segundo cero de la fila 1, y con eso terminamos.

  • (De lo contrario, la rama continuó) Para todos los ceros encontrados durante el camino, ceros con asterisco y ceros sin asterisco.
    • Como la ruta comienza y termina con un cero inicial al intercambiar ceros con asterisco, hemos asignado un cero más.
  • (De lo contrario, la rama continúa) Desprima todos los ceros primados y descubra todas las líneas.
  • Repita los pasos anteriores (continúe el bucle hasta que se llegue a "saltar al paso 5" mencionado anteriormente).
    • Cubrimos las columnas 1, 2 y 3. El segundo cero de la fila 2 queda al descubierto, por lo que cubrimos la fila 2 y descubrimos la columna 2:

Ahora todos los ceros están cubiertos con un número mínimo de filas y columnas.

La descripción detallada mencionada anteriormente es solo una forma de dibujar el número mínimo de líneas para cubrir todos los ceros. Otros métodos también funcionan.

Paso 5

Si el número de ceros marcados con asterisco es n (o en el caso generalmetroinorte(norte,metro){\displaystyle min(n,m)}(donde n es el número de personas y m es el número de puestos de trabajo), el algoritmo finaliza. Consulte la subsección «Resultados» a continuación para obtener información sobre cómo interpretarlos.

De lo contrario, encuentre el valor más bajo sin cubrir. Réstelo a cada elemento sin cubrir y súmelo a cada elemento cubierto por dos líneas. Vuelva al paso 4.

Esto equivale a restar un número a todas las filas que no están cubiertas y sumar el mismo número a todas las columnas que sí lo están. Estas operaciones no modifican las asignaciones óptimas.

Resultado

Si se sigue esta versión específica del algoritmo, los ceros con asterisco forman la asignación mínima.

Según el teorema de Kőnig, [ 11 ] el número mínimo de líneas (cobertura mínima de vértices [ 12 ] ) será n (el tamaño del emparejamiento máximo [ 13 ] ). Por lo tanto, cuando se requieren n líneas, la asignación de costo mínimo se puede encontrar considerando solo los ceros en la matriz.

Bibliografía

  • RE Burkard, M. Dell'Amico, S. Martello: Problemas de asignación (reimpresión revisada). SIAM, Filadelfia (PA.) 2012. ISBN 978-1-61197-222-1
  • M. Fischetti, "Lezioni di Ricerca Operativa", Edizioni Libreria Progetto Padova, Italia, 1995.
  • R. Ahuja , T. Magnanti , J. Orlin , "Network Flows", Prentice Hall, 1993.
  • S. Martello, «Jeno Egerváry: de los orígenes del algoritmo húngaro a la comunicación por satélite». Central European Journal of Operational Research 18, 47–58, 2010

Referencias

  1. Harold W. Kuhn, "El método húngaro para el problema de asignación", Naval Research Logistics Quarterly , 2 : 83–97, 1955. Publicación original de Kuhn.
  2. Harold W. Kuhn, "Variantes del método húngaro para problemas de asignación", Naval Research Logistics Quarterly , 3 : 253–258, 1956.
  3. "Presentación" . Archivado del original el 16 de octubre de 2015.
  4. 1 2 J. Munkres, "Algoritmos para los problemas de asignación y transporte", Journal of the Society for Industrial and Applied Mathematics , 5 (1):32–38, marzo de 1957.
  5. Edmonds, Jack; Karp, Richard M. (1 de abril de 1972). "Mejoras teóricas en la eficiencia algorítmica para problemas de flujo de red" . Journal of the ACM . 19 (2): 248– 264. doi : 10.1145/321694.321699 . S2CID 6375478 . 
  6. Tomizawa, N. (1971). "Sobre algunas técnicas útiles para la solución de problemas de redes de transporte". Networks . 1 (2): 173– 194. doi : 10.1002/net.3230010206 . ISSN 1097-0037 . 
  7. "Algoritmo húngaro para resolver el problema de asignación" . e-maxx :: algo . 23 de agosto de 2012 . Consultado el 13 de mayo de 2023 . 
  8. Jacob Kogler (20 de diciembre de 2022). "Flujo de costo mínimo: algoritmo de ruta más corta sucesiva" . Algoritmos para programación competitiva . Consultado el 14 de mayo de 2023 .
  9. "Resolución del problema de asignación mediante flujo de costo mínimo" . Algoritmos para programación competitiva . 17 de julio de 2022. Consultado el 14 de mayo de 2023 .
  10. Flood, Merrill M. (1956). "El problema del viajante". Operations Research . 4 (1): 61– 75. doi : 10.1287/opre.4.1.61 . ISSN 0030-364X . 
  11. Teorema de Kőnig (teoría de grafos) Teorema de Kőnig
  12. Cobertura de vértices Cobertura mínima de vértices
  13. Emparejamiento (teoría de grafos) emparejamiento

Lecturas adicionales

  • Cormen, Thomas; Leiserson, Charles; Rivest, Ronald; Stein, Clifford. «25.3: El algoritmo húngaro para el problema de asignación». Introducción a los algoritmos (4.ª  ed.). págs. 723–739 . ISBN  978-0-262-04630-5.
  • Bruff, Derek, El problema de la asignación y el método húngaro (formalismo matricial).
  • Mordecai J. Golin, Emparejamiento bipartito y el método húngaro (formalismo de bigrafos), Apuntes de curso, Universidad de Ciencia y Tecnología de Hong Kong .
  • Algoritmo húngaro de coincidencia máxima (ambos formalismos), en el sitio web de Brilliant.
  • RA Pilgrim , Algoritmo de asignación de Munkres. Modificado para matrices rectangulares , Apuntes del curso, Universidad Estatal de Murray .
  • Mike Dawes , El problema de la asignación óptima , Apuntes del curso, Universidad de Western Ontario .
  • Sobre el método húngaro de Kuhn: un tributo desde Hungría , András Frank , Grupo de Investigación Egervary, Pazmany P. setany 1/C, H1117, Budapest, Hungría.
  • Conferencia: Fundamentos de la Investigación Operativa - Problema de Asignación - Algoritmo Húngaro , Prof. G. Srinivasan, Departamento de Estudios de Gestión, IIT Madras.
  • Extensión: Análisis de sensibilidad de asignación (con complejidad temporal O(n^4)) , Liu, Shell.
  • Resuelve cualquier problema de tarea en línea ; proporciona una explicación paso a paso del algoritmo húngaro.

Implementaciones

Tenga en cuenta que no todos estos satisfacen laO(norte3){\displaystyle O(n^{3})}complejidad temporal, incluso si afirman lo contrario. Algunos pueden contener errores, implementan el más lento.O(norte4){\displaystyle O(n^{4})}algoritmo, u otras ineficiencias. En el peor de los casos, un ejemplo de código enlazado desde Wikipedia podría modificarse posteriormente para incluir código malicioso. Es necesario verificar y realizar pruebas de rendimiento al utilizar ejemplos de código de autores desconocidos.

  • Versiones en Lua y Python del código de RA Pilgrim (afirmandoO(norte3){\displaystyle O(n^{3})}complejidad temporal)
  • Implementación de Julia
  • Implementación de C que afirmaO(norte3){\displaystyle O(n^{3})}complejidad temporal
  • Implementación de Java que afirmaO(norte3){\displaystyle O(n^{3})}complejidad temporal
  • Implementación en Python
  • Implementación en Ruby con pruebas unitarias
  • Implementación en C# que afirmaO(norte3){\displaystyle O(n^{3})}complejidad temporal
  • Implementación en D con pruebas unitarias (port de una versión Java que afirmaO(norte3){\displaystyle O(n^{3})}Archivado el 30 de diciembre de 2019 en Wayback Machine .
  • Implementación interactiva en línea
  • Implementaciones en serie y en paralelo.
  • Matlab y C. Archivado el 3 de mayo de 2008 en la Wayback Machine.
  • Implementación en Perl
  • Implementación en C++
  • Implementación de C++ que afirmaO(norte3){\displaystyle O(n^{3})}Complejidad temporal (licencia de código abierto estilo BSD)
  • Implementación en MATLAB
  • Implementación en C
  • Implementación de JavaScript con pruebas unitarias (port de una versión de Java que afirmaO(norte3){\displaystyle O(n^{3})}complejidad temporal)
  • El paquete Clue R propone una implementación, solve_LSAP.
  • Implementación de Node.js en GitHub
  • Implementación en Python en el paquete scipy