Está en la página 1de 40

Tecnología de la Programación

Título de Grado en Ingeniería Informática


Curso 2010/11
Fernando Jiménez Barrionuevo
Gracia Sánchez Carpena
Mercedes Valdés Vela
Mari Carmen Garrido Carrera
Santiago Paredes Moreno

Departamento de Ingeniería de la Información de las Comunicaciones


Universidad de Murcia 1
Tecnología de la Programación

Teoría y Prácticas (30 + 30)


Metodología
Asistencia
Cambio de Grupo
Tutorías
Contenidos en SUMA
Evaluación

Tecnología de la Programación 2
Temario
Tema 1. Recursividad

1.1 Definición de recursividad


1.2 Recursividad en C++
1.3 Esquemas recursivos
1.4 Tiempo de ejecución de los algoritmos recursivos
1.5 Recursión vs iteración

Tecnología de la Programación 3
Tema 1. Recursividad
1.1 Definición de Recursividad
Se dice que un objeto es recursivo si forma parte de sí mismo o se
define en función de sí mismo.

Técnica particularmente potente en las definiciones matemáticas.

La potencia de la recursión reside en la posibilidad de definir un número


infinito de objetos mediante un enunciado finito.

De igual forma, un número infinito de operaciones de cálculo puede


describirse mediante un programa recursivo finito.

En general, un programa recursivo P puede expresarse como una


composición ℘ de instrucciones básicas Si (que no contienen a P)
y el propio P.
P≡℘[Si,P]

Tecnología de la Programación 4
Tema 1. Recursividad
1.1 Definición de Recursividad
Ejemplos:

Números naturales:
ƒ 1 es un número natural
ƒ El siguiente de un número natural es un número natural

Función factorial, n! (para números enteros no negativos):


ƒ 0! = 1
ƒ Si n>0 entonces n! = n · (n–1)!

3! = 3 · (3-1)!
= 3 · 2!
= 3 · 2 · (2-1)!
= 3 · 2 · 1!
= 3 · 2 · 1 · (1-1)!
= 3 · 2 · 1 · 0!
= 3 · 2 · 1 · 1
= 6
Tecnología de la Programación 5
Tema 1. Recursividad
1.1 Definición de Recursividad
Un algoritmo es recursivo si se invoca a sí mismo al menos una
vez.

ƒ Recursividad directa:
El algoritmo contiene una llamada explícita a sí mismo, es decir, A
invoca a A.

ƒ Recursividad indirecta:
El algoritmo se invoca a sí mismo de forma indirecta, es decir, A
invoca a B y B invoca a A.

ƒ Recursividad de cola o extremo final:


La llamada recursiva es la última instrucción que ejecuta el
algoritmo.

Tecnología de la Programación 6
Tema 1. Recursividad
1.1 Definición de Recursividad
Un algoritmo recursivo está bien construido si:

1. Contiene, al menos, un caso base, en el cual el algoritmo


no se llama a sí mismo.

2. Los valores de los parámetros del algoritmo en las


sucesivas llamadas recursivas están más cerca del caso
base que el valor con el que se llamó inicialmente al
algoritmo, garantizando que el caso base se alcanza.

Tecnología de la Programación 7
Tema 1. Recursividad
1.2 Recursividad en C++
Multiplicación n · m (m ≥ 0)

Caso Base: (m = 0) n · 0 = 0
Caso General: (m > 0) n · m = n + n · (m - 1)

// m>=0
int multiplica(int n, int m)
{
if (m==0) return 0;
return n+multiplica(n,m-1);
}

// m>=0 (version comprimida)


int multiplica(int n, int m)
{
return (m==0?0:n+multiplica(n,m-1));
}

Tecnología de la Programación 8
Tema 1. Recursividad
1.2 Recursividad en C++
División entera n / m (n ≥ 0 y m>0)

Caso Base: (n < m) n / m = 0


