1. {Realizado por:
Berroterán, Omaira C.I.:23.592.559
INSTITUTO UNIVERSITARIO POLITÉCNICO
“SANTIAGO MARIÑO”
EXTENSIÓN PORLAMAR
ESCUELA: INGENIERÍA DE SISTEMAS
2. {
Existen diferentes formas de solucionar
problemas, en la programación existen diferentes métodos,
enfoques y tipos de programación para llegar a la
resolución más óptima de un problema. En matemáticas, la
informática y la economía, la programación dinámica es
un método para la resolución de problemas complejos
desglosándolos en subproblemas más simples
En este trabajo nos enfocaremos en la
programación dinámica y la aplicación de los métodos de
recursividad de avance y y reversa, y la explicación del
algoritmo de las n reinas.
Se presenta conceptos esenciales para poder
comprender estos temas como también la aplicación de
ejercicios prácticos donde se ven en ejecución los
diferentes métodos.
3. La programación dinámica
encuentra la solución óptima de un problema con n variables
descomponiéndolo en n etapas, siendo cada etapa un
subproblema de una sola variable. Sin embargo, como la
naturaleza de la etapa difiere de acuerdo con el problema de
optimización, la programación dinámica no proporciona los
detalles de cómputo para optimizar cada etapa.
Los cálculos de programación dinámica se hacen en
forma recursiva, ya que la solución óptima de un subproblema
se usa como dato para el siguiente subproblema. Para cuando se
resuelve el último subproblema queda a la mano la solución
óptima de todo el problema. En particular, los subproblemas se
vinculan normalmente mediante restricciones comunes. Al
pasar de un subproblema al siguiente se debe mantener la
factibilidad de esas restricciones comunes.
4. Existe la recursión en
avance y la recursión en reversa; ambas
recursiones producen la misma solución y
no es otra cosa que el orden en el que se
han de realizar los cálculos, es decir, si se
va a resolver primero la etapa inicial, o si
se empezará por la etapa final.
Al visualizar la recursión en
avance, ésta parece más lógica, sin
embargo, en las publicaciones sobre
programación dinámica se usa la recursión
en reversa de modo invariable. La razón de
ésta preferencia es que, en general, la
recursión en reversa es más eficiente desde
el punto de vista computacional.
5. Menciona en su documental del tema características de los
problemas de programación dinámica las siguientes características para la aplicación
de la programación dinámica indispensables para la aplicación de la recursividad de
avance y reversa
• Característica 1:
Es posible dividir el problema en etapas, y se requiere una decisión en cada etapa.
• Característica 2:
Cada etapa se relaciona con una cierta cantidad de etapas.
• Característica 3:
La decisión tomada en cualquier etapa describe el modo en que el estado en la etapa
actual se transforma en el estado en la etapa siguiente.
• Característica 4:
Dado el estado actual, la decisión óptima para cada una de las etapas restantes no
tiene que depender de v los estados ya alcanzados o de las decisiones tomadas
previamente.
• Característica 5:
Si los estados del problema se clasifican dentro de uno de T etapas, debe haber una
RECURSIÓN que relacione el costo o la recompensa ganada durante las etapas t, t
+1,…..T con el costo o la recompensa ganada a partir de las etapas t, +1, t +2,…….,T.
La RECURSIÓN formaliza, en esencia, el procedimiento de ir hacia atrás.
6. Ejemplo: (Problema de la ruta más corta)
Se trata de seleccionar la ruta más corta entre dos ciudades. La siguiente
red muestra las rutas posibles entre el inicio en el nodo 1 y el destino en el nodo
7. Las rutas pasan por ciudades intermedias, representadas por los nodos 2 a 6.
Para resolver el problema con programación dinámica e descomponer el
problema en etapas. A continuación se hacen los cálculos para cada etapa por
separado.
• Resultados de la etapa 1
Distancia más corta al nodo 2 = 5 millas (de nodo 1)
Distancia más corta al nodo 3 = 9 millas (de nodo 1)
Distancia más corta al nodo 4 = 8 millas (de nodo 1)
• Resultados de la etapa 2
Distancia menor al nodo
5 = mini =2,3,4 Distancia menor al nodo i + Distancia del nodo i al nodo 5 =
mini=2; 5+10=15 i=3; 9+4=13 i=4; 8+9=1713 millas de nodo 3
Distancia menor al nodo 6=mini=2,3,4 Distancia menor al nodo i + Distancia del
nodo i al nodo 6 = min i= 2; 5+17=22 i=3; 9+10=19 i=4; 8+9=1717 millas de nodo 4
7. • Resultados de la etapa 3
Distancia menor al nodo 7=mini=5,6 Distancia menor al nodo i + Distancia
del nodo i al nodo 7 =mini=5; 13+8=21 i=6; 17+9=2621 millas de nodo 5
Distancia más corta al nodo 7 = 21 millas desde el nodo 5
Según los resultados de las etapas encontramos la ruta óptima. La distancia
más corta del nodo 1 al nodo 7 es de 21 millas, el nodo 7 está enlazado al
nodo 5 de acuerdo a la solución de la etapa 3.
El nodo 5 está enlazado al nodo 3 según la etapa 2.
Finalmente en la etapa 1 se observa que el nodo 3 está vinculado al nodo 1.
Así la ruta óptima (más corta) se define como: 1 → 3 → 5 → 7.
8. Supongamos que hay 30 cerillas sobre una mesa. Yo
empiezo eligiendo 1,2 ò 3 cerillas. Luego mi contrincante
debe tomar 1, 2 ò 3. Así continuamos hasta que alguno
de los jugadores toma la última cerilla. Este jugador
pierde. ¿Cómo puedo yo (el primer jugador) estar seguro
de ganar el juego?
9. Si puedo tener la certeza que le tocará el turno a mi oponente cuando
quede una cerilla, claro que ganaré. Al regresar un paso hacia atrás, si yo
puedo tener la seguridad de que le tocara su turno a mi contrario cuando
queden 5 cerillas, yo puedo estar seguro de que cuando le toque el
siguiente turno solo le quedara una cerilla. Por ejemplo, suponga que el
turno de mi contrincante es cuando quedan 5 cerillas. Si mi contrario
toma 2 cerillas, yo elegiré 2 y le dejare una cerilla, con lo que aseguro mi
victoria. De manera similar, si soy capaz de obligar a mi contrincante a
jugar cuando quedan 5, 9, 13, 17, 21, 25, ò 29 cerillas, tengo la victoria
asegurada. Por lo tanto, no puedo perder si tomo 30- 29 = 1 la primera vez
que me toque jugar. Luego simplemente me aseguro que mi contrario
siempre se quede con 29, 25, 21, 17, 13, 9, ò 5 cerillas cuando le toque
jugar.
10. .
Para comprender el
funcionamiento de esta técnica vamos a comenzar viendo cómo se
soluciona el "problema de las ocho reinas". Partimos de un tablero de
ajedrez, el cual tiene un total de 64 casillas (8 filas x 8 columnas). El
problema consiste en situar ocho reinas en el tablero de tal forma que no se
den jaque entre ellas. Una reina puede dar jaque a aquellas reinas que se
sitúen en la misma fila, columna o diagonal en la que se encuentra dicha
reina. En la figura siguiente, podemos observar una solución al problema:
11. La solución a este rompecabezas pasa, por tanto, en situar una reina en cada
fila y en cada columna. ¿Cómo actuaríamos para situar las ocho reinas?
Comenzaríamos por la primera columna y situaríamos la primera reina. Al
hacer esto, eliminamos como posibles casillas donde localizar reinas la fila,
la columna y las dos diagonales. Teniendo en cuenta estas restricciones, se
sitúa en la segunda columna la segunda reina y se "tachan" las nuevas
casillas prohibidas. Seguidamente se procede a poner la tercera reina y así
sucesivamente. Supongamos que se han situado las seis primeras reinas.
Puede llegar un momento en el que no se pueda situar la séptima sin que
ninguna otra le dé jaque. En ese momento, se deshace el movimiento que ha
ubicado la sexta reina y busca otra posición válida para dicha reina. Si no se
pudiera, se vuelve deshacer el movimiento de la sexta reina y se intenta
buscar otra localización. En el caso de que no sea posible, se sigue hacia
atrás
SOLUCIÓN:
12. intentando colocar la reina quinta. Si se puede colocar, entonces se procederá a
dejar en el tablero la sexta de nuevo. Si se ha conseguido sin que le den jaque, se
intentará emplazar la séptima y, por último, la octava. En general, si hay algún
problema, se deshace el último movimiento y se prueba a localizar una casilla
alternativa. Si no se consigue, se vuelve hacia atrás y así sucesivamente .Ésta
técnica de prueba-error o avance-retroceso, se conoce como bactracking (vuelta
atrás).En ella se van dando pasos hacia delante mientras sea posible y se deshacen
cuando se ha llegado a una situación que no conduce a la resolución del problema
original.
¿Cómo se puede aplicar la recursividad para solventar este problema? De forma
muy sencilla: dado que se ha situado una reina en una posición correcta en una
columna, hay que considerar el problema de situar otra reina en la columna
siguiente: resolver el mismo problema con una columna menos. En este momento
realizaríamos una llamada recursiva. ¿El caso base? Cuando el tablero se haya
reducido a uno de tamaño cero, en cuyo caso no se hará nada. Por tanto, se partirá
de un problema de tamaño 8, y se considerarán problemas de tamaño menor
quitando una columna en cada llamada recursiva hasta que se llegue a un tablero
de tamaño cero, momento en el cual habremos alcanzado el caso base. Si se puede
situar una reina en la columna correspondiente, se hace una llamada recursiva, si
no, se vuelve hacia atrás y se prueba otras posiciones.
13. Para implementar la función recursiva en C que encuentre una solución para el
problema delas ocho reinas necesitaremos una estructura de datos para representar el
tablero, para lo cual utilizaremos una matriz de tamaño 8 x 8 de enteros, donde un 1
en una posición indicará que hay situada una reina en ella y un 0, que no está
ocupada.
int Tablero[8][8]; Inicialmente todos los elementos de la matriz bidimensional serán 0,
indicando que no hay ninguna reina en el tablero y será global a la función recursiva,
con objeto de ahorrar espacio en la pila.
Las funciones auxiliares que nos ayudarán a resolver el problema y que no
implementaremos aquí por ser irrelevantes al tema que nos centra nuestra atención,
son:
• void AsignarReinaA(int Fila, int Columa) => Sitúa un 1 en la casilla (Fila,
Columna)
Indicando que está ocupada por una reina.
• void EliminarReinaDe(int Fila, int Columna) => Sitúa un 0 en la casilla (Fila,
Columna)
Indicando que esa casilla está libre (antes estaba ocupada por una reina y ahora deja
de estarlo).
• int RecibeJaqueEn(int Fila, int Columna) => Devuelve 1 si la casilla (Fila,
Columna)
14. Recibe jaque de alguna reina y 0 en caso contrario.
La función recursiva que implementaremos será void SituarReina(int Columna,
int *Situada). El parámetro formal Col indica en qué columna se quiere situar la
reina, y Situada, pasado por referencia, es un parámetro que tomará el valor 1
cuando se haya logrado ubicar correctamente a la reina correspondiente, y 0
cuando el intento haya sido infructuoso. Columna se pasará por copia para que,
cuando se realice backtracking se pueda trabajar con el valor original de esta
variable.
Básicamente, se comprueba si se está en el caso base, en cuyo caso se indica que se
ha situado la reina en una casilla libre. En otro caso, se comenzará un bucle que
iterará mientras no se haya situado correctamente la reina dentro de la columna
especificada en el parámetro, o nos hayamos salido fuera del tablero, en cuanto a
las filas se refiere. En el bucle se comprobará si en la casilla (Fila, Columna) se
puede situar la reina. Si no es así, se incrementa la fila para probar en otra casilla
de la misma columna. Si no recibiera jaque una reina situada en esa casilla, se
asigna a la posición del tablero y se llama recursivamente a la función SituarReina
para situar en la siguiente columna otra reina. Cuando han finalizado las
llamadas recursivas, se comprueba el valor de Situada para ver si ha habido éxito
en el resto de intentos. En el caso de que no, se deshace la asignación anterior y se
incrementa en una la fila para probar con otra casilla.
De forma sencilla se puede modificar esta función para que calcule, no sólo una
solución posible, si no todas las existentes.
15.
16. La Programación Dinámica no sólo tiene sentido aplicarla
por razones de eficiencia, sino porque además presenta un
método capaz de resolver de manera eficiente problemas cuya
solución ha sido abordada por otras técnicas y ha fracasado.
Existe una serie de problemas cuyas soluciones pueden
ser expresadas recursivamente en términos matemáticos, y
posiblemente la manera más natural de resolverlos es mediante
un algoritmo recursivo. Sin embargo, el tiempo de ejecución de la
solución recursiva, normalmente de orden exponencial y por
tanto impracticable, puede mejorarse substancialmente mediante
la Programación Dinámica
La recursividad es una técnica ampliamente utilizada en
matemáticas, y que básicamente consiste en realizar una
definición de un concepto en términos del propio concepto que se
está definiendo.
17. • Hamdy A Taha. 7ª edición (2004). Investigación de Operaciones. México:
Editorial. Pearson educación
• J. Poot (2010). Documental del tema características de los problemas de
programación dinámica. Consultada el 12 de Julio de 2014, de
http://webcache.googleusercontent.com/search?q=cache:96Ypz1BWQCoJ
:www.itescam.edu.mx/principal/sylabus/fpdb/recursos/r64674.DOCX+&
cd=2&hl=es-419&ct=clnk&gl=ve
• A. Aho, J.E. Hopcroft, J. Ullman(1988). Estructuras de datos y
algoritmos. Addison-Wesley