Programación dinámica y similitudes de divide y vencerás
Como lo veo por ahora, puedo decir que la programación dinámica es una extensión del paradigma de divide y vencerás .
No los trataría como algo completamente diferente. Debido a que ambos funcionan dividiendo recursivamente un problema en dos o más subproblemas del mismo tipo o relacionados, hasta que estos se vuelven lo suficientemente simples como para ser resueltos directamente. Las soluciones a los subproblemas se combinan para dar una solución al problema original.
Entonces, ¿por qué todavía tenemos diferentes nombres de paradigma y por qué llamé a la programación dinámica una extensión? Esto se debe a que el enfoque de programación dinámica puede aplicarse al problema solo si el problema tiene ciertas restricciones o requisitos previos . Y después de eso, la programación dinámica extiende el enfoque de divide y vencerás con la técnica de memorización o tabulación .
Vamos paso a paso ...
Prerrequisitos / restricciones de programación dinámica
Como acabamos de descubrir, hay dos atributos clave que deben dividir y vencer el problema para que la programación dinámica sea aplicable:
Subestructura óptima: la solución óptima se puede construir a partir de soluciones óptimas de sus subproblemas
Subproblemas superpuestos : el problema se puede dividir en subproblemas que se reutilizan varias veces o un algoritmo recursivo para el problema resuelve el mismo subproblema una y otra vez en lugar de generar siempre nuevos subproblemas
Una vez que se cumplan estas dos condiciones, podemos decir que este problema de divide y vencerás puede resolverse usando un enfoque de programación dinámica.
Extensión de programación dinámica para dividir y conquistar
El enfoque de programación dinámica extiende el enfoque de divide y vencerás con dos técnicas ( memorización y tabulación ) que tienen el propósito de almacenar y reutilizar soluciones de subproblemas que pueden mejorar drásticamente el rendimiento. Por ejemplo, la implementación recursiva ingenua de la función Fibonacci tiene una complejidad de tiempo O(2^n)
en la que la solución DP hace lo mismo con solo O(n)
tiempo.
La memorización (relleno de caché de arriba hacia abajo) se refiere a la técnica de almacenamiento en caché y reutilización de resultados previamente calculados. La fib
función memorizada se vería así:
memFib(n) {
if (mem[n] is undefined)
if (n < 2) result = n
else result = memFib(n-2) + memFib(n-1)
mem[n] = result
return mem[n]
}
La tabulación (relleno de caché de abajo hacia arriba) es similar, pero se centra en llenar las entradas de la caché. Calcular los valores en el caché es más fácil de hacer de forma iterativa. La versión de tabulación de fib
se vería así:
tabFib(n) {
mem[0] = 0
mem[1] = 1
for i = 2...n
mem[i] = mem[i-2] + mem[i-1]
return mem[n]
}
Puede leer más sobre la comparación de memorización y tabulación aquí .
La idea principal que debe comprender aquí es que debido a que nuestro problema de dividir y conquistar tiene subproblemas superpuestos, el almacenamiento en caché de las soluciones de subproblemas se hace posible y, por lo tanto, la memorización / tabulación pasa a la escena.
Entonces, ¿cuál es la diferencia entre DP y DC después de todo
Como ahora estamos familiarizados con los requisitos previos de DP y sus metodologías, estamos listos para poner todo lo que se mencionó anteriormente en una sola imagen.
Si desea ver ejemplos de código, puede echar un vistazo a una explicación más detallada aquí, donde encontrará dos ejemplos de algoritmos: Búsqueda binaria y Distancia mínima de edición (Distancia de Levenshtein) que ilustran la diferencia entre DP y DC.