Caso General: (n ≥ m) n / m = 1 + ( n – m ) / m

// n>=0, m>0
int divide(int n, int m)
{
if (n<m) return 0;
return 1+divide(n-m,m);
}

// n>=0 y m>0 (version comprimida)


int divide(int n, int m)
{
return (n<m?0:1+divide(n-m,m));
}

Tecnología de la Programación 9
Tema 1. Recursividad
1.2 Recursividad en C++
Módulo n % m (n ≥ 0 y m > 0)

Caso Base: (n < m) n % m = n


Caso General: (n ≥ m) n % m = ( n – m ) % m

// n>=0, m>0
int modulo(int n, int m)
{
if (n<m) return n;
return modulo(n-m,m);
}

// n>=0 y m>0 (version comprimida)


int modulo(int n, int m)
{
return (n<m?n:modulo(n-m,m));
}

Tecnología de la Programación 10
Tema 1. Recursividad
1.2 Recursividad en C++
Potencia nm (m ≥ 0)

Caso Base: (m = 0) n 0 = 1
Caso General: (m > 0) n m = n · n m - 1

// m>=0
int potencia(int n, int m)
{
if (m==0) return 1;
return n*potencia(n,m-1);
}

// m>=0 (version comprimida)


int potencia(int n, int m)
{
return (m==0?1:n*potencia(n,m-1));
}

Tecnología de la Programación 11
Tema 1. Recursividad
1.2 Recursividad en C++
Factorial n! (n ≥ 0)

Caso Base: (n = 0) 0! = 1
Caso General: (n > 0) n! = n · ( n – 1 )!

// n>=0
int factorial(int n)
{
if (n==0) return 1;
return n*factorial(n-1);
}

// n>=0 (version comprimida)


int factorial(int n)
{
return (n==0?1:n*factorial(n-1));
}

Tecnología de la Programación 12
Tema 1. Recursividad
1.2 Recursividad en C++
Fibonacci fib(n) (n ≥ 0)

Caso Base: (n ≤ 1) fib(n) = n


Caso General: (n > 1) fib(n) = fib(n-2) + fib(n-1)

// n>=0
int fibonacci(int n)
{
if (n<=1) return n;
return fibonacci(n-2)+fibonacci(n-1);
}

// n>=0 (version comprimida)


int fibonacci(int n)
{
return (((n<=1))?n:fibonacci(n-2)+fibonacci(n-1));
}

Tecnología de la Programación 13
Tema 1. Recursividad
1.2 Recursividad en C++
Hanoi h(n,a,b,c) (n ≥ 1)

Caso Base: (n = 1) h(1,a,b,c): a → c


Caso General: (n > 1) h(n,a,b,c): { h(n-1,a,c,b), a → c, h(n-1,b,a,c) }

// n>=1
void hanoi(int n, char a, char b, char c)
{
if (n>1) hanoi(n-1,a,c,b);
cout << “Mover de “ << a << “ a “ << c << endl;
if (n>1) hanoi(n-1,b,a,c);
}

Tecnología de la Programación 14
Tema 1. Recursividad
1.2 Recursividad en C++
Nota C++: Entrada / Salida

Librería standard de C++ std


ƒ Operador << Operador de dirección
ƒ Objeto de flujo predefinido cout Salida estándar (la pantalla)
ƒ Constante endl Retorno de carro

#include <iostream>
using namespace std;

cout << a; // Dirige a hacia la salida estándar, es decir


// imprime a en la pantalla

cout << a << endl; // Imprime a y un retorno de carro


cout << a << “,“ << b << endl; // Imprime a,b y retorno de carro

Tecnología de la Programación 15
Tema 1. Recursividad
1.2 Recursividad en C++
Nota C++: Entrada / Salida

Librería standard de C++ std


ƒ Operador >> Operador de dirección
ƒ Objeto de flujo predefinido cin Entrada estándar (el teclado)

#include <iostream>
using namespace std;

