¿Qué es la optimización de divide y vencerás en la programación dinámica?

Esta es una optimización para calcular los valores de programación dinámica (DP) de la forma [matemática] dp [i] [j] = \ min \ limits_ {k <j} (dp [i – 1] [k] + C [ k + 1] [j]) [/ math] para alguna función de costo arbitraria [math] C [i] [j] [/ math] de modo que se pueda probar la siguiente propiedad sobre esta programación dinámica con esta función de costo. Denotemos por [matemática] A [i] [j] [/ matemática] la [matemática] k [/ matemática] óptima para la cual [matemática] dp [i] [j] = dp [i – 1] [k] + C [k] [j] [/ matemáticas]. La propiedad es que para cualquier [matemática] i [/ matemática] y [matemática] j [/ matemática], [matemática] A [i] [j] \ leq A [i] [j + 1] [/ matemática], es decir, la [matemática] k [/ matemática] óptima es monótona en [matemática] j [/ matemática] para [matemática] i [/ matemática] fija.

Un ejemplo de este DP es el siguiente problema: dados los objetos [matemáticos] n [/ matemáticos] con pesos [matemáticos] w_1, w_2, \ puntos, w_n [/ matemáticos], se dividen en grupos [matemáticos] m [/ matemáticos] de objetos consecutivos , de modo que la suma de cuadrados de los pesos totales de los grupos es mínima (el peso total del grupo es la suma de los pesos de los objetos en el grupo). Se puede demostrar que la [matemática] k [/ matemática] óptima en este problema es monótona en [matemática] j [/ matemática]. Lo mismo puede probarse si tomamos la función de costo [matemática] W \ log W [/ matemática] para el peso total [matemática] W [/ matemática] del grupo dado que todos los pesos son positivos (minimice la suma de [matemática] ] W \ log W [/ math] entre todas las distribuciones de objetos [math] n [/ math] en grupos [math] m [/ math]) o cualquier otra función convexa del peso total del grupo. En estos casos, [matemática] C [i] [j] [/ matemática] en la formulación DP sería la función de costo para un grupo con objetos desde [matemática] i [/ matemática] a [matemática] j [/ matemática] inclusivo.

La solución directa de este DP es [matemática] O (mn ^ 2) [/ matemática], porque necesitamos un ciclo sobre [matemática] i [/ matemática] ([matemática] m [/ matemática] iteraciones), un ciclo sobre [matemática] j [/ matemática] ([matemática] n [/ matemática] iteraciones) y un bucle sobre [matemática] k [/ matemática] para cada [matemática] j [/ matemática] ([matemática] n / 2 [/ matemáticas] en promedio). Sin embargo, dada la condición de monotonicidad [matemática] A [i] [j] \ leq A [i] [j + 1] [/ matemática], este DP puede resolverse en [matemática] O (mn \ log n) [/ mates]. Más específicamente, para cada [matemática] i [/ matemática] fija, resolveremos la iteración en [matemática] O (n \ log n) [/ matemática] en lugar de [matemática] O (n ^ 2) [/ matemática].

Esto puede hacerse mediante el siguiente pseudocódigo que para [matemática] i [/ matemática] aplica divide y vencerás en [matemática] j [/ matemática] y mantiene el rango ([matemática] jleft, jright [/ matemática]) de valores de [matemática] j [/ matemática] para la cual estamos buscando la respuesta y el rango correspondiente ([matemática] kleft, kright [/ matemática]) de los valores posibles para una óptima [matemática] k [/ matemática] cuando [matemática] j [/ math] está en el rango ([math] jleft, jright [/ math]):

def ComputeDP (i, jleft, jright, kleft, kright):
# Seleccione el punto medio
jmid = (jleft + jright) / 2
# Calcular el valor de dp [i] [jmid] por definición de DP
dp [i] [jmid] = + INFINITO
bestk = -1
para k en rango (kleft, jmid):
si dp [i – 1] [k] + C [k + 1] [jmid] <mejor:
dp [i] [jmid] = dp [i – 1] [k] + C [k + 1] [jmid]
bestk = k
# Divide y conquistaras
si jleft <jmid – 1:
ComputeDP (i, jleft, jmid – 1, kleft, bestk)
si jleft + 1 <jright:
ComputeDP (i, jmid + 1, jright, bestk, kright)

def ComputeFullDP:
Inicialice dp para i = 0 de alguna manera
para i en rango (1, m):
ComputeDP (i, 0, n, 0, n)

Resulta que ComputeDP funciona para [math] O (n \ log n) [/ math] para cualquier [math] i [/ math] fijo. En realidad, una ComputeDP(i, jleft, jright, kleft, kright) funciona a tiempo [math] O (q \ log p) [/ math] donde [math] p = jright – jleft [/ math] es la longitud de el rango para [math] j [/ math] y [math] q = kright – kleft [/ math] es la longitud del rango para [math] k [/ math]. La relación de recurrencia aquí es [matemáticas] T (p, q) = O (q) + T (\ frac {p} {2}, a) + T (\ frac {p} {2}, q – a) < Bq + T (\ frac {p} {2}, a) + T (\ frac {p} {2}, q – a) [/ math] para alguna constante [math] B [/ math]. Al calcularlo aún más, vemos [matemáticas] T (p, q) <Bq + Ba + B (q – a) + T (\ frac {p} {4}, a ') + T (\ frac {p} {4}, a – a ') + T (\ frac {p} {4}, q') + T (\ frac {p} {4}, q – a – q ') = [/ matemáticas] [matemáticas ] = 2Bq + T (\ frac {p} {4}, a ') + T (\ frac {p} {4}, a – a') + T (\ frac {p} {4}, q ') + T (\ frac {p} {4}, q – a – q ') <[/ matemática] [matemática] \ ldots <[/ matemática] [matemática] <\ log_2 (p) Bq = O (q \ log p) [/ matemáticas].

