32. Funciones recursivas básicas

Una función recursiva se define en términos de sí misma. Resuelve un problema grande dividiéndolo en instancias más pequeñas del mismo tipo, hasta alcanzar un caso base que detiene el proceso.

32.1 Introducción

Hasta ahora hemos estudiado funciones que se definen mediante fórmulas directas o tablas de valores. Sin embargo, muchos problemas discretos se describen de forma natural como **repetición de un mismo patrón sobre un tamaño decreciente**.

La **recursión** es el mecanismo matemático y computacional que formaliza esta idea: una función recursiva resuelve un problema invocándose a sí misma con una entrada más simple, hasta llegar a un **caso base** que puede resolverse directamente sin nuevas llamadas.

32.2 Definición Formal

Una función f definida sobre un subconjunto de los enteros no negativos (o sobre otro dominio ordenado) es **recursiva** si su definición incluye dos partes:

1. Caso base: f(0) = c (o f(k) = c para algún valor fijo k) 2. Caso recursivo: f(n) = g(n, f(n - 1)) para n > k

La función g combina el valor actual n con el resultado de la llamada recursiva f(n − 1). Cada llamada reduce el problema hacia el caso base, garantizando que el proceso termine en un número finito de pasos.

32.3 Caso Base y Condición de Terminación

El **caso base** es indispensable: sin él, la función seguiría llamándose indefinidamente. En programación esto produce un error de **desbordamiento de pila** (stack overflow).

La **condición de terminación** es la regla que decide cuándo dejar de hacer llamadas recursivas y devolver un valor directo. Por ejemplo, en el factorial:

factorial(0) = 1 ← caso base factorial(n) = n · factorial(n − 1) ← caso recursivo, válido si n > 0

Cada llamada a factorial(n) con n > 0 delega el trabajo a factorial(n − 1), acercándose en una unidad al caso base en cada paso.

32.4 Simulador: Pila de Llamadas Recursivas

El simulador muestra cómo el intérprete gestiona las llamadas recursivas mediante una **pila de activación**. Cada nueva llamada apila un marco (frame) con el valor del parámetro; al retornar, el marco se desapila y el resultado sube al llamador. Usa **Siguiente Paso** para avanzar paso a paso o **Reproducir** para ver la animación completa.

Visualizador de Pila Recursiva Paso 0 / 0
n =
Selecciona una función y pulsa "Siguiente Paso" para observar la pila de llamadas.

32.5 Ejemplos Clásicos

Factorial: cuenta las permutaciones de n elementos distintos.

factorial(0) = 1 factorial(n) = n · factorial(n − 1) si n > 0 Ejemplo: factorial(4) = 4 · 3 · 2 · 1 = 24

Suma de los primeros n enteros positivos:

suma(0) = 0 suma(n) = n + suma(n − 1) si n > 0 Ejemplo: suma(4) = 4 + 3 + 2 + 1 = 10

Ambas funciones comparten la misma estructura: un caso base que detiene la recursión y un paso que reduce el problema en una unidad.

32.6 Recursión en Programación

En JavaScript, una función recursiva se implementa haciendo que el cuerpo de la función invoque a la misma función con un argumento más pequeño, siempre verificando primero el caso base. Todo algoritmo recursivo finito puede reescribirse con un bucle (versión iterativa), pero la forma recursiva suele reflejar mejor la definición matemática.

// Factorial recursivo
function factorial(n) {
  if (n === 0) return 1;           // caso base
  return n * factorial(n - 1);     // caso recursivo
}

// Suma recursiva de 1 + 2 + ... + n
function suma(n) {
  if (n === 0) return 0;           // caso base
  return n + suma(n - 1);          // caso recursivo
}

console.log("factorial(5) =", factorial(5)); // 120
console.log("suma(5) =", suma(5));         // 15

// Versión iterativa equivalente del factorial (para comparar)
function factorialIterativo(n) {
  let resultado = 1;
  for (let i = 2; i <= n; i++) {
    resultado *= i;
  }
  return resultado;
}

console.log("factorialIterativo(5) =", factorialIterativo(5)); // 120

32.7 Errores Comunes

  • Olvidar el caso base: La función nunca termina y provoca un desbordamiento de pila. Siempre define explícitamente cuándo dejar de llamarse.
  • No acercarse al caso base: Llamar a f(n - 2) cuando el caso base es f(0) puede saltarse valores y no terminar correctamente; o llamar a f(n + 1) aleja la entrada del caso base.
  • Confundir recursión con iteración: Un bucle for que repite una operación no es recursión; la recursión requiere que la función se invoque a sí misma.
  • Recalcular subproblemas repetidos: En funciones como Fibonacci ingenua, la misma llamada se repite muchas veces. Las técnicas de memoización o programación dinámica evitan este costo (se estudian en temas avanzados).

32.8 Qué debes recordar de este tema

  • Una función recursiva se define con un **caso base** y un **caso recursivo**.
  • Cada llamada recursiva debe acercar el problema al caso base para garantizar la terminación.
  • El intérprete gestiona las llamadas mediante una **pila de activación**: cada llamada apila un marco y cada retorno lo desapila.
  • Ejemplos clásicos: factorial y suma de enteros.
  • Toda recursión finita puede expresarse de forma iterativa, pero la forma recursiva suele reflejar mejor la definición matemática.

32.9 Conclusión

Las funciones recursivas modelan problemas que se repiten a escala decreciente: desde contar combinaciones hasta recorrer estructuras anidadas. Comprender la pila de llamadas es clave para depurar programas recursivos y anticipar límites de memoria.

En el próximo tema estudiaremos las **sucesiones y progresiones discretas**, analizando cómo los términos de una lista ordenada evolucionan según reglas aritméticas o geométricas, y cómo se conectan con las definiciones recursivas que acabamos de ver.