cin >> a; // Dirige la entrada estándar hacia a,


// es decir, lee a del teclado

cin >> a >> b; // Dirige la entrada estándar hacia a y b,


// es decir, lee a del teclado

Tecnología de la Programación 16
Tema 1. Recursividad
Ejercicios Propuestos
Máximo Común Divisor mcd(n,m) (n ≥ 0, m ≥ 0, n ≥m)

Caso Base: (m == 0) mcd(n,m) = n


Caso General: (m > 0) mcd(n,m) = mcd(m,n%m)

Algoritmo recursivo en C++ que calcule el máximo común divisor de


dos enteros.

Tecnología de la Programación 17
Tema 1. Recursividad
1.3 Esquemas recursivos
Divide y Vencerás

función DV(x) devuelve solución

si x es suficientemente pequeño o sencillo entonces (Caso Base):


devolver ad hoc (x)

en otro caso (Caso General):


descomponer x en casos más pequeños x1,x2,...,xn
para i=1 hasta n hacer yi = DV(xi)
combinar los yi para obtener una solución y de x
devolver y

Tecnología de la Programación 18
Tema 1. Recursividad
1.3 Esquemas recursivos
Divide y Vencerás: Búsqueda Binaria
Busca un elemento e en un vector v ordenado.
Devuelve el índice de una ocurrencia del elemento e en del vector v.
Si el vector v no contiene ninguna ocurrencia del elemento e, devuelve -1.

// v está ordenado
int buscabin(int v[], int ini, int fin, int e)
{
if (ini>fin) return -1;
int med = (ini+fin)/2;
if (v[med]==e) return med;
if (v[med]>e) return buscabin(v,ini,med-1,e);
if (v[med]<e) return buscabin(v,med+1,fin,e);
}
// v está ordenado, tam es el numero de elementos de v
int buscabin(int v[], int e)
{
return buscabin(v,0,sizeof(v),e);
}

Tecnología de la Programación 19
Tema 1. Recursividad
1.3 Esquemas recursivos
Divide y Vencerás: Ordenación por Mezcla
Ordena un vector v desde el índice ini hasta el índice fin.

void mergesort(int v[], int ini, int fin)


{
if ((fin-ini)>0)
{
int med = (ini+fin)/2;
mergesort(v,ini,med);
mergesort(v,med+1,fin);
mezcla(v,ini,fin,med);
}
}
// tam es el numero de elementos de v
void mergesort(int v[])
{
mergesort(v,0,sizeof(v));
}

Tecnología de la Programación 20
Tema 1. Recursividad
1.3 Esquemas recursivos
Vuelta Atrás: Una solución
Devuelve cierto si encuentra una solución a partir de un paso, y falso en caso
contrario. Si encuentra una solución la devuelve en el parámetro solucion.

funcion VA(entrada: paso, salida: solución) devuelve booleano

si solución completa entonces devolver cierto fin si


para cada posibilidad de solución
si posibilidad aceptable en solución entonces
anotar posibilidad en solución
si VA(siguiente paso, solución) entonces devolver cierto fin si
cancelar posibilidad en solución
fin si
fin para
devolver falso

Tecnología de la Programación 21
Tema 1. Recursividad
1.3 Esquemas recursivos
Nota C++: Paso de objetos
ƒ Paso por valor: Ineficiente

void funcion(solucion s)

ƒ Paso por referencia: El objeto que pasamos va a modificarse

void funcion(solucion & s)

ƒ Paso por referencia constante: El objeto que pasamos no se modifica

void funcion(const solucion & s)

Tecnología de la Programación 22
Tema 1. Recursividad
1.3 Esquemas recursivos
Vuelta Atrás: Una solución
Solución compuesta por n pasos y m posibilidades para cada paso.
El parámetro solucion debe pasarse por referencia porque es de salida.
bool VA(int i, solucion & s)
{
if (i==n) return true;
for(int j=0;j<m;j++)
if (aceptable(s,i,j))
{
anotar(s,i,j);
if (VA(i+1,s)) return true;
cancelar(s,i,j);
}
return false;
}

