Alyx P. Hacker

Recursion

Introduccion al concepto de recursion, junto con ejercicios, y ejemplos utilizando codigo en C

Recursion

Recursion

Como Funciona la Recursion

Como Funcionan las Llamadas a una Funcion

Si tenemos un programa como el siguiente:

void
fun1 (int n)
{
  /* instruccion */
  /* instruccion */
  /* instruccion */
}

int
main (void)
{
  /* instruccion */
  /* instruccion */
  fun1 (x);
  /* instruccion */
  /* instruccion */
}

Cuando se ejecuta main, se van a ejecutar las instrucciones en orden (una despues de la otra), cuando llegue a la llamada de fun1, se van a ejecutar sus tres instrucciones, y el control del programa va a volver a main donde se van a ejecutar las dos instrucciones restantes (a menos que haya alguna otra operacion en esa linea, ya que se realizaria antes de pasar a la siguiente.)

A lo que se refiere con otra operacion, es por ejemplo si tenemos:

int a = fun1 (x) * 2;

La multiplicacion se va a realizar, una vez la ejecucion de fun1 haya terminado.

Recursion

Una funcion recursiva, es una funcion que se llama a si misma:

void
fun1 (/* parametros */)
{
  if (/* condicion base */)
    {
      fun1 (parametro);
    }
}

Algo importante sobre la recursion, es que siempre debe haber una condicion base que termine las llamadas recursivas, o se llamaria a si misma de forma infinita. Por eso, debe haber una condicion que haga que pare las llamadas recursivas.

Recursion tiene dos fases:

  1. Calling phase: cuando la funcion se llama a si misma.
  2. Returning phase: Cuando los resultados son pasados hacia arriba.

Complejidad de Espacio: Recursion

La complejidad de espacio de la recursion es \(O(n)\), ya que por cada llamada se esta creando un record de activacion en el stack \(n + 1\) veces (mas la llamada donde no se cumple la condicion base.)

Complejidad de Tiempo: Recursion

La complejidad de tiempo de la recursion es \(O(n)\) ya que las unidades de tiempo que tomara finalizar una llamada recursiva, depende del numero de veces \(n\) que tenga que ser llamada.

Variables Globales y Estaticas en Recursion

Como las variables globales y estaticas estan almacenadas en una seccion especial de .code, no se creara por cada llamada recursiva, sino que su valor se mantendra.

Ejemplo

int
func (int n)
{
  static int x = 0;
  if (n <= 0)
    return 0;

  x++;
  return func (n - 1) + x;
}

Trazo ATTACH

Tail Recursion (Recursividad de Cola)

Una funcion con Recursividad de Cola, es aquella que se llama a si misma, y este llamado es la ultima instruccion de la funcion. Por ejemplo:

void
func (int n)
{
  if (n <= 0)
    return;

  printf ("%d ", n);
  func (n - 1);
}

Si la llamada recursiva fuese de la siguiente forma, no seria recursiva de cola:

func (n - 1) + n;

Porque aun tiene pendiente agregar n al resultado de la llamada recursiva.

Recursividad de Cola vs Bucles

Todas las funciones recursivas pueden ser escritas con bucles, y vice-versa. Convertir las funciones con recursividad de cola a bucles es mas facil:

void
fun_rec (int n)
{
  if (n <= 0)
    return;

  printf ("%d ", n);
  fun_rec (n - 1);
}

void
fun_buc (int n)
{
  while (n > 0)
    {
      printf ("%d ", n);
      n--;
    }
}

La complejidad de tiempo de ambas es de \(O(n)\).

La complejidad de espacio de la funcion recursiva es \(O(n)\) y la complejidad de espacio de la funcion con bucle es \(O(1)\).

Por eso, si se va a escribir una funcion con recursividad de cola, es mejor reescribirla como un bucle debido a la complejidad de espacio. Incluso, algunos compiladores detectan estas funciones, y las convierten a bucles como optimizacion.

Head Recursion (Recursion de Cabeza)

Cuando una funcion se llama a si misma, como primera instruccion de la funcion, decimos que es recursiva de cabeza:

void
func (int n)
{
  if (n <= 0)
    return;

  func (n - 1);
  printf ("%d ", n);
}

Si se debe realizar alguna operacion antes de la llamada recursiva, entonces no es una funcion con recursividad de cabeza. Dado este caso, la funcion seria unicamente recursiva, no se le daria ningun nombre especial.

Recursividad de Cabeza vs Bucles

Convertir una funcion de recursividad de cabeza a bucles es mas complicado que las funciones recursivas de cola. Si queremos que la salida del programa con bucles sea la misma a la del ultimo ejemplo recursivo (1, 2, 3), tendriamos la siguiente funcion:

void
func_rec (int n)
{
  if (n <= 0)
    return;

  func_rec (n - 1);
  printf ("%d ", n);
}

void
func_buc (int n)
{
  int i = 1;
  while (i <= n)
    {
      printf ("%d ", i);
      i++;
    }
}

Tree Recursion (Recursion de Arbol)

