Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Introducción a la Recursividad

Universidad Nacional de Río Negro

Introducción a la recursividad

Prerrequisitos: funciones, condicionales, parámetros por valor y stack frame. Debés poder trazar una llamada y reconocer cuándo una variable local deja de existir.

Objetivo: diseñar un caso base alcanzable y demostrar que cada llamada se acerca a él.

Comprobación de salida: señalá el caso base y la medida que decrece en una función recursiva propuesta.

Introducción

La recursividad es una técnica de programación fundamental en la que una función se llama a sí misma para resolver un problema. Este enfoque se basa en la idea de descomponer un problema complejo en subproblemas más pequeños y de la misma naturaleza.


Desarrollo

Definición Matemática

Desde una perspectiva matemática, una definición recursiva tiene dos partes esenciales:

  1. Caso Base: Es una o más condiciones terminales que no requieren de una nueva llamada a la función para ser resueltas. Es la solución explícita para el caso más simple del problema.

  2. Paso Recursivo (o Relación de Recurrencia): Es la regla que reduce el problema a una versión más simple de sí mismo. Define cómo se resuelve el problema para un caso n en términos de uno o más casos “menores” (por ejemplo, n-1).

Un ejemplo clásico es la función factorial, n!n!, que se define de la siguiente manera:

n!={1si n=0 (Caso Base)n×(n−1)!si n>0 (Paso Recursivo)n! = \begin{cases} 1 & \text{si } n = 0 \text{ (Caso Base)} \\ n \times (n-1)! & \text{si } n > 0 \text{ (Paso Recursivo)} \end{cases}

Esta definición establece que el factorial de 0 es 1 (caso base) y que el factorial de cualquier otro número natural n es n multiplicado por el factorial de n-1.

Construcción de Algoritmos Recursivos en C

Para implementar un algoritmo recursivo en C, debés seguir la estructura de la definición matemática.

Componentes Clave

Un algoritmo recursivo siempre debe tener:

Ejemplo: Función Factorial en C

Veamos cómo se traduce la definición matemática del factorial a una función en C.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
// Declaración de la función factorial
long int factorial(int n);
int main(void)
{
    int numero = 5;
    long int resultado = factorial(numero);
    if (resultado == -1)
    {
        printf("Error: no se puede calcular el factorial de un número
        negativo.\n");
    }
    else
    {
        printf("El factorial de %d es %ld\n", numero, resultado);
    }
    return 0;
}
// Definición de la función recursiva
long int factorial(int n)
{
    // Validación de precondición (robustez ante valores inválidos, regla
    {
        ref
    }`0x2001h`)
    if (n < 0)
    {
        return -1;
    }
    // Caso Base: si n es 0, el factorial es 1.
    if (n == 0)
    {
        return 1;
    }
    // Paso Recursivo: n * factorial(n-1)
    return n * factorial(n - 1);
}
Análisis del Código
  1. Caso Base: La línea if (n == 0) comprueba la condición de parada. Si n es 0, la función retorna 1 y la cadena de llamadas recursivas comienza a resolverse.

  2. Paso Recursivo: En la última sentencia, la función retorna el resultado de n multiplicado por el valor devuelto por la llamada a factorial(n - 1). Esta llamada opera sobre un subproblema de menor tamaño (n-1), garantizando la convergencia hacia el caso base.

A continuación se muestra de forma gráfica y formal la distribución física en memoria del Call Stack durante el cálculo recursivo de factorial(3) hasta alcanzar el caso base, ilustrando las direcciones físicas de memoria en la pila y las direcciones lógicas de retorno de código:

Crecimiento y colapso de los marcos de pila en la recursión de factorial(3).
Cada llamada apila un nuevo marco temporal consumiendo espacio físico de memoria
RAM.

Figure 1:Crecimiento y colapso de los marcos de pila en la recursión de factorial(3). Cada llamada apila un nuevo marco temporal consumiendo espacio físico de memoria RAM.

Como se observa en el diagrama, cada llamada suspendida (factorial(3) y factorial(2)) mantiene su estado completo en una dirección de memoria diferente de la RAM. Solo cuando factorial(1) retorna su valor constante 1 a la dirección de retorno de su llamador, el marco superior se destruye (se desplaza el puntero de pila rsp) y se reanuda la evaluación aritmética en el marco inmediatamente inferior.

El Peligro de la Recursividad: Stack Overflow y la Paradoja del Factorial

El tamaño total disponible para la pila de llamadas (call stack) es finito (típicamente entre 1 y 8 megabytes en sistemas Unix/Linux). Si el consumo de pila excede dicho límite físico, se produce un desbordamiento catastrófico de pila o stack overflow, lo cual interrumpe inmediatamente el programa con un fallo de segmentación.

Las causas principales de este fallo son:

  1. Ausencia o fallo en el Caso Base (Recursión Infinita): Si la condición de parada no se cumple o los parámetros no convergen al caso base.

  2. Recursión Demasiado Profunda: Aún si el algoritmo es lógicamente correcto, si la profundidad de llamadas es excesiva, la pila se agotará.

La Paradoja del Factorial: Límites del Tipo de Dato vs. Límites de la Pila

Ejercicios de Autoevaluación

Definición Matemática

Construcción de Algoritmos en C

Diagnóstico y Estabilidad


Glosario

Recursión
Técnica de programación y diseño algorítmico donde una función se define e invoca en términos de sí misma.
Caso base
Condición lógica terminal en un algoritmo recursivo que detiene la recursión y retorna un resultado de forma directa sin realizar nuevas llamadas.
Paso recursivo
Sentencia lógica en una función recursiva donde se realiza una nueva invocación a la propia función sobre un subproblema de menor tamaño.
Stack Overflow
Desbordamiento físico de la pila de llamadas del sistema provocado por el consumo excesivo de memoria física asignada al stack.
Desbordamiento aritmético
Situación física en la cual el resultado numérico de una operación excede los límites representables por el tipo de dato físico de la variable.

Síntesis y Resumen

En este capítulo analizaste los principios de la recursión y su comportamiento en memoria:


Referencias y Lecturas Complementarias