LLamada inicial al método VA:


solucion s;
inicia(s);
if (VA(0,s)) imprime(s);

Tecnología de la Programación 23
Tema 1. Recursividad
1.3 Esquemas recursivos
Vuelta Atrás: Todas las soluciones
Obtiene todas las soluciones posibles a partir de un paso.

funcion VA(entrada: paso, salida: solución)

si solución completa entonces anotar solución


en otro caso
para cada posibilidad de solución
si posibilidad aceptable en solución entonces
anotar posibilidad en solución
VA(siguiente paso, solución)
cancelar posibilidad en solución
fin si
fin para
fin si

Tecnología de la Programación 24
Tema 1. Recursividad
1.3 Esquemas recursivos
Vuelta Atrás: Todas las soluciones
Solución compuesta por n pasos y m posibilidades para cada paso.
void VA(int i, solucion & s)
{
if (i==n) imprime(s);
else
for(int j=0;j<m;j++)
if (aceptable(s,i,j))
{
anotar(s,i,j);
VA(i+1,s);
cancelar(s,i,j);
}
}

LLamada inicial al método VA:


solucion s;
inicia(s);
VA(0,s);

Tecnología de la Programación 25
Tema 1. Recursividad
1.3 Esquemas recursivos
Vuelta Atrás: Ocho Reinas
n = 8 (8 pasos, un paso por cada reina)
m = 8 (8 posibilidades para cada reina)

x[i]: indica la posición en columna de la reina en la fila i-ésima


a[i]: true si la columna i-ésima está libre
b[i]: true si la diagonal derecha i-ésima está libre
c[i]: true si la diagonal izquierda i-ésima está libre
struct solucion
{
int x[8];
bool a[8];
bool b[15];
bool c[15];
};

Tecnología de la Programación 26
Tema 1. Recursividad
1.3 Esquemas recursivos
Vuelta Atrás: Ocho Reinas
void inicia(solucion & s)
{
for(int j=0;j<8;j++) s.a[j] = true;
for(int j=0;j<15;j++) { s.b[j] = true; s.c[j] = true; }
}
void imprime(const solucion & s)
{
for(int j=0;j<8;j++) cout << s.x[j] << " "; cout << endl;
}
bool aceptable(const solucion & s, int i, int j)
{
return s.a[j]&&s.b[i+j]&&s.c[i-j+7];
}
void anotar(solucion & s, int i, int j)
{
s.x[i]=j; s.a[j]=false; s.b[i+j]=false; s.c[i-j+7]=false;
}
void cancelar(solucion & s, int i, int j)
{
s.a[j]=true; s.b[i+j]=true; s.c[i-j+7]=true;
}

Tecnología de la Programación 27
Tema 1. Recursividad
1.4 Tiempo de ejecución de los algoritmos recursivos

Expansión de recurrencias

Utiliza la recurrencia misma para sustituir m<n por


cualquier T(m) en la derecha, hasta que todos los términos
T(m) para m>1 se hayan reemplazado por fórmulas que
impliquen sólo T(1).

Como T(1) siempre es constante, se tiene una fórmula


para T(n) en función de n y de algunas constantes.

A esta fórmula se le denomina “forma cerrada” para T(n).

Tecnología de la Programación 28
Tema 1. Recursividad
1.4 Tiempo de ejecución de los algoritmos recursivos

Ejemplo: Factorial

Ecuación de recurrencia:
T(0) = c1
T(n) = T(n-1) + c2 si n>0

Expansión de recurrencias:
T(n) = T(n-1)+c2 = [T(n-2)+c2]+c2 = T(n-2)+2c2 = [T(n-3)+c2]+2c2 = T(n-3)+3c2

Forma cerrada para T(n) (por inducción en i):


