recursividad

Transcripción

recursividad
RECURSIVIDAD
Existen muchas funciones matemáticas cuyos argumentos son números naturales, que
pueden definirse de manera recursiva. Esto quiere decir que el valor de la función para el
argumento n puede definirse en términos del argumento n-1 (o alguno anterior). En este
tipo de definiciones siempre existirá un caso base a partir del cual parte la definición, el
cual normalmente es el valor de la función en cero o en uno, aunque no necesariamente
debe ser así.
Por ejemplo, el factorial puede definirse de manera recursiva de la siguiente manera:
Para definir una función en forma recursiva es necesario especificar:
•
Caso(s) base: Donde la recursividad se detiene
•
Paso de recursión: Como se define un elemento distinto del base, en términos de
elementos anteriores.
Usualmente los lenguajes de programación permiten definir funciones de manera recursiva.
El lenguaje C es uno de ellos. La definición recursiva para el factorial sería:
int factorial(int n) {
if ((n == 0) || (n == 1))
return(1);
else
return(n*factorial(n-1));
}
Normalmente las definiciones recursivas pueden expresarse en forma no recursiva. Sin
embargo, dependiendo del caso, el resultado puede ser más confuso. Por ejemplo, una
función en C que calcula el factorial en forma iterativa sería:
int factorial(int n) {
int i, fact = 1;
for (i=2; i<=n; i++)
fact = fact * i;
return(fact);
}
Sin embargo, los algoritmos iterativos tienen una ventaja en cuanto al uso de memoria, si
se comparan con los recursivos. La recursividad requiere que se guarde el estado de la
ejecución antes de cada llamado recursivo, implicando un gasto considerable de memoria.
Es probable que, por esta razón, las versiones recursivas tengan mayores limitaciones al
Algoritmos y Estructura de datos
ejecutarse en un computador.
La aplicación de las definiciones recursivas puede ampliarse a una amplia gama de
problemas, en los que la solución más natural puede ser la que se expresa de esta forma.
Por ejemplo, para buscar un número en un vector podemos tener una función que reciba
como argumento el vector, el rango de búsqueda y el número buscado. El prototipo de esta
función sería como:
int busqueda(int vec[], int inicio, int fin, int num);
La función que quiere hacer la búsqueda haría el llamado indicando los rangos apropiados
para el vector:
resultado = busqueda(vector, 0, N, x);
La función busqueda() puede hacer su trabajo partiendo el vector en dos partes y
llamándose a sí misma en forma recursiva, de la siguiente manera:
res1 = busqueda(vec, inicio, fin-inicio/2, num);
res2 = busqueda(vec, (fin-inicio/2)+1, fin, num);
El caso base sería cuando el vector que recibe la función busqueda() contiene un único
elemento. En este caso simplemente compara el elemento con el número buscado y retorna
el valor apropiado.
Más adelante, en el capítulo sobre Ordenamiento y Búsqueda, se tratará en detalle este
algoritmo que recibe el nombre de Búsqueda Binaria.
∼
Ejemplo: Números de Fibonacci
La Sucesión de Fibonacci es una secuencia de números naturales, que empieza con 0 y 1, y
cuyos siguientes términos están definidos como la suma de los dos anteriores:
Algoritmos y Estructura de datos
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
Algoritmo Recursivo
Es bastante sencillo y natural definir la Sucesión de Fibonacci en forma recursiva:
A partir de esta definición, se puede escribir una función en C que genere el término nésimo de la sucesión. En el siguiente ejemplo se presenta esta función, junto con un
programa de prueba que pide un número entero al usuario y determina el correspondiente
término de la sucesión.
/***************************************************************************
* Ejemplo: Calculo recursivo del n-esimo termino de fibonacci
* Por definicion:
*
Fibonacci(0) = 0
*
Fibonacci(1) = 1
*
Fibonacci(n) = Fibonacci(n - 1) + Fibonacci(n - 2)
***************************************************************************/
double Fibonacci(int n) {
switch (n) {
case 0 : return 0;
case 1 : return 1;
default: return Fibonacci(n - 1) + Fibonacci(n - 2);
/* ^-- Llamado recursivo --^ */
}
}
/***************************************************************************
* Programa que calcula, de forma recursiva, el termino n-esimo de la
* sucesion de fibonacci, para un valor n dado por el usuario.
***************************************************************************/
main() {
int n;
printf("Ingrese el valor de n: ");
/* Se solicita al usuario el valor de n */
scanf("%d", &n);
/* scanf requiere puntero: & */
/* Imprime el fibonacci de n */
printf("El termino %d de Fibonacci es: %.0lf\n", n, Fibonacci(n));
}
Ejemplo de ejecución:
Ingrese el valor de n: 10
El termino 10 de Fibonacci es: 55
Ingrese el valor de n: 20
El termino 20 de Fibonacci es: 6765
Algoritmo Iterativo
En contraposición a la implementación recursiva que se presentó anteriormente, se ofrece
aquí una versión iterativa de la función para calcular el n-ésimo término de la Sucesión de
Fibonacci.
Algoritmos y Estructura de datos
Para implementar el algoritmo en forma iterativa, es necesario guardar en variables los
valores para los dos términos anteriores de la sucesión, para poder llevar a cabo el cálculo
del siguiente. Una vez hecho esto se actualizan los valores anteriores para preparar el
siguiente cálculo (siguiente iteración).
Compare ambas funciones. Aparentemente la versión recursiva es más limpia y clara (fácil
de entender) que su contraparte iterativa. ¿Cuál es su opinión?.
Se mantiene el mismo programa de prueba empleado anteriormente.
/**************************************************************************
* Ejemplo: Calculo iterativo del n-esimo termino de fibonacci
* Por definicion:
*
Fibonacci(0) = 0
*
Fibonacci(1) = 1
*
Fibonacci(n) = Fibonacci(n - 1) + Fibonacci(n - 2)
***************************************************************************/
/**************************************************************************
* Funcion que calcula iterativamente el n-esimo termino de fibonacci
**************************************************************************/
double Fibonacci(int n) {
int i;
double Fibi, Fibn1, Fibn2;
switch (n) {
case 0 : return 0;
case 1 : return 1;
default :
Fibn2 = 0; /* Fibonacci(n - 2) */
Fibn1 = 1; /* Fibonacci(n - 1) */
for (i = 2; i <= n; i++) {
Fibi = Fibn1 + Fibn2;
Fibn2 = Fibn1;
Fibn1 = Fibi;
}
return Fibi;
}
}
/***************************************************************************
* Programa que calcula, de forma iterativa, el termino n-esimo de la
* sucesion de fibonacci, para un valor n dado por el usuario.
***************************************************************************/
main() {
int n;
printf("Ingrese el valor de n: "); /* Se solicita al usuario el valor de n */
scanf("%d", &n);
/* scanf requiere puntero: & */
/* Imprime el fibonacci de n */
printf("El termino %d de Fibonacci es: %.0lf\n", n, Fibonacci(n));
}
Ejemplo de ejecución:
Ingrese el valor de n: 10
El termino 10 de Fibonacci es: 55
Ingrese el valor de n: 20
El termino 20 de Fibonacci es: 6765
Experimento Sugerido
Como se mencionó al introducir la recursividad, la implementación recursiva de un
algoritmo normalmente consume más memoria que su contraparte iterativa. Esto se debe a
que es necesario guardar el estado actual de la ejecución antes de cada llamado recursivo.
Algoritmos y Estructura de datos
Esto puede provocar limitaciones importantes en los programas que emplean funciones
recursivas.
Se sugiere probar los dos programas ofrecidos en esta sección para valores altos (a partir
de 20) y comparar los resultados obtenidos.
Variantes Propuestas
A manera de ejercicio pueden escribirse variantes de este programa, que empleen la
función Fibonacci, ya sea iterativa o recursiva:
•
Programa que lee un número y dice si pertenece a la Sucesión de Fibonacci.
•
Programa lee un número entero n y despliega en pantalla los primeros n términos de
la Sucesión de Fibonacci. En este caso, ¿existe alguna ventaja en usar una versión
sobre la otra?, ¿por qué?.
Algoritmos y Estructura de datos
∼
Ejemplo: Obtención del máximo en un vector de enteros
En este ejemplo mostraremos una función recursiva para encontrar el valor máximo en un
vector de números enteros. Se incluye un programa de prueba que recibe los valores del
vector del usuario y despliega el valor máximo calculado mediante la función mencionada.
Algoritmo
La definición recursiva de la función para obtener el máximo puede hacerse como:
•
•
•
Si el vector tiene un único elemento, ese elemento es el máximo (caso base).
Si el valor en la posición inicial del vector es mayor que el maximo del resto del
vector, entonces el valor de la primera posición es el máximo global.
Si el valor en la posición inicial del vector es menor que el maximo del resto del
vector, entonces el máximo global será el máximo en el resto del vector.
Escrito en palabras simples: Para encontrar el máximo en un vector, basta comparar el
primer elemento con el máximo del resto del vector.
/***************************************************************************
* Funcion recursiva que obtiene el maximo en un vector de numeros enteros.
* Retorna el valor del maximo elemento dentro del vector.
***************************************************************************/
#define TAMANO_VECTOR 100
/* Definicion del tamano maximo del vector */
int Maximo(int vector[], int n) {
int max, maxResto;
if (n == 1) max = vector[0]; /* Caso base */
else {
maxResto = Maximo(vector+1, n-1);
/* Maximo del resto del vector */
if (vector[0] > maxResto)
max = vector[0];
else
max = maxResto;
}
return(max);
}
main() {
int i;
int n=0;
int vec[TAMANO_VECTOR];
while (n < 1) {
/* Garantiza que el usuario ingresa un numero de eltos > 0 */
printf("Ingrese el numero de elementos: ");
scanf("%d", &n);
}
for (i=0; i<n; i++) {
/* Lee los elementos del vector */
printf("\tElemento[%d]: ", i);
scanf("%d", &vec[i]);
}
/* Calcula el maximo a la vez que despliega el resultado */
printf("\nMaximo: %d\n", Maximo(vec, n));
}
Ejemplo de ejecución:
Ingrese el numero de elementos: 5
Elemento[0]: 6
Elemento[1]: 10
Elemento[2]: 8
Elemento[3]: 15
Elemento[4]: 12
Maximo: 15
Algoritmos y Estructura de datos
∼
Ejemplo: Función para calcular módulo
El operador binario módulo obtiene el resto o residuo de la división de dos números
enteros. Es bastante sencillo definir en forma recursiva la operación m mód n. La idea es ir
restando el divisor del dividendo y calculando de nuevo el módulo, pues el resultado es el
mismo. La recursividad se detiene cuando el divisor es mayor que el dividendo. En ese caso
el resultado es el dividendo. La definición sería:
A partir de esta definición, se puede escribir una función en C que calcule m mód n, con m
y n como los parámetros de la función. En el siguiente ejemplo se presenta esta función,
junto con un programa de prueba que pide dos números enteros al usuario, calcula la
operación y muestra en pantalla el resultado.
/***********************************************************************
* Ejemplo: Calculo recursivo de m mod n
* Por definicion:
*
m mod n = m , si m < n
*
m mod n = ( m - n ) mod n , si m >= n
***********************************************************************/
int modulo(int m, int n) {
if (m < n)
return(m);
else
return( modulo( (m-n) , n) );
/* ^-- Llamado recursivo */
}
main() {
int m, n;
printf("Ingrese el valor de m: "); /* Se solicita los valores de m y n */
scanf("%d", &m);
/* scanf requiere puntero: & */
printf("Ingrese el valor de n: ");
scanf("%d", &n);
/* scanf requiere puntero: & */
/* Imprime el resultado de m mod n, empleando la funcion modulo */
printf("%d mod %d es: %d\n", m, n, modulo(m,n));
}
Ejemplo de ejecución:
Ingrese el valor de m: 10
Ingrese el valor de n: 3
10 mod 3 es: 1
Ingrese el valor de m: 3
Ingrese el valor de n: 5
3 mod 5 es: 3
Ingrese el valor de m: 3
Ingrese el valor de n: 3
3 mod 3 es: 0
Algoritmos y Estructura de datos
Ejercicio Propuesto
De manera similar a como se definió recursivamente el operador módulo es posible definir
el operador para división entera, es decir, división sobre operandos enteros y cuyo
resultado es entero (se ignoran los decimales). Se deja como ejercicio la definición
recursiva de este operador y la implementación de la función en C correspondiente. Como
programa de prueba puede emplearse el mismo que se empleó para el módulo con unos
pocos cambios.
∼
Ejemplo: Solución de un laberinto
Se presenta un programa que resuelve laberintos en forma recursiva. El laberinto a resolver
se representa mediante una matriz de números enteros.
Descripción
Se presenta un programa que resuelve laberintos en forma recursiva. El laberinto a resolver
se representa mediante una matriz de números enteros, y su configuración está dada por la
inicialización que se haga a dicha matriz. Por esto, para cambiar el laberinto será necesario
cambiar el código del programa. Una vez que se haya introducido el concepto de archivo
será posible ampliar este programa para que sea más útil, almacenando distintas
configuraciones de laberinto en distintos archivos.
Diseño
A continuación se aplicará nuestra metodología para la solución de problemas para diseñar
el programa mencionado.
1. Definición del problema
Conceptualización: El problema se ubica en un contexto en el que se debe buscar una
solución a un laberinto con una entrada y una salida, y con movimientos válidos en cuatro
direcciones (no diagonales). En el caso de este ejemplo, el contexto está limitado a una
aplicación de computador, en la que el laberinto está representado mediante una matriz de
números enteros.
Objetivo: Partiendo de la entrada del laberinto, señalar una ruta que nos guíe hasta la
salida. Por ejemplo, dado el siguiente laberinto:
Algoritmos y Estructura de datos
Una solución que nos permitiría alcanzar nuestro objetivo sería la ruta mostrada con
asteriscos en la siguiente figura:
Descripción general de la solución: El problema se resolverá haciendo uso de la
recursividad. La solución se basa en una tarea o función que se llamará recursivamente
para intentar solucionar el laberinto a partir de una posición particular. Desde cualquier
lugar dentro del laberinto se intentará resolverlo primero intentando hacia izquierda, luego
a la derecha, luego arriba y finalmente abajo. Para hacer estos intentos se empleará la
función recursiva. El procedimiento parte de la posición de entrada. Se utilizarán marcas
que serán dejadas en el camino para no intentar por caminos por los que ya se pasó y, en
particular, no volver atrás salvo si se determina que no hay solución por el camino que se
sigue hasta ahora.
Elementos involucrados: No existen elementos externos involucrados en la búsqueda de
la solución. El único elemento activo es el computador en sí llevando a cabo su trabajo. En
el problema participan también los siguientes elementos pasivos:
•
la matriz que representa el laberinto
•
la ubicación de la entrada
•
la ubicación de la salida
2. Conceptualización de la solución
Variables: Como se mencionó, el laberinto estará representado mediante una matriz de
números enteros: lab. Además, la posición de la entrada en el laberinto estará dada por las
coordenadas dentro de la matriz, representadas por las variables x0, y0, para la fila y la
columna, respectivamente. De manera similar, la posición de la salida del laberinto se
guardará en xf, yf Todas estas variables serán globales.
Cada posición de la matriz puede estar en uno de tres estados:
•
parte del muro (ladrillo)
•
camino libre no visitado
•
camino libre visitado
En el programa en C, la declaración de las variables y constantes globales se hará de la
siguiente manera:
Algoritmos y Estructura de datos
/* Constantes que definen la dimension del laberinto */
#define FILAS 13
#define COLUMNAS 21
/* Caracteres que se almacenan en las posiciones del laberinto:
/*
ASCII 219 es un cuadro relleno que se usa para las paredes
/*
' ' es un espacio en blanco, para los caminos libres
/*
'*' es la marca que senala las posiciones visitadas (camino)
#define L 219
#define E ' '
#define MARCA '*'
*/
*/
*/
*/
/* Constantes logicas para valor de retorno de funciones */
#define TRUE 1
#define FALSE 0
/* Matriz global que representa el laberinto. En la inicializacion, */
/* L representa un ladrillo en la pared y E un espacio en blanco
*/
int lab[FILAS][COLUMNAS] =
{ { L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L },
{ E, E, E, L, E, L, E, L, E, L, E, E, E, L, E, E, E, E, E, E, L },
{ L, L, E, L, E, E, E, L, E, E, E, L, E, L, E, L, E, E, L, E, L },
{ L, E, E, E, E, L, E, L, L, L, E, L, E, L, E, L, L, E, L, E, L },
{ L, E, L, L, L, L, E, L, E, L, E, L, E, L, L, L, E, E, L, E, L },
{ L, E, E, E, L, E, E, L, E, L, E, L, E, E, E, E, E, L, L, E, L },
{ L, E, L, L, L, L, L, L, E, L, E, L, L, E, L, E, E, E, E, E, L },
{ L, E, E, E, E, E, E, E, E, E, E, E, L, E, L, E, L, L, E, L, L },
{ L, L, L, L, L, L, L, L, L, L, L, E, L, E, L, E, E, L, E, L, L },
{ L, E, E, E, E, E, E, E, L, E, E, E, L, L, L, L, L, L, E, L, L },
{ L, E, L, L, L, L, L, E, E, E, L, L, L, E, L, E, E, E, E, E, L },
{ L, E, E, L, E, E, E, E, L, E, E, L, E, E, E, E, L, L, L, E, E },
{ L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L }
};
La inicialización de la matriz que aquí se muestra corresponde al laberinto presentado en la
figura del inicio. Los caminos están formados de espacios libres (E's). Observe el contorno
del laberinto, formado por L's, y los puntos de entrada y salida.
En la implementación de las distintas subtareas se hace uso de otras variables locales,
propias de la función de cada subtarea.
Descomposición en Tareas: El problema global puede descomponerse en las siguientes
tareas. Como las variables más importantes se manejan en forma global, no es necesario
pasarlas como parámetros. Este es el caso de la variable lab, la matriz que representa el
laberinto.
Cada una de las tareas aquí definidas se implementará como una función en el programa en
C. Para una mejor comprensión, se incluye el código en C que corresponde a cada una de
estas funciones.
Algoritmos y Estructura de datos
valida
Parámetros: fila y columna de la posición.
Valor de retorno: valor booleano que indica si la posición es válida.
Descripción: Esta función analiza una posición dentro del laberinto, especificada por la
fila y columna recibidas como parámetro, y deduce si es una posición válida para
recorrer. Básicamente debe verificar que no esté fuera de los límites del laberinto, que
no se trate de una posición en la que hay pared, y que no se trate de una posición
previamente visitada.
Algoritmo:
1. Inicialmente resultado vale VERDADERO
2. Si la posición dada está fuera del laberinto
2.1. Cambiar resultado a FALSO
3. Si la posición dada es pared o ya fue visitada
3.1. Cambiar resultado a FALSO
Código en C:
int valida(int f, int c) {
int resultado = TRUE;
/* Se supone inicialmente valida */
/* Controla si la posicion esta fuera del laberinto */
if ((f<0) || (f=FILAS) || (c<0) || (c=COLUMNAS))
resultado = FALSE;
/* Controla si la posicion ya fue visitada o es muro */
if (lab[f][c] == MARCA || lab[f][c] == L)
resultado = FALSE;
return(resultado);
}
recorrer
Parámetros: fila y columna (fil y col) desde donde se empieza el recorrido.
Valor de retorno: TRUE o FALSE que indica si se logró resolver el laberinto o no.
Descripción: Tarea recursiva que recorre el laberinto a partir de una posicion dada por
la fila y columna pasadas como parametro y devuelve un valor booleano que indica si se
logró llegar a la salida. El caso base de la recursividad (condición de término) es que las
coordenadas correspondan con la salida del laberinto. En caso contrario, se procede a
marcar la casilla y a intentar en las distintas direcciones (izquierda, derecha, arriba y
abajo), asegurándose primero que en esa dirección haya una casilla valida (no ladrillo,
no fuera del laberinto y no visitada). En caso en que ninguna dirección nos lleva a la
salida, se desmarca la casilla actual (pues se va a retroceder) y se devuelve falso como
valor de retorno. Las marcas sirven también para senalar la ruta que conforma la
solución, una vez alcanzada.
Algoritmos y Estructura de datos
Algoritmo:
1. Inicialmente listo vale FALSO.
2. Se marca la casilla.
3. Si la posición es el fin del laberinto.
3.1. Retornar VERDADERO.
4. Si no se ha solucionado (listo vale FALSO) y la posición de la
izquierda es válida.
4.1. Llamar a recorrer recursivamente con la casilla de la
izquierda como parámetro y guardar valor de retorno en listo,
para ver si se resolvió el laberinto a partir de ahí.
5. Si no se ha solucionado (listo vale FALSO) y la posición de la
derecha es válida.
5.1. Llamar a recorrer recursivamente con la casilla de la
derecha como parámetro y guardar valor de retorno en listo,
para ver si se resolvió el laberinto a partir de ahí.
6. Si no se ha solucionado (listo vale FALSO) y la posición de
arriba es válida.
6.1. Llamar a recorrer recursivamente con la casilla de arriba
como parámetro y guardar valor de retorno en listo, para ver si
se resolvió el laberinto a partir de ahí.
7. Si no se ha solucionado (listo vale FALSO) y la posición de
abajo es válida.
7.1. Llamar a recorrer recursivamente con la casilla de abajo
como parámetro y guardar valor de retorno en listo, para ver si
se resolvió el laberinto a partir de ahí.
8. Si no se logró resolver el laberinto en ninguna dirección
(listo todavía vale FALSO).
8.1. Quitar marca de la casilla (pues no formará parte del
camino).
9. Retornar listo indicando si se pudo resolver o no el laberinto.
Algoritmos y Estructura de datos
Código en C:
int recorrer(int fil, int col) {
int listo = FALSE;
/* Indica si se ha encontrado la salida */
/* Se marca la casilla como visitada */
lab[fil][col] = MARCA;
/* Se controla la condicion de termino de recursividad: */
/*
" Llegamos a la salida? "
*/
if (fil == xf && col == yf)
return(TRUE);
if (!listo && valida(fil,col-1))
/* Intento a la izquierda */
listo = recorre(fil,col-1);
if (!listo && valida(fil,col+1))
/* Intento a la derecha */
listo = recorre(fil,col+1);
if (!listo && valida(fil-1,col))
/* Intento hacia arriba */
listo = recorre(fil-1,col);
if (!listo && valida(fil+1,col))
/* Intento hacia abajo */
listo = recorre(fil+1,col);
/* Si no se logro resolver el laberinto desde esta posicion, se
*/
/* desmarca la casilla pues no sera parte de la solucion. En este */
/* caso se retornara falso lo que provocara que se retroceda.
*/
if (!listo)
lab[fil][col] = E;
/* Se retorna TRUE/FALSE dependiendo de si se encontro solucion */
return(listo);
}
desplegar
Parámetros: no tiene
Valor de retorno: no tiene
Descripción: Despliega en la pantalla el laberinto
Algoritmo:
1. Iterar i por todas las filas
1.1. Iterar j por todas las columnas
1.1.1. Desplegar posición i,j
1.2. Cambiar de línea pues terminó una fila
Código en C:
void desplegar() {
int i, j;
printf("\n");
for (i=0; i<FILAS; i++) {
for (j=0; j<COLUMNAS; j++) {
printf("%c", lab[i][j]);
}
printf("\n");
}
return;
}
Algoritmos y Estructura de datos
3. Especificación del algoritmo
El algoritmo principal debe llamar a la tarea que recorre el laberinto, especificando como
posición la entrada: ( x0 , y0 ). Antes y después de la resolución se despliega el laberinto.
La segunda vez incluirá el camino que forma la solución (las posiciones que fueron
marcadas y no desmarcadas posteriormente).
Algoritmo:
1. Desplegar el laberinto sin resolver
2. Recorrer desde la entrada del laberinto: ( x0 , y0 )
3. Si no se pudo resolver
3.1. Desplegar un mensaje indicando que no hay solución
4. Desplegar el laberinto una vez resuelto (con el camino)
Código en C:
main() {
int ok;
/* Para saber si se obtuvo solucion */
desplegar();
/* Despliega el laberinto sin resolver */
/* Resuelve el laberinto desde la entrada: (x0,y0) */
ok = recorrer(x0,y0);
if (!ok)
/* Si no se pudo resolver se envia un mensaje */
printf("\nLaberinto sin solucion\n");
desplegar();
/* Despliega el laberinto resuelto (con el camino) */
}
4. Validación del algoritmo
La validación de un algoritmo recursivo requiere verificar que la condición de término se
cumplirá en algún momento. En caso contrario, el algoritmo se llamará a sí mismo
recursivamente hasta que el stack se agote y el programa colapse (error de ejecución).
En este caso, la condición de término se alcanza cuando se logra llegar a la salida del
laberinto o cuando se han probado todas las posibles direcciones (izquierda, derecha, arriba
y abajo) y aún así no se ha encontrado solución. En este caso, el algoritmo recursivo se ha
planteado de una forma bastante segura. El procedimiento siempre termina, ya sea positiva
o negativamente, encontrando la solución o desistiendo de encontrarla, respectivamente.
Por otra parte, es necesario definir dominios que caractericen situaciones distintas bajo las
cuales se puede ejecutar el algoritmo.
Dominios: Deben definirse dominios en los que, tanto la entrada como la salida del
laberinto, se encuentren en distintas posiciones, incluso del mismo lado o hasta
contiguas.
Algoritmos y Estructura de datos
Validación: Ejecutando en algoritmo para los dominios definidos debe verificarse que la
solución cumple con la especificación planteada y por lo tanto alcanza el objetivo
propuesto.
5. Limitaciones del algoritmo
A simple vista el algoritmo no tiene limitaciones importantes. Sin embargo, no existe
una prueba formal de esto, por lo que se deja como ejercicio analizar la solución
propuesta a fin de determinar potenciales limitaciones.
Posibles modificaciones (Ejercicios)
Existen diversas variantes de este programa que pueden implementarse como ejercicio.
Algunas requieren leves modificaciones, principalmente en la función recursiva, pero otras
involucran mayores cambios.
•
¿Será posible eliminar los pasos innecesarios que puede provocar la función recursiva
suministrada?. Si es así, modifique la función para que no haga esos pasos.
•
El problema puede replantearse, indicando que el laberinto tiene una entrada y el
objetivo es buscar algún tesoro que se encuentra en el interior, tomarlo y regresar a
la entrada. Implemente la solución de este nuevo problema.
•
La solución presentada no toma en cuenta la dirección en la que se viene buscando la
solución. Puede implementarse una solución en la cual se mantenga la dirección
actual, y la búsqueda se haga haciendo giros a partir de esa dirección. ¿Tendría esto
alguna ventaja?. Trate de implementar la solución.
•
¿Será posible implementar una función recursiva que diseñe el laberinto?. Si lo logra
concebir, impleméntela. Esto sustituiría la tediosa inicialización de la matriz y le daría
mayor flexibilidad al programa. Ahora el programa plantea laberintos y luego los
resuelve.
•
Cuando se hayan estudiado bibliotecas para manejo de gráficos, será posible
implementar una versión animada de la solución del laberinto, en la que se vayan
mostrando los distintos caminos que va probando el algoritmo.
Algoritmos y Estructura de datos
/**********************************************************************
* Ejemplo: Solucion de un laberinto en forma recursiva.
* Para mejor comprensión ver la descripción adjunta.
**********************************************************************/
#include <stdio.h>
/* Constantes que definen la dimension del laberinto */
#define FILAS 13
#define COLUMNAS 21
/* Caracteres que se almacenan en las posiciones del laberinto:
/*
ASCII 219 es un cuadro relleno que se usa para las paredes
/*
' ' es un espacio en blanco, para los caminos libres
/*
'*' es la marca que senala las posiciones visitadas (camino)
#define L 219
#define E ' '
#define MARCA '*'
*/
*/
*/
*/
/* Constantes logicas para valor de retorno de funciones */
#define TRUE 1
#define FALSE 0
/* Matriz global que representa el laberinto. En la inicializacion, */
/* L representa un ladrillo en la pared y E un espacio en blanco
*/
int lab[FILAS][COLUMNAS]
{ { L, L, L, L, L, L,
{ E, E, E, L, E, L,
{ L, L, E, L, E, E,
{ L, E, E, E, E, L,
{ L, E, L, L, L, L,
{ L, E, E, E, L, E,
{ L, E, L, L, L, L,
{ L, E, E, E, E, E,
{ L, L, L, L, L, L,
{ L, E, E, E, E, E,
{ L, E, L, L, L, L,
{ L, E, E, L, E, E,
{ L, L, L, L, L, L,
};
=
L,
E,
E,
E,
E,
E,
L,
E,
L,
E,
L,
E,
L,
L,
L,
L,
L,
L,
L,
L,
E,
L,
E,
E,
E,
L,
L,
E,
E,
L,
E,
E,
E,
E,
L,
L,
E,
L,
L,
L,
L,
E,
L,
L,
L,
L,
E,
L,
E,
E,
E,
L,
L,
E,
E,
E,
E,
E,
E,
E,
L,
E,
L,
E,
L,
L,
E,
L,
L,
L,
L,
L,
E,
E,
E,
L,
L,
L,
L,
E,
E,
E,
E,
E,
L,
L,
L,
L,
L,
E,
L,
L,
L,
L,
L,
L,
E,
E,
E,
E,
L,
E,
E,
L,
L,
E,
E,
E,
L,
E,
L,
L,
L,
L,
L,
E,
L,
L,
E,
L,
L,
L,
E,
E,
E,
E,
L,
E,
E,
L,
L,
E,
E,
L,
E,
E,
E,
L,
E,
L,
E,
L,
L,
L,
E,
E,
E,
E,
L,
E,
L,
L,
L,
E,
L,
L,
L,
E,
L,
L,
L,
L,
E,
E,
E,
E,
E,
L,
L,
L,
E,
E,
E,
E,
E,
E,
L,
L,
L,
E,
E,
L,
L
L
L
L
L
L
L
L
L
L
L
E
L
},
},
},
},
},
},
},
},
},
},
},
},
}
/* Coordenadas de la entrada del laberinto */
int x0 = 1;
int y0 = 0;
/* Coordenadas de la salida del laberinto */
int xf = 11;
int yf = 20;
/**********************************************************************
* Funcion que despliega el laberinto en su totalidad.
**********************************************************************/
Algoritmos y Estructura de datos
void desplegar() {
int i, j;
printf("\n");
for (i=0; i<FILAS; i++) {
for (j=0; j<COLUMNAS; j++) {
printf("%c", lab[i][j]);
}
printf("\n");
}
return;
}
/**********************************************************************
* Analiza una posicion dentro del laberinto, especificada por la fila
* y columna recibidas como parametro, y deduce si es una posicion
* valida para recorrer. Basicamente debe verificar que no este fuera de
* los limites del laberinto, que no se trate de una posicion en la que
* hay pared, y que no se trate de una posicion previamente visitada.
**********************************************************************/
int valida(int f, int c) {
int resultado = TRUE;
/* Se supone inicialmente valida */
/* Controla si la posicion esta fuera del laberinto */
if ((f<0) || (f>=FILAS) || (c<0) || (c>=COLUMNAS))
resultado = FALSE;
/* Controla si la posicion ya fue visitada o es muro */
if (lab[f][c] == MARCA || lab[f][c] == L)
resultado = FALSE;
return(resultado);
}
/**********************************************************************
* Funcion recursiva que recorre el laberinto a partir de una posicion
* dada por la fila y columna pasadas como parametro y devuelve un valor
* booleano que indica si se logro llegar a la salida. El caso base de
* la recursividad (condicion de termino) es que las coordenadas
* correspondan con la salida del laberinto. En caso contrario, se
* procede a marcar la casilla y a intentar en las distintas direcciones
* (izquierda, derecha, arriba y abajo), asegurandose primero que en
* esa direccion haya una casilla valida (no ladrillo, no fuera del
* laberinto y no visitada). En caso en que ninguna direccion nos lleva
* a la salida, se desmarca la casilla actual (pues se va a retroceder)
* y se devuelve falso como valor de retorno. Las marcas sirven también
* para senalar la ruta que conforma la solucion, una vez alcanzada.
**********************************************************************/
int recorrer(int fil, int col) {
int listo = FALSE;
/* Indica si se ha encontrado la salida */
/* Se marca la casilla como visitada */
lab[fil][col] = MARCA;
/* Se controla la condicion de termino de recursividad: */
/*
" Llegamos a la salida? "
*/
if (fil == xf && col == yf)
return(TRUE);
if (!listo && valida(fil,col-1))
listo = recorrer(fil,col-1);
Algoritmos y Estructura de datos
/* Intento a la izquierda */
if (!listo
listo =
if (!listo
listo =
if (!listo
listo =
&& valida(fil,col+1))
recorrer(fil,col+1);
&& valida(fil-1,col))
recorrer(fil-1,col);
&& valida(fil+1,col))
recorrer(fil+1,col);
/* Intento a la derecha */
/* Intento hacia arriba */
/* Intento hacia abajo */
/*
/*
/*
if
Si no se logro resolver el laberinto desde esta posicion, se
*/
desmarca la casilla pues no sera parte de la solucion. En este */
caso se retornara falso lo que provocara que se retroceda.
*/
(!listo)
lab[fil][col] = E;
/* Se retorna TRUE/FALSE dependiendo de si se encontro solucion */
return(listo); }
/**********************************************************************
* Funcion principal para la resolucion del laberinto. Se despliega
* el laberinto antes y despues de resolverlo (encontrar el camino).
**********************************************************************/
main() {
int ok;
/* Para saber si se obtuvo solucion */
desplegar();
/* Despliega el laberinto sin resolver */
/* Resuelve el laberinto desde la entrada: (x0,y0) */
ok = recorrer(x0,y0);
if (!ok)
/* Si no se pudo resolver se envia un mensaje */
printf("\nLaberinto sin solucion\n");
desplegar();
/* Despliega el laberinto resuelto (con el camino) */
}
Obsérvense los pasos innecesarios que se dan (¿por qué ocurre?)
Algoritmos y Estructura de datos

Documentos relacionados