Preludio: en un grupo de 100 personas, encuentre los dos con la menor diferencia de edad al cuadrado.

personOneBirthday = -1009065600;
// los cumpleaños se expresan en marcas de tiempo Unix establecidas al comienzo del día.
personTwoBirthday = -722563200;
ageDifferenceSquare = (personOneBirthday – (personTwoBirthday)) ^ 2;

Estamos interesados ​​en encontrar la pareja de personas con la menor [math] ageDifferenceSquare [/ math] entre ellas.

La solución ingenua sería calcularlo para cada par posible. Llegaremos a nuestro resultado, eventualmente, pero tomaría un tiempo cuadrático (es decir, para n puntos, tomaría [matemáticas] n² [/ matemáticas] cálculos).

Coral: Divide y vencerás.

Para acelerar el proceso, dividimos el grupo entre los fanáticos de Knuth y Sussman. Solo comparamos los meses de nacimiento de los miembros de cada grupo con otros en ese grupo, y obtenemos el par más cercano para cada uno.

Al final, comparamos los pares ganadores de los dos grupos (y verificamos si no hay pares más cercanos que crucen las mitades [1]).

Ahora las comparaciones son solo [matemáticas] 2 (n / 2) ² [/ matemáticas] , lo cual es bueno.

Si tenemos dos núcleos, podemos dejar que cada núcleo se ocupe de un grupo, lo cual es mejor: solo tenemos que esperar [matemáticas] (n / 2) ² [/ matemáticas] comparaciones!

¿Quieres dividirlos aún más entre los seguidores de Vim y Emacs?

¡Solo [matemáticas] 4 (n / 4) ² [/ matemáticas] comparaciones!

¿Más núcleos? [matemáticas] (n / 4) ² [/ matemáticas]

Chicos? ¿Dónde están chicos? ¿Debería esperar aquí mientras terminas?
Núcleo a cargo del cuadrante Sussman / Vim

Incalzando: ¿Qué es la programación dinámica?

Es una técnica que utilizamos cuando tenemos un gran conjunto de problemas similares donde sospechamos que habrá un resultado similar: podemos almacenar cada resultado en una estructura de datos, por lo que no tendremos que volver a calcularlo.

Por ejemplo, podemos llenar gradualmente una matriz [math] mxm [/ math] donde [math] m [/ math] es el número de cumpleaños disponibles: la primera fila y columna contendrá un vector [math] v [/ math ], lleno de todos los cumpleaños en nuestro espacio.

En cada comparación verificaríamos nuestra matriz para ver si [math] ageDifferenceSquare [/ math] ya está allí: en [math] m (personOneBirthday, personTwoBirthday) [/ math], de lo contrario, intercambiamos la celda con nuestro resultado.

Scherzo: ¿Estaba todo mal entonces? Ejercicios para el lector.

  • ¿Es una matriz el mejor tipo de datos? ¿No se repetirán algunos cálculos de todos modos?
  • ¿Vale la pena el conjunto de datos de nuestro ejemplo? ¿Cuántas personas es probable que compartan un cumpleaños en nuestro conjunto de datos? ¿Qué pasa si no nos importa el año?
  • ¿En qué grupo terminaría? Inicialmente he sido audaz y lo deletreé, lo cual es completamente al revés.
  • Esta fue una gran pérdida de tiempo – complejidad sabia – tiempo sabio. ¿Habría sido lo mismo si estuviéramos buscando a las dos personas con la suma más cercana de [math] ageDifferenceSquare [/ math] entre cada una y una fecha fija?

Notas al pie

[1] Un enfoque de divide y vencerás

More Interesting

¿Cuál es la complejidad temporal de una función que calcula la altura de un árbol binario de forma recursiva? ¿Es O (N) u O (NlogN)?

¿Cuál es la complejidad de T (n) = 2T (n-3)?

Si sabemos cómo funciona un algoritmo de hash de contraseña en particular, ¿por qué no podemos simplemente crear una contraseña que genere el mismo hash?

¿Qué es un contador Loglog?

¿Cuál es la mejor manera de crear una estructura de datos basada en valores clave en C ++ que admita memoria compartida entre procesos usando C ++ 11?

¿Por qué Java utiliza una implementación mediocre de hashCode para cadenas?

Rendimiento del software: ¿los algoritmos de cálculo se ejecutarán más rápido cuando se implementen en Node.js en lugar de C?

¿Cuál es una explicación intuitiva del algoritmo Metropolis-Hastings?

¿Cuál es el algoritmo más eficiente para encontrar el késimo elemento más pequeño en una matriz que tiene n elementos desordenados?

Para el siguiente algoritmo sea n = 0. Para cada persona en la sala, establezca n = n + 1 '. ¿Dónde exactamente explica el algoritmo que compones n?

¿Qué es una explicación intuitiva de union-find?

¿Es cierto que si me vuelvo competente en estructuras de datos y algoritmos, puedo aprender cualquier lenguaje de programación y habilidades técnicas muy rápido?

¿Cuál es el peor caso de complejidad temporal de BFS (cuando se busca un elemento), sin almacenar los estados visitados?

Visión por computadora: ¿Cuáles son algunas técnicas de detección de bordes ultrarrápidas y eficientes en memoria?

¿Cuál es la compensación tiempo-espacio en el diseño de algoritmos?