Está en la página 1de 4

Análisis de un Algoritmo de Programación Dinámica para la Solución

del Proyecto 1
Rozo Fajardo, Nicolas Ortiz Gomez, Andres
n.rozo@uniandes.edu.co a.ortizg@uniandes.edu.co
Tapias Guzman, David Santiago
ds.tapias@uniandes.edu.co
Septiembre de 2023

1 Estrategia de Diseño
Antes de crear la ecuación de recurrencia que modela y resuelve el problema, nuestro proceso de diseño comenzó
con una observación simple pero poderosa sobre el patrón implı́cito en las configuraciones finales. En primer lugar,
notamos que en cualquier configuración final con i rocas, j ranas y k movimientos, existen dos escenarios
posibles. Uno donde la rana j se encuentra en la piedra i (esto ocurre si la distancia entre la rana j y la piedra
i es menor o igual a k, y otro donde la rana j no se encuentra en la piedra i. Por lo tanto, la cantidad total
de configuraciones finales es la suma de estos dos casos. Además, reconocimos que, debido a la naturaleza y la
lógica inherente del problema, era necesario establecer casos base para i=0, j=0, y k=0.Con esta perspicacia en
mente, diseñamos la relación de recurrencia c(i, j, k), la cual devuelve el número de configuraciones finales válidas
que se obtienen al ubicar j ranas en las primeras i piedras usando k movimientos. A continuación, se presenta una
descripción detallada de cada uno de los casos de la relación de recurrencia, junto con su posterior formalización
matemática.

• Caso Base 1: Si el número de ranas es mayor que el número de piedras, no se puede formar ninguna
configuración válida, sin importar la cantidad de movimientos disponibles. Por lo tanto, la recurrencia retorna
0.

Para los siguientes casos, se debe cumplir que la cantidad de ranas es menor o igual que la cantidad de piedras.

• Caso Base 2: Si el número de piedras es 0, tampoco se puede formar ninguna configuración válida, por lo
cual la recurrencia retorna 0.

Para los siguientes casos, se debe cumplir que la cantidad de piedras es mayor a 0.

• Caso Base 3: Si el número de ranas es mayor que 0 y el número de movimientos es 0, solo se puede formar
una configuración válida si las primeras j ranas se encuentran en las primeras i piedras. En la relación de
recurrencia, esta verificación se realiza con el enunciado v(i, j), el cual retorna 1 si las primeras j ranas se
encuentran en las primeras i piedras o 0 de lo contrario. A nivel de implementación, esta verificación se realiza
con la función verify.
• Caso Base 4: Si el número de ranas es 0 y el número de movimientos es 0, solo se puede formar una
configuración válida (solo hay una forma de no elegir ranas), por lo cual la recurrencia retorna 1.

Para los siguientes casos, se debe cumplir que el número de movimientos es mayor a 0.

• Caso Base 5: Si la cantidad de ranas es igual a 0, la recurrencia retorna 0, ya que no existen formas de no
seleccionar ranas usando k movimientos.
• Caso Recursivo 1: Si el número de ranas es igual a 1 y la distancia entre la rana 1 y la piedra i (rep-
resentada mediante el enunciado d(i, j) en la ecuación de recurrencia y mediante la función distance en la
implementación) es igual al número de movimientos disponibles, entonces existen 1 + c(i − 1, j, k) configura-
ciones finales posibles. Esto es, una configuración en la que la rana 1 llega a la piedra 1 más la cantidad de
configuraciones finales que se pueden lograr con 1 rana y k movimientos en i-1 piedras.
• Caso Recursivo 2: Si el número de ranas es igual a 1 y la distancia entre la rana 1 y la piedra i es diferente
al número de movimientos disponibles, entonces la rana 1 no puede llegar a la piedra i y la cantidad de
configuraciones finales está dada por c(i, j, k), es decir, la cantidad de configuraciones finales que se pueden
lograr al ubicar 1 rana con k movimientos en i-1 piedras.

1
• Caso Recursivo 3: Si el número de ranas es mayor a 1 y la distancia entre la rana j y la piedra i es menor o
igual a la cantidad de movimientos disponibles, entonces se suman la cantidad de configuraciones que se logran
cuando la rana j no está en la piedra i con la cantidad de configuraciones finales que se logran después de
ubicar a la rana j en la piedra i. Esto es c(i − 1, j, k) + c(i − 1, j − 1, k − d).

• Caso Recursivo 4: Si el número de ranas es mayor a 1 y la distancia entre la rana j y la piedra i es mayor
que la cantidad de movimientos disponibles, entonces la rana j no puede llegar a la piedra i y, por ende, se
ignora ese caso. Esto es c(i − 1, j, k).

La estrategia descrita anteriormente se puede representar con la siguiente ecuación de recurrencia:

 

 0 j>i 

0 (j ≤ i) ∧ (i = 0)

 


 

0 (j ≤ i) ∧ (i > 0) ∧ (j = 0) ∧ (k > 0)

 


 

1 (j ≤ i) ∧ (i > 0) ∧ (j = 0) ∧ (k = 0)

 

 
C(i, j, k) = V (i, j) (j ≤ i) ∧ (i > 0) ∧ (j > 0) ∧ (k = 0) (1)
1 + C(i − 1, j, k) (j ≤ i) ∧ (i > 0) ∧ (j = 1) ∧ (k > 0 ∧ k = d)

 


 

C(i − 1, j, k) (j ≤ i) ∧ (i > 0) ∧ (j = 1) ∧ (k > 0 ∧ k ̸= d)

 


 

C(i − 1, j, k) + C(i − 1, j − 1, k − d) (j ≤ i) ∧ (i > 0) ∧ (j > 1) ∧ (k > 0 ∧ k ≥ d)

 


 

C(i − 1, j, k) (j ≤ i) ∧ (i > 0) ∧ (j > 1) ∧ (k > 0 ∧ k < d)
 

A continuación, con el objetivo de facilitar la comprensión de la ecuación de recurrencia propuesta, se presenta


un grafo de necesidades para un caso especı́fico en el que hay 4 piedras, 2 ranas, 1 movimiento, y la configuración
está descrita por la cadena RPRP. Los casos base se representan en verde, mientras que los casos que se calculan
mediante recursión se presentan en negro.

Figure 1: Grafo de necesidades

Finalmente, es necesario destacar que el algoritmo propuesto soluciona a la perfección el problema debido a que
aborda de manera exhaustiva el problema planteado al considerar todas las posibles subconfiguraciones para llegar
a la respuesta final. Sin embargo, su complejidad temporal se reduce significativamente, pasando de exponencial (si
se utiliza únicamente la recursión) a polinomial gracias a la aplicación del enfoque de programación dinámica.

2 Analisis de Complejidades Espacial y Temporal


Para calcular la complejidad temporal de un algoritmo, se analiza la complejidad de cada función y se suman. En
este proceso, las complejidades menores se consideran constantes, ya que en el análisis asintótico, las funciones que
crecen más rápido son más relevantes a medida que la entrada se acerca al infinito. En resumen, se simplifica el
análisis al enfocarse en las funciones más rápidas.

makeArray O(n).
Esta función tiene un solo bucle for que itera sobre cada carácter en la cadena de entrada. La complejidad temporal
de convertir una cadena en una matriz de caracteres es O(n), donde n es la longitud de la cadena.

makeMap O(n)
La función recorre los elementos del arreglo initConfig.El bucle inserta un par clave-valor en infoMap e incrementa
la variable cont si el carácter actual es equivalente a R. El tiempo promedio requerido para una operación de inserción
en un mapa hash es constante O(1). Como resultado, la complejidad temporal del bucle, incluida la inserción en
HashMap se puede expresar como O(n) de forma asintotica.

2
verify O(1)
En esta función las operaciones incluyen verificar si la clave j existe en infoMap utilizando el método containsKey(),
recuperar el valor asociado con la clave j utilizando el método get(), y comparar el valor recuperado con i - 1.

distance O(1)
El fragmento de código tiene una complejidad temporal constante de O(1) porque realiza un número constante de
operaciones independientemente del tamaño de la entrada. Las operaciones incluyen verificar si la clave j existe en
el infoMap utilizando el método containsKey(), recuperar el valor asociado con la clave j utilizando el método
get(), y realizar una operación de resta y valor absoluto. Estas operaciones no dependen del tamaño del mapa ni
de los valores de i y j.

countFrogs
El analisis de complejidad de tiempo para etsa función se va a detallar en 3 secciónes:
1. Inicialización y Procesamiento de la Entrada:
• Crear la matriz de caracteres config a partir de initConfigString toma un tiempo de O(n), donde n
es la longitud de la cadena de entrada.
• Crear el mapa infoDict utilizando la función makeMap también toma un tiempo de O(n) ya que procesa
la matriz config.
2. Bucles Anidados Triples:
• El trabajo computacional principal ocurre dentro de los bucles anidados triples:
– El bucle más externo itera desde k = 0 hasta k = movements, lo que resulta en movements + 1
iteraciones.
– El bucle intermedio itera desde i = 0 hasta i = rocks, lo que resulta en rocks + 1 iteraciones.
– El bucle más interno itera desde j = 0 hasta j = f rogs, lo que resulta en f rogs + 1 iteraciones.
3. Operaciones Dentro de los Bucles Anidados:
• Las operaciones dentro de los bucles anidados consisten en declaraciones condicionales y operaciones
aritméticas. Analicemos la complejidad de estas operaciones:
– La condición if (j > i) es una comparación simple y tiene una complejidad temporal de O(1).
– Las funciones verify y distance se llaman dentro de los bucles. Estas funciones se han analizado
previamente:
∗ La función verify tiene una complejidad temporal de O(1).
∗ La función distance también tiene una complejidad temporal de O(1).
– Las operaciones aritméticas, como la suma y la resta, también son operaciones de tiempo constante
y tienen una complejidad de O(1).
4. Complejidad Espacial & Temporal:
• Finalmente, la función tiene una complejidad temporal de O(movements ∗ rocks ∗ f rogs) segun su com-
portamiento asintotico. Por otro lado, la complejidad en espacio tambien es de orden O(movements ∗
rocks ∗ f rogs) ya que se usan matrices 2D (rocks * frogs) y tenemos k matrices de este tipo donde k es
el numero de movimientos.

3 Respuestas a los Escenarios de Comprensión de Problemas Algorı́tmicos


3.1 Escenario 1: Las ranas pueden devolverse
• Nuevos Retos: En este caso, se deberı́an considerar todas las posibles permutaciones que se pueden hacer
sobre la cadena que representa la configuración inicial haciendo uso de m movimientos, ya que al poder
devolverse las ranas, aumenta considerablemente la cantidad de configuraciones finales que son válidas. Esto
implica que se deben considerar escenarios más allá de que la rana j se encuentre o no en la posición i, ya que,
aunque esto suceda, en la siguiente iteración o configuración la rana podrı́a devolverse y dar paso a nuevos
casos.
• Cambios Necesarios: La función deberı́a modificarse de forma que no solo se verifique si la rana j puede o
no puede llegar a la piedra i, sino que, a su vez, se tengan en cuenta todos aquellos casos en los que la rana
devuelve sus movimientos. A primera vista, este problema es exponencial; sin embargo, es muy probable que
con una correcta implementación se logre en tiempo polinomial.

3
3.2 Escenario 2: Todas las ranas deben ir en la misma dirección
• Nuevos Retos: En este caso, debido a que todas las ranas se deben mover en la misma dirección, serı́a
necesario controlar la dirección de movimiento mediante un parámetro, a la vez que se deberı́a restringir la
forma en la cual se calculan las distancias, ya que no siempre se podrá realizar el movimiento desde la dirección
especificada.
• Cambios Necesarios: Un cambio que se podrı́a aplicar sobre la solución actual serı́a el de usar dos matrices
con las mismas caracterı́sticas que la actual, y en una contar las configuraciones que se pueden lograr moviendo
todas las ranas a la derecha, y en la otra contar lo mismo pero restringiendo el movimiento hacia la izquierda.
Para esto serı́a necesario que la función de distancia tome en cuenta la dirección y, el resultado final, se
obtendrı́a sumando las entradas [p][r][m] de ambas matrices.

También podría gustarte