T(n) = T(n-i) + i c2

La expansión terminará cuando se alcance T(0) en lado derecho de la forma cerrada,


es decir, cuando n-i=0, por tanto i=n. Sustituyendo en la forma cerrada i por n se
obtiene:
T(n) = T(0) + n c2 = c1 + n c2

Esto demuestra que T(n) es O(n).

Tecnología de la Programación 29
Tema 1. Recursividad
1.4 Tiempo de ejecución de los algoritmos recursivos

Ejemplo: Búsqueda Binaria

Ecuación de recurrencia:
T(1) = c1
T(n) = T(n/2) + c2 si n>1

Expansión de recurrencias:
T(n) = T(n/2)+c2 = [T(n/4)+c2]+c2 = T(n/4)+2c2 = [T(n/8)+c2]+2c2 = T(n/8)+3c2

Forma cerrada para T(n) (por inducción en i):


T(n) = T(n/2i) + ic2

La expansión terminará cuando se alcance T(1) en lado derecho de la forma cerrada, es


decir, cuando n/2i=1, por tanto i=log n. Sustituyendo en la forma cerrada i por log n
se obtiene:
T(n) = T(1) + log n c2 = c1 + log n c2

Esto demuestra que T(n) es O(log n).

Tecnología de la Programación 30
Tema 1. Recursividad
1.4 Tiempo de ejecución de los algoritmos recursivos

Ejemplo: Mergesort

Ecuación de recurrencia:
T(1) = c1
T(n) = 2 T(n/2) + n c2 si n>1

Expansión de recurrencias:
T(n) = 2T(n/2)+nc2 = 2[2T(n/4)+(n/2)c2]+nc2 = 4T(n/4)+2nc2 = 8T(n/8)+3nc2

Forma cerrada para T(n) (por inducción en i):


T(n) = 2i T(n/2i) + i n c2

La expansión terminará cuando se alcance T(1) en lado derecho de la forma cerrada, es


decir, cuando n/2i=1, por tanto i=log n. Sustituyendo en la forma cerrada i por log n
se obtiene:
T(n) = nT(1) + (log n) n c2 = nc1 + n log n c2

Esto demuestra que T(n) es O(n log n).

Tecnología de la Programación 31
Tema 1. Recursividad
1.5 Recursión vs Iteración
La recursión implica múltiples llamadas y el uso de la pila interna para
almacenar, en cada llamada recursiva, los parámetros de llamada,
variables locales y dirección de retorno, lo que hace que en general
sea más ineficiente que el diseño iterativo.

Siempre es posible sustituir un algoritmo recursivo por un algoritmo


iterativo que utilice una pila.

Casos en los que no se debe utilizar la recursión:


1. Recursividad de cola
2. Árbol de recursión lineal (ejemplo, factorial)
3. Árbol de recursión ramificado, pero con nodos repetidos (ejemplo
Fibonacci)

Conclusión: la recursión se deberá utilizar cuando, además de facilitar el


diseño del algoritmo, genere un árbol de recursión ramificado y con
nodos no repetidos.

Tecnología de la Programación 32
Tema 1. Recursividad
1.5 Recursión vs Iteración
Multiplicación n · m (m ≥ 0)

Caso Base: (m = 0) n · 0 = 0
Caso General: (m > 0) n · m = n + n · (m - 1)

// m>=0 (versión recursiva)


int multiplica(int n, int m)
{
if (m==0) return 0;
return n+multiplica(n,m-1);
}
// m>=0 (versión iterativa)
int multiplica(int n, int m)
{
int res = 0;
for(;m>0;m--) res+=n;
return res;
}

Tecnología de la Programación 33
Tema 1. Recursividad
1.5 Recursión vs Iteración
División entera n / m (n ≥ 0 y m > 0)

Caso Base: (n < m) n / m = 0


Caso General: (n ≥ m) n / m = 1 + ( n – m ) / m

// n>=0, m>0 (versión recursiva)


