1 - Clase02+Clase03 - Recursividad+Backtracking
1 - Clase02+Clase03 - Recursividad+Backtracking
1 - Clase02+Clase03 - Recursividad+Backtracking
Que es recursividad
1-Definición: Consiste en la definición de algo (una propiedad, una operación, un
procedimiento o una función) en términos de sí mismo( definición dada en clase).
Los casos base: Son los casos del problema que se resuelven con un segmento
de código sin recursividad. Normalmente corresponden a instancias del problema
simples y fáciles de implementar cuya solución es conocida. Ejemplo, Factorial de
0.
Ejemplos adicionales
Function Factorial(Integer n) : Integer
If n<=0 then
Return 1;
Else
Return n * Factorial(n-1);
EndIf
EndFunction
If y==0 then
Return 1;
Else
If (y mod 2 0) then
aux = x * aux;
Return aux;
EndIf EndFunction
Function Potencia(Integer x, Integer y) : Integer
If y==0 then
Return 1;
Else
Return x*Potencia(x,y-1);
EndIf
EndFunction
5-La técnica que se aplica para definir la solución recursiva de problemas se conoce como
divide y vencerás, y se trata de resolver un problema mediante su descomposición en
varios subproblemas similares al original (o del mismo tipo) pero con datos más
pequeños. Si un problema puede subdividirse en subproblemas similares, entonces el
mismo proceso puede ser aplicado a cada uno de los subproblemas hasta que se pueda
encontrar una solución directa o conocida. Finalmente se combinan las soluciones de los
subproblemas para producir la solución final del problema original.
6- Ejecución
Si una función recursiva contiene variables locales y/o parámetros, se creará un nivel en
la pila diferente por cada llamada. Los nombres de las variables locales serán los mismos,
pero en cada nivel recursivo son un grupo nuevo de variables, que tienen posiciones de
memoria distintas (están en distintos ambientes de referenciación). Las variables locales a
las que puede acceder la función o acción recursiva son las definidas en su propio
ambiente.
Así como se apilan las variables, se guarda la posición de la última instrucción ejecutada,
de manera tal que, cuando la función termina, se desapila todo el ambiente local, y se
devuelve el control al punto en donde se hizo la invocación. Los resultados pueden ser
retornados, o dejados en variables pasadas por referencia, en atributos, o en variables
globales.
Ejemplos:
1- La función QuickSort
EndProcedure
Integer i;
For i=1 To Sx Do
Result[i] = X[i];
EndFor
For i=1 To Sy Do
Result[Sx+i] = Y[i];
EndFor
For i=1 To Sz Do
Result[Sx+Sy+i] = Z[i];
EndFor
EndProcedure
2- El Fibonacci en su forma original
3- La función MergeSort
If Ls>Li Then
MergeSort(A, Li + c + 1, Ls);
EndIf
EndProc
Integer i,j,k;
Select
If A[j]>A[k] Then
Else
EndIf
EndSelect
EndFor
For i=Li To Ls Do
A[i] = Aux[i-Li+1];
EndFor
EndProcedure
4- torres de Hanoi,( este ejemplo esta en una de las guías enviadas por correo de la
clase02) Revise Google Drive, DropBox o su correo)
1.Tipos de algoritmos recursivos(continuación)
Ejemplo
int par(int n){
if (n == 0) return 1;
return impar(n-1);
}
int impar(int n){
if (n == 0) return 0;
return par(n-1);
}
main(){
printf("Ingrese un numero natural:");
scanf("%d",&n);
if (par(n)) {
printf("El numero %d es par\n", n);
}else{
printf("El numero %d es impar\n", n);
}
}
2.Clasificación de funciones recursivas
3.Técnica de divide y vencerás
Los problemas divide y conquista por lo general particionan el problema en varios
subproblemas similares más pequeños, y tienen un costo adicional para combinar las
soluciones parciales. Por ejemplo, el binary search reduce un problema de tamaño n a
tamaño n/2. El MergeSort particiona un conjunto en dos, con un costo de combinación
igual a la mezcla ordenada de ambos conjuntos. El QuickSort particiona el conjunto en 3,
con un costo adicional de particionamiento y de concatenación.
If Ls>Li Then
MergeSort(A, Li + c + 1, Ls);
EndIf
EndProc
Integer i,j,k;
Select
If A[j]>A[k] Then
Else
EndIf
EndSelect
EndFor
For i=Li To Ls Do
A[i] = Aux[i-Li+1];
EndFor
EndProcedure
Integer i;
For i=1 To Sx Do
Result[i] = X[i];
EndFor
For i=1 To Sy Do
Result[Sx+i] = Y[i];
EndFor
For i=1 To Sz Do
Result[Sx+Sy+i] = Z[i];
EndFor
EndProcedure
4.Ventajas y desventajas de usar recursividad
Ventajas:
system("pause");
return 0;
}
Desventajas
TIPS RECOMENDADOS
Nota: Estas sugerencias no incitan al estudiante a que se olvide de leer el resto del
material enviado por el docente.
Miercoles 25 Clase 04- Backtracking. Esquema de
Soluciones
Motivación de la técnica:
1- Hay problemas para los que NO se conoce un algoritmo para su resolución o al menos, NO
cuentan con un algoritmo eficiente para calcular su solución en estos casos, la único posibilidad es
una exploración directa de todas las posibilidades.
O
D1={1,2,3,4,5,6}.
D2={1,2,3,4,5,6}.
Pero al lanzar los 2 dados al mismo tiempo podemos obtener cualquiera de las siguiente
combinaciones:
Ω={
(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),
(4,1),(4,2),(4,3),(4,4),(4,5),4,6),
Backtracking=Fuerza bruta
Es un método sistemático que itera a través de todas las combinaciones posibles del
espacio de búsqueda para cada aplicación particular.
Siempre puede encontrar todas las posibles soluciones existentes
Sí, si eliminamos la necesidad de obtener todas las combinaciones posibles del conjunto
Cuando al comenzar a construir la solución nos encontramos con nodos o valores iniciales a partir
del cual sabemos que no vamos a alcanzar la solución, o leído de otro modo cuando sabemos que
no nos va a llevar a soluciones útiles. Este proceso de descartar estas soluciones que no son útiles
es lo que se denomina podar la rama completa de un árbol.
Entonces para tener que recorrer todas las soluciones e ir descartando de primera mano las
que sabemos no nos van a llevar a ningún lado o no nos interesan se utliza la técnica de
Backtracking.
Existen diversos algoritmos clásicos que se resuelven con algoritmos de backtracking: rompecabezas,
laberintos, permutaciones, problemas de las 8-reinas, crucigramas, Sudoku, problema de la mochila, problema
del agente viajero, etc.
Ejemplo de aplicación: Una agencia de viajes que determina la ruta más corta para viajar de
Venezuela a Australia por ejemplo. Pueden haber diversas rutas por las que usted puede pasar
pero hay una ruta que será la más económica en tiempo o en costo para usted, según sus
necesidades.
SALTO A SECCIÓN. CUANDO DEBE APLICARSE LA TÉCNICA DE BACKTRACKING
No resulta útil para conjuntos no ordenados, donde la exploración es total (explorar todos los
candidatos). Entonces, el punto clave de los algoritmos de backtracking es: descartar/seleccionar
rápidamente las soluciones invalidas/validas.
2-Backtracking
O en otras palabras el backtracking es: “Encontrar una solución intentándolo con una de varias
opciones. Si la elección es incorrecta, el computo retrocede o vuelve a comenzar desde el punto de
decisión anterior y lo intenta con otra opción.
Visto de otro modo, la solución deseada debe expresarse como una n-tupla (x1, ..., xn) donde los xi
son elegidos de algún conjunto finito Si. Usualmente el problema a resolver requiere encontrar un
vector que maximice/minimice/satisfaga una función criterio P(x1, ..., xn). A veces, se busca todos
los vectores que satisfagan P.
La llamada inicial del algoritmo se hace como Backtrack (P, Inicio (P)), donde Backtrack se puede
escribir como:
void B a c k t r a c k ( Set P , C )
if (R e c h a z a r ( P , C )) then A b o r t a r ( )
if (A c e p t a r ( P , C )) then S o l u c i o n ( P , C )
Set s = P r i m e r o ( P , C )
while not s . E s V a c i o ( ) do
Backtrack(P,s)
s=Proximo(P,s)
end
end
Los algoritmos de vuelta atrás (backtracking) hacen una búsqueda sistemática de todas las
posibilidades, sin dejar ninguna por considerar. Cuando intenta una solución que no lleva a
ningún sitio, retrocede deshaciendo el último paso, e intentando una nueva variante desde
esa posición (es normalmente de naturaleza recursiva).
Estos algoritmos se pueden modificar fácilmente para obtener una única solución o todas las
soluciones posibles al problema dado.
Esta técnica ofrece un método para resolver problemas tratando de completar una o varias
soluciones por etapas. En cada paso se intenta extender una solución parcial de todos los modos
posibles, y si ninguno resulta satisfactorio se produce la vuelta atrás hasta el último punto donde
quedaban alternativas por explorar. Es una mejora de la búsqueda completa de soluciones y una
alternativa a los algoritmos voraces
Sucesores: Es una función que dado un candidato genera todos los candidatos que son
exensiones de éste.
También se dice que es posible clasificar un algoritmo de backtracking de acuerdo al tamaño del
subconjunto C solución:
EJEMPLO PARA FORMARSE UNA IDEA GENERAL DE COMO COMENZAR A ATACAR LOS
EJERCICIOS DE BACKTRACKING
2- El laberinto
En los casos planteados se toma como posición inicial las coordenadas (1,1) siendo esta la que representa al
inicio del laberinto, por ende se toma como final del laberinto a la esquina opuesta, al llegar a esta la función
se deja de llamarse recursivamente y retorna el ultimo nodo.
Una vez cargados los caminos posibles, cada rama del nodo actual representa un camino que seguir, entonces
se evalúan una a una mediante la misma función de backtracking.
El hecho que la función retorne el último elemento del árbol, el cual representa el fin del laberinto hace
posible que a partir de este se recorra el árbol de forma inversa y se pueda construir un nuevo árbol de forma
muy simple eliminando los demás nodos que no pertenecen a la solución.
a). El problema del cambio:
Supongamos que el cajero sólo tiene billetes de 2000, 5000 y 10000 y nos debe dar 21.000
pts. Con una estrategia voraz nunca llegaría a la solución correcta (daría 2 de 10.000 y no
podría dar el resto), mientras que con backtracking podemos ir dando billetes (empezando
por el mayor valor posible) y si llegamos a una combinación sin solución, volvemos atrás
intentándolo con el segundo billete más grande, y así sucesivamente.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* Progrmaa que calcula las permutaciones O Resultados a obtener para la cadena: abc
*abc
*acb
*bac
*bca
*cab
*cba
*/
int main(void)
{
char conjunto[] = "abc";
size_t card = sizeof conjunto - 1;
Para más información del uso del editor consulte el siguiente enlace:
http://programacionymetodos.blogspot.com/2012/05/depuracion-de-programas-dev-c.html
Tipos de Backtracking
Los algoritmos que hacen uso de la técnica del backtracking no siempre tendrán la mismas
estructuras de una de las plantillas, siempre dependerán del problema. Lo importante es que
entiendas la técnica del backtracking y te enfoques a trabajar en función de una búsqueda
sistematizada.
//C++
#include <iostream>
#define F 7
#define C 8
cout<<laberinto[i][j];
cout<<endl;
cout<<endl;
//función backtracking
//comprueba si es solución
if(laberinto[i][j]=='S'){
laberinto[i][j]='.';
imprimir(laberinto);
return true;
//marcar el camino
laberinto[i][j]='.';
//imprimir(laberinto);
//Alternativas
laberinto[i-1][j]=='S'))
return true;
laberinto[i][j+1]=='S'))
if(recorrer(laberinto, i, j+1))
return true;
laberinto[i+1][j]=='S'))
return true;
laberinto[i][j-1]=='S'))
if(recorrer(laberinto, i, j-1))
return true;
laberinto[i][j]=' ';
return false;
int main(){
//'#' = barrera
//'S' = salida
char laberinto[F][C]={{'#','#','#','#','#','#','#','#'},
{'#',' ',' ',' ',' ',' ','#','#'},
{'#','#','#','#','S','#','#','#'}};
imprimir(laberinto);
if(!recorrer(laberinto, 5, 2))
system("pause");
El problema de la mochila consiste en llenar una mochila con una serie de objetos que
tienen una serie de pesos con un valor asociado. Es decir, se dispone de n tipos de
objetos y que no hay un número limitado de cada tipo de objeto (si fuera limitado no
cambia mucho el problema). Cada tipo i de objeto tiene un peso wi positivo y un valor
vi positivo asociados. La mochila tiene una capacidad de peso igual a W. Se trata de
llenar la mochila de tal manera que se maximice el valor de los objetos incluidos pero
respetando al mismo tiempo la restricción de capacidad. Notar que no es obligatorio
que una solución óptima llegue al límite de capacidad de la mochila.
Ejemplo: se supondrá:
n=4
W=8
w() = 2, 3, 4, 5
v() = 3, 5, 6, 10
Es decir, hay 4 tipos de objetos y la mochila tiene una capacidad de 8. Los pesos
varían entre 2 y 5, y los valores relacionados varían entre 3 y 10.
Una solución no óptima de valor 12 se obtiene introduciendo cuatro objetos de peso 2,
o 2 de peso 4. Otra solución no óptima de valor 13 se obtiene introduciendo 2 objetos
de peso 3 y 1 objeto de peso 2. ¿Cuál es la solución óptima?.
Dicho procedimiento puede ser ejecutado de esta manera, siendo n, W, peso y valor
variables globales para simplificar el programa:
n = 4,
W = 8,
peso[] = {2,3,4,5},
valor[] = {3,5,6,10},
optimo = 0;
...
mochila(0, W, 0, &optimo);
http://www.widget-101.com/codigo/backtracking-
recursivo/
El resto de los ejercicios contenidos en la guía se haría una corrida GENERAL sin colocar
el algoritmo, ya que no alcanzaría el tiempo el hacer una corrida en frio utilizando los
algoritmos.
ALGORITMO:
ALGORITMO:
ALGORITMO:
ALGORITMO:
ALGORITMO:
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
ALGORITMO: IDEM
EL CASO ANALOGO SERIA PARA El problema de las 8 reinas:
Enunciado: “Sobre un tablero de ajedrez hay que colocar 8 reinas de forma que ninguna de
ellas se amenace.” Como cada reina estará en una fila diferente, podemos representar la
solución con un vector de columnas donde están situadas las según la fila. Para resolverlo
se situaría la dama sobre la primera fila y se colocaría la segunda dama en un lugar donde
no amenace a las demás. Si no encontramos solución parcial, volvemos un paso atrás y nos
replanteamos la solución de la etapa anterior (una nueva posición).
if( i == 8 )
*halladaSol = 1;
else
Reinas8(i+1, S, halladaSol );
}
http://www.uhu.es/nieves.pavon/pprogramacion/temario/tema4/tema4.html
http://www.uhu.es/nieves.pavon/pprogramacion/temario/tema4/tema4.html
NOTA: El programa les pedira el tamañ del tablero(n)= pueden probar con n=4.
Podemos intentar todas las combinaciones posibles, obteniendo un árbol completo donde
los nodos terminales serían las ciudades de partida, y asociados a los mismos los kilómetros
asociados al camino desde el raíz a ese nodo.
Poda del árbol de vuelta atrás: Consiste en la eliminación de posibilidades (poda del
árbol):
o Exclusión previa: Tras un análisis previo, puede organizarse la búsqueda
para que ignore situaciones infructuosas (ej: problema 8 reinas, cuando
ponemos cada dama en una fila diferente).
o Fusión de ramas: Cuando la búsqueda a partir de diferentes ramas lleve a la
misma solución, podemos limitar la búsqueda (por ejemplo en problemas
con simetría como el de las 8 reinas o el del PAV).
o Ramificación y acotamiento: Cuando lo que se busca es una solución
óptima, es posible reducir el árbol de búsqueda abandonando las ramas que
se sepa con certeza que no llevan hacia soluciones óptimas. (por ejemplo en
el problema del viajante podríamos tener una variable T que almacene el
valor más óptimo hasta el momento, y abortar un camino en el mismo
momento en que se rebase T).
Reordenación de la búsqueda: Consiste en reorganizar las ramas del árbol de
búsqueda de manera que se analicen 1º las situaciones con mejores expectativas.
Permite por tanto resolver más rapidamente el problema.