En la recursion tenemos dos posibilidades de hacer una llamada recursiva: de forma lineal, o por la recursion de arbol.

Recursion Lineal

Llamamos recursion lineal, a la funcion que se llama a si misma una unica vez:

void
fun (int n)
{
  if (n <= 0)
    return;

  /* instruccion */
  /* instruccion */
  fun (n - 1);
  /* instruccion */
}

Recursion de Arbol

Llamamos recursion de arbol, a la funcion que se llama a si misma mas de una vez:

void
fun (int n)
{
  if (n <= 0)
    return;

  /* instruccion */
  /* instruccion */
  fun (n - 1);
  /* instruccion */
  fun (n - 1);
  /* instruccion */
  /* instruccion */
}

Ahi la funcion fun esta siendo llamada en 2 ocasiones.

Recursion Indirecta ATTACH

En la recursion indirecta, supongamos que tenemos 3 funciones: A, B y C:

En este ejemplo, A llama a B, B llama a C y C llama de nuevo a A.

Por ejemplo:

void A(int n);

void
B (int n)
{
  if (/* cond */)
    return;

  A (n - 1);
}

void
A (int n)
{
  if (/* cond */)
    return;

  B (n - 1);
}

Ejemplo

void fun2 (int n);

void
fun1 (int n)
{
  if (n <= 0)
    return;

  printf ("%d ", n);
  fun2 (n - 1);
}

void
fun2 (int n)
{
  if (n <= 1)
    return;

  printf ("%d ", n);
  fun1 (n / 2);
}

Trazo ATTACH

Recursion Anidada

En la recursion anidada, una llamada recursiva va a tomar otra llamada recursiva como parametro. Por ejemplo:

int
func (int n)
{
  if (/* condicion */)
    return;

  /* instruccion */
  /* instruccion */
  func (func (n - 1));
}

Ejemplo

int
func (int n)
{
  if (n > 100)
    return n - 10;
  return func (func (n + 11));
}

Trazo ATTACH

Ejemplos de Recursion

Suma de \(N\) Numeros Naturales Usando Recursion

Lo que queremos encontrar es \(1 + 2 + 3 + \cdots + n\).

Nosotros podemos definir esa funcion como:

\(\text{sum}(n) = 1 + 2 + 3 + \cdots + (n - 1) + n\)

Y recursivamente la podemos definir como:

\(\text{sum}(n) = \text{sum}(n - 1) + n\)

Y mas a fondo la podemos definir como:

\begin{equation} \text{sum}(n) = \begin{cases} 0 &\quad n = 0 \\ \text{sum}(n - 1) + n &\quad n > 0 \\ \end{cases} \end{equation}

Factorial Usando Recursion

Un factorial (denotado por “!”) es definido como:

\(n! = 1 \times 2 \times 3 \times \cdots \times (n - 1) \times n\)

Entonces el factorial de 5:

\(5! = 1 \times 2 \times 3 \times 4 \times 5 = 120\)

Es importante tener en cuenta que \(0! = 1, 1! = 1\). Entonces, podemos definir esta funcion recursivamente como:

\begin{equation} \text{fact}(n) = \begin{cases} 1 &\quad n = 0\\ \text{fact}(n - 1) \times n &\quad n>0 \end{cases} \end{equation}

Funcion Exponencial \(m^n\) Usando Recursion

Nosotros podemos definir la funcion exponencial como:

\(m^n = m \times m \times m \times m \times \cdots \times \text{n veces}\)

Y la podemos definir recursivamente como:

\begin{equation} \text{exp}(m, n) = \begin{cases} 1 &\quad n = 0 \\ \text{exp}(m, n - 1) \times m &\quad n > 0 \end{cases} \end{equation}

Sucesion de Fibonacci

La serie de fibonacci es la siguiente:

\(\text{0 1 1 2 3 5 8 13}\cdots\)

Cada uno de los terminos es obtenido con la suma de los dos terminos anteriores. Por ejemplo: \(1 + 1 = 2\), \(2 + 3 = 5\), \(5 + 8 = 13\), etc.

Los terminos iniciales son 0 y 1, mientras que los demas terminos son obtenidos con la suma de los dos terminos anteriores.

Matematicamente lo podemos definir como:

\begin{equation} \text{fib}(n) = \begin{cases} 0 &\quad n =0 \\ 1 &\quad n = 1\\ \text{fib}(n - 2) + \text{fib}(n - 1) &\quad n > 1 \end{cases} \end{equation}

La Torre de Hanoi ATTACH

El problema de la torre de hanoi es el siguiente:

Fuente: www.mathsisfun.com

Hay una cantidad \(n\) de discos en una torre A (de tres torres: A, B y C). El problema, es que debemos mover todos los discos de la torre A a la torre C y tenemos 2 reglas para hacerlo:

  1. Unicamente podemos mover un disco a la vez.
  2. No puede haber un disco mas grande sobre uno mas pequeno.

Siguiendo esas reglas tenemos que mover los \(n\) discos de torrea A a torre C.