int divide(int n, int m)
{
if (n<m) return 0;
return 1+divide(n-m,m);
}
// n>=0, m>0 (versión iterativa)
int divide(int n, int m)
{
int res = 0;
for(;n>=m;n-=m) res++;
return res;
}

Tecnología de la Programación 34
Tema 1. Recursividad
1.5 Recursión vs Iteración
Módulo n % m (n ≥ 0 y m > 0)

Caso Base: (n < m) n % m = n


Caso General: (n ≥ m) n % m = ( n – m ) % m

// n>=0, m>0 (versión recursiva)


int modulo(int n, int m)
{
if (n<m) return n;
return modulo(n-m,m);
}
// n>=0, m>0 (versión iterativa)
int modulo(int n, int m)
{
for(;n>=m;n-=m);
return n;
}

Tecnología de la Programación 35
Tema 1. Recursividad
1.5 Recursión vs Iteración
Potencia nm (m ≥ 0)

Caso Base: (m = 0) n 0 = 1
Caso General: (m > 0) n m = n · n m - 1

// m>=0 (versión recursiva)


int potencia(int n, int m)
{
if (m==0) return 1;
return n*potencia(n,m-1);
}
// m>=0 (versión iterativa)
int potencia(int n, int m)
{
int res = 1;
for(;m>0;m--) res*=n;
return res;
}

Tecnología de la Programación 36
Tema 1. Recursividad
1.5 Recursión vs Iteración
Factorial n! (n ≥ 0)

Caso Base: (n = 0) 0! = 1
Caso General: (n > 0) n! = n · ( n – 1 )!

// n>=0 (versión recursiva)


int factorial(int n)
{
if (n==0) return 1;
return n*factorial(n-1);
}
// n>=0 (version iterativa)
int factorial(int n)
{
int res = 1;
for(;n>0;n--) res*=n;
return res;
}

Tecnología de la Programación 37
Tema 1. Recursividad
1.5 Recursión vs Iteración
Fibonacci fib(n) (n ≥ 0)
Caso Base: (n ≤ 1) fib(n) = n
Caso General: (n > 1) fib(n) = fib(n-2) + fib(n-1)
// n>=0 (versión recursiva)
int fibonacci(int n)
{
if ((n<=1)) return n;
return fibonacci(n-2)+fibonacci(n-1);
}
// n>=0 (version iterativa)
int fibonacci(int n)
{
if (n<=1) return n;
int aux[n+1];
aux[0] = 0;
aux[1] = 1;
for(int i=2; i<=n; i++) aux[i] = aux[i-2] + aux[i-1];
int res = aux[n];
free(aux);
return res;
}

Tecnología de la Programación 38
Tema 1. Recursividad
1.5 Recursión vs Iteración
Búsqueda Binaria
// v está ordenado (versión recursiva)
int buscabin(int v[], int ini, int fin, int e)
{
if (ini>fin) return -1;
int med = (ini+fin)/2;
if (v[med]==e) return med;
if (v[med]>e) return buscabin(v,ini,med-1,e);
if (v[med]<e) return buscabin(v,med+1,fin,e);
}
// v está ordenado (versión iterativa)
int buscabin(int v[], int ini, int fin, int e)
{
while(ini<=fin)
{
int med = (ini+fin)/2;
if (v[med]==e) return med;
if (v[med]>e) fin = med-1; else ini = med+1;
}
return -1;
}

Tecnología de la Programación 39
Tema 1. Recursividad
Ejercicios Propuestos
Máximo Común Divisor mcd(n,m) (n ≥ 0, m ≥ 0, n ≥m)

Caso Base: (m == 0) mcd(n,m) = n


Caso General: (m > 0) mcd(n,m) = mcd(m,n%m)

Algoritmo iterativo que calcule el máximo común divisor de dos


enteros.

Tecnología de la Programación 40
Tema 1. Recursividad

También podría gustarte