Una distinción de caso en programación dinámica: ¡ejemplo necesario!

19

He estado trabajando en programación dinámica por algún tiempo. La forma canónica de evaluar una recursión de programación dinámica es creando una tabla de todos los valores necesarios y llenándola fila por fila. Ver por ejemplo Cormen, Leiserson et al: "Introducción a los algoritmos" para una introducción.

Me concentro en el esquema de cálculo basado en tablas en dos dimensiones (relleno fila por fila) e investigo la estructura de las dependencias de las celdas, es decir, qué celdas deben hacerse antes de que se pueda calcular otra. Denotamos con el conjunto de índices de celdas de las que depende la celda . Tenga en cuenta que necesita estar libre de ciclos.i ΓΓ(i)iΓ

Extracto de la función real que se calcula y me concentro en su estructura recursiva. Formalmente, considero que una recurrencia es programación dinámica si tiene la formad

d(i)=f(i,Γ~d(i))

con , y alguna función (computable) que no utiliza que no sea a través de .˜ Γ d ( i ) = { ( j , d ( j ) ) jΓ d ( i ) } f d ˜ Γ di[0m]×[0n]Γ~d(i)={(j,d(j))jΓd(i)}fdΓ~d

Al restringir la granularidad de a áreas rugosas (a la izquierda, arriba a la izquierda, arriba, arriba a la derecha, ... de la celda actual) se observa que existen esencialmente tres casos (hasta simetrías y rotación) de validez Recurrencias de programación dinámica que informan cómo se puede llenar la tabla:Γd

Tres casos de dependencias de celdas de programación dinámica

Las áreas rojas denotan (sobre aproximaciones de) . Los casos uno y dos admiten subconjuntos, el caso tres es el peor de los casos (hasta la transformación del índice). Tenga en cuenta que no es estrictamente necesario que todas las áreas rojas estén cubiertas por ; Algunas celdas en cada parte roja de la tabla son suficientes para pintarla de rojo. Se requiere explícitamente que las áreas blancas no contengan ninguna celda requerida.ΓΓΓ

Ejemplos para el caso uno son la distancia de edición y la subsecuencia común más larga , el caso dos se aplica a Bellman & Ford y CYK . Entre los ejemplos menos obvios se incluyen los que funcionan en las diagonales en lugar de las filas (o columnas), ya que pueden rotarse para ajustarse a los casos propuestos; Vea la respuesta de Joe como ejemplo.

¡Sin embargo, no tengo un ejemplo (natural) para el caso tres! Entonces mi pregunta es: ¿Cuáles son los ejemplos para el caso tres de recurrencias / problemas de programación dinámica?

Rafael
fuente
2
Caso 3 subsume casos 1 y 2.
JeffE
No, no lo hace, a pesar de la apariencia. Por ejemplo, una instancia de caso 1 no puede tener una célula requerida en la zona superior izquierda, mientras que una instancia de caso 3 tiene que tener una célula requerida en el área superior izquierda. Edité la explicación para aclarar.
Raphael

Respuestas:

15

Hay muchos otros ejemplos de algoritmos de programación dinámica que no se ajustan a su patrón en absoluto.

  • El problema de subsecuencia creciente más largo requiere solo una tabla unidimensional.

  • Existen varios algoritmos de programación dinámica natural cuyas tablas requieren tres o incluso más dimensiones. Por ejemplo: Encuentre el rectángulo blanco de área máxima en un mapa de bits. El algoritmo de programación dinámica natural utiliza una tabla tridimensional.

  • Pero lo más importante, la programación dinámica no se trata de tablas ; Se trata de desenrollar la recursión. Hay muchos algoritmos de programación dinámica natural en los que la estructura de datos utilizada para almacenar resultados intermedios no es una matriz, porque la recurrencia que se desenrolla no está en un rango de enteros. Dos ejemplos fáciles son encontrar el mayor conjunto independiente de vértices en un árbol y encontrar el subárbol común más grande de dos árboles. Un ejemplo más complejo es el algoritmo de aproximación para el problema del vendedor ambulante euclidiano de Arora y Mitchell.(1+ϵ)

JeffE
fuente
Gracias por su respuesta, pero restringí explícitamente la pregunta a problemas bidimensionales y al esquema de cálculo canónico basado en tablas (editado para aclarar aún más ese punto). Soy consciente del marco más general, pero no estoy interesado en él en este momento.
Raphael
99
Está bien, pero realmente creo que te estás perdiendo el punto.
JeffE
Como hay muchos votos a favor, pensé que debería aclarar esto: esta publicación no responde la pregunta y, de hecho, ni siquiera intenta hacerlo.
Raphael
2
@Raphael es correcto. Mi "respuesta" no es una respuesta, sino una crítica de la pregunta, pero fue demasiado larga para un comentario.
JeffE
3

Calcular la función de Ackermann está en este espíritu. Para calcular necesita saber y para algunas grandes . O la segunda coordenada disminuye, o la primera disminuye, y la segunda potencialmente aumenta.A ( m , n - 1 ) A ( m - 1 , k ) kA(m,n)A(m,n1)A(m1,k)k

Idealmente, esto no se ajusta a los requisitos, ya que el número de columnas es infinito, y el cálculo generalmente se realiza de arriba a abajo con la memorización, pero creo que vale la pena mencionarlo.

sdcvvc
fuente
1
No, la anidación como en no se presta realmente a la programación dinámica. Jeje, esto es tan extraño que tendré que verificar que mis definiciones excluyan tales casos de alguna manera. DP no primitivo-recursivo, oh mi ...A(m1,A(m,n1))
Raphael
1
No estoy seguro de por qué esta respuesta fue rechazada, ya que es una buena respuesta. La función Ackermann se presta extremadamente bien para la programación dinámica. En general, cualquier función definida recursivamente que se calcula repetidamente para los mismos argumentos se presta a la programación dinámica. Para ver esto solo hay que implementarlo y comparar los tiempos de ejecución. Lo que lleva años calcular con la función normal de Ackermann puede llevar segundos con la función de programación dinámica.
Jules
@Jules: El problema para el esquema de tabla canónica es que no conoce un límite (primitivo recursivo) en el tamaño de la tabla a priori. Por supuesto, puede hacer una memorización, pero no del modo habitual. Entonces, sí, puede ser viable para DP pero no se ajusta a la clase de problemas que concierne a mi pregunta.
Raphael
1
No creo que sea un requisito para DP que tengas un límite a priori en el tamaño de la tabla. De hecho, como JeffE menciona, el caché no tiene que ser una tabla en absoluto. Puede ser cualquier estructura de datos asociativa. DP es realmente una idea muy muy simple: desea calcular una función definida recursivamente, pero esta función se llama repetidamente en los mismos argumentos. DP es la optimización en la que introduce un caché para asegurarse de calcular cada caso solo una vez. Hay muchas funciones que no se ajustan a ninguno de sus casos, incluso si son funciones de dos enteros delimitados.
Jules
2

Esto no encaja exactamente en el caso 3, pero no sé si alguno de sus casos captura un problema muy común utilizado para enseñar programación dinámica: Multiplicación de cadena de matriz . Para resolver este problema, (y muchos otros, este es solo el canónico) llenamos la matriz diagonal por diagonal en lugar de fila por fila.

Entonces la regla es algo como esto:

diagMatrix

Joe
fuente
1
Escrito así, de hecho, no cabe en ningún caso. Sin embargo, si gira 45 grados en el sentido de las agujas del reloj, obtendrá el caso 2 (y todas las propiedades implícitas). Esto es cierto para otros ejemplos que también funcionan desde la diagonal hacia las esquinas. Pero gracias por mencionarlo!
Raphael
@Raphael no es obvio de inmediato que sean equivalentes, es posible que desee mencionar eso en su pregunta.
Joe
0

Sé que es un ejemplo tonto, pero creo que un problema iterativo simple como

Encuentra la suma de los números en una matriz cuadrada

Podría calificar. El tradicional "para cada fila para cada columna" se parece a su caso 3.

hugomg
fuente
-1

Este no es exactamente el espacio de búsqueda que está buscando, pero tengo una idea de la parte superior de mi cabeza que podría ser de ayuda.

Problema:

Dada una matriz , digamos, con enteros distintos en los que las entradas de cada fila (de izquierda a derecha) y cada columna (de arriba a abajo) se ordenan en orden creciente y las entradas en cada columna están en orden creciente . Dé un algoritmo eficiente para encontrar la posición de un entero en (o digamos que el entero no está presente en la matriz).M x Mn×nMxM

Responder

Esto se puede resolver de la siguiente manera recursiva:

Tenemos una matriz n × n. Deje . Ahora compare con . Si podemos descartar cualquier elemento para y , es decir, el espacio de búsqueda se reduce en . De manera similar, cuando , el espacio de búsqueda se reduce en . Entonces, después de la primera iteración, el tamaño del espacio de búsqueda se convierte en . Puede hacer esto de manera recursiva de la siguiente manera: hacemos 3 comparaciones:xmk,kx<mk,kmi,jkinkjn1/4x>mk,k1/43k=1+n2xmk,kx<mk,kmi,jkinkjn1/4x>mk,k1/4x( 334n2xcon el elemento central en cada uno de los tres cuadrantes restantes, y el tamaño del espacio de búsqueda restante se convierte en y así sucesivamente.(34)3n2

0x0
fuente
1
Esta no es una instancia de programación dinámica, ¿no?
Raphael