Recursión en Python: Funciones Recursivas con Ejemplos Prácticos

La recursión es una técnica de programación donde una función se llama a sí misma para resolver un problema dividiéndolo en subproblemas más pequeños. En esta guía completa aprenderás todo sobre funciones recursivas en Python, desde conceptos básicos hasta patrones avanzados.
Contenido
- 1 ¿Qué es la Recursión en Python?
- 2 Anatomía de una Función Recursiva
- 3 Ejemplos Clásicos de Recursión
- 4 El Call Stack (Pila de Llamadas)
- 5 Recursión con Estructuras de Datos
- 6 Recursión vs Iteración
- 7 Límite de Recursión en Python
- 8 Casos Prácticos de Recursión
- 9 Errores Comunes y Cómo Evitarlos
- 10 Buenas Prácticas con Recursión
- 11 Recursión de Cola (Tail Recursion)
- 12 Pepitas de conocimiento:
- 13 Profundiza en Python
¿Qué es la Recursión en Python?
La recursión es un método de resolución de problemas donde la solución depende de soluciones a instancias más pequeñas del mismo problema.
def cuenta_regresiva(n):
    """Función recursiva simple"""
    if n <= 0:  # Caso base
        print("¡Despegue!")
    else:  # Caso recursivo
        print(n)
        cuenta_regresiva(n - 1)  # Llamada recursiva
cuenta_regresiva(5)
# Output:
# 5
# 4
# 3
# 2
# 1
# ¡Despegue!Componentes esenciales de la recursión:
- Caso base: Condición que detiene la recursión
- Caso recursivo: Llamada a la función misma con argumentos modificados
- Progreso hacia el caso base: Cada llamada debe acercarse al caso base
Anatomía de una Función Recursiva

Toda función recursiva debe tener una estructura clara:
def funcion_recursiva(parametros):
    # 1. Caso base (condición de parada)
    if condicion_base:
        return valor_base
    # 2. Caso recursivo
    # Procesamiento
    resultado = funcion_recursiva(parametros_modificados)
    # 3. Retorno del resultado
    return resultadoEjemplo: Factorial
El factorial de un número (n!) es un caso clásico de recursión:
def factorial(n):
    """Calcula el factorial de n de forma recursiva"""
    # Caso base
    if n == 0 or n == 1:
        return 1
    # Caso recursivo: n! = n * (n-1)!
    return n * factorial(n - 1)
# Ejemplos
print(factorial(5))  # Output: 120 (5*4*3*2*1)
print(factorial(3))  # Output: 6 (3*2*1)
print(factorial(0))  # Output: 1¿Cómo funciona internamente?
factorial(5)
  = 5 * factorial(4)
  = 5 * (4 * factorial(3))
  = 5 * (4 * (3 * factorial(2)))
  = 5 * (4 * (3 * (2 * factorial(1))))
  = 5 * (4 * (3 * (2 * 1)))
  = 120Ejemplos Clásicos de Recursión
1. Secuencia de Fibonacci
Cada número es la suma de los dos anteriores:
def fibonacci(n):
    """Retorna el n-ésimo número de Fibonacci"""
    # Casos base
    if n <= 0:
        return 0
    if n == 1:
        return 1
    # Caso recursivo: F(n) = F(n-1) + F(n-2)
    return fibonacci(n - 1) + fibonacci(n - 2)
# Ejemplos
for i in range(8):
    print(f"fibonacci({i}) = {fibonacci(i)}")
# Output:
# fibonacci(0) = 0
# fibonacci(1) = 1
# fibonacci(2) = 1
# fibonacci(3) = 2
# fibonacci(4) = 3
# fibonacci(5) = 5
# fibonacci(6) = 8
# fibonacci(7) = 132. Suma de una Lista
def suma_recursiva(lista):
    """Suma todos los elementos de una lista recursivamente"""
    # Caso base: lista vacía
    if len(lista) == 0:
        return 0
    # Caso recursivo: primer elemento + suma del resto
    return lista[0] + suma_recursiva(lista[1:])
numeros = [1, 2, 3, 4, 5]
print(suma_recursiva(numeros))  # Output: 153. Potencia
def potencia(base, exponente):
    """Calcula base^exponente de forma recursiva"""
    # Caso base
    if exponente == 0:
        return 1
    # Caso recursivo: base^n = base * base^(n-1)
    return base * potencia(base, exponente - 1)
print(potencia(2, 5))   # Output: 32
print(potencia(3, 3))   # Output: 27
print(potencia(10, 0))  # Output: 1El Call Stack (Pila de Llamadas)

Python usa una pila de llamadas para gestionar las llamadas recursivas:
def ejemplo_stack(n):
    """Demuestra el call stack"""
    print(f"Entrando con n={n}")
    if n <= 0:
        print("¡Caso base alcanzado!")
        return
    ejemplo_stack(n - 1)  # Llamada recursiva
    print(f"Saliendo con n={n}")
ejemplo_stack(3)
# Output:
# Entrando con n=3
# Entrando con n=2
# Entrando con n=1
# Entrando con n=0
# ¡Caso base alcanzado!
# Saliendo con n=1
# Saliendo con n=2
# Saliendo con n=3Visualización del stack:
ejemplo_stack(3)
  └─ ejemplo_stack(2)
      └─ ejemplo_stack(1)
          └─ ejemplo_stack(0)  ← Caso baseRecursión con Estructuras de Datos
Recorrer Directorios
import os
def listar_archivos(ruta, nivel=0):
    """Lista archivos y carpetas recursivamente"""
    try:
        elementos = os.listdir(ruta)
        for elemento in elementos:
            ruta_completa = os.path.join(ruta, elemento)
            print("  " * nivel + elemento)
            # Si es directorio, llamada recursiva
            if os.path.isdir(ruta_completa):
                listar_archivos(ruta_completa, nivel + 1)
    except PermissionError:
        print("  " * nivel + "[Permiso denegado]")
# Uso
listar_archivos("./mi_proyecto")Aplanar Listas Anidadas
def aplanar_lista(lista):
    """Convierte una lista anidada en una lista plana"""
    resultado = []
    for elemento in lista:
        # Si el elemento es una lista, recursión
        if isinstance(elemento, list):
            resultado.extend(aplanar_lista(elemento))
        else:
            resultado.append(elemento)
    return resultado
# Ejemplo
anidada = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
print(aplanar_lista(anidada))
# Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]Búsqueda en Árbol Binario
class Nodo:
    def __init__(self, valor):
        self.valor = valor
        self.izquierdo = None
        self.derecho = None
def buscar_en_arbol(nodo, objetivo):
    """Busca un valor en un árbol binario recursivamente"""
    # Caso base: árbol vacío
    if nodo is None:
        return False
    # Caso base: valor encontrado
    if nodo.valor == objetivo:
        return True
    # Caso recursivo: buscar en subárboles
    return (buscar_en_arbol(nodo.izquierdo, objetivo) or
            buscar_en_arbol(nodo.derecho, objetivo))
# Crear árbol
raiz = Nodo(10)
raiz.izquierdo = Nodo(5)
raiz.derecho = Nodo(15)
raiz.izquierdo.izquierdo = Nodo(3)
raiz.izquierdo.derecho = Nodo(7)
print(buscar_en_arbol(raiz, 7))   # Output: True
print(buscar_en_arbol(raiz, 20))  # Output: FalseRecursión vs Iteración
Muchos problemas recursivos pueden resolverse con bucles:
Factorial Iterativo
# Versión recursiva
def factorial_rec(n):
    if n <= 1:
        return 1
    return n * factorial_rec(n - 1)
# Versión iterativa
def factorial_iter(n):
    resultado = 1
    for i in range(2, n + 1):
        resultado *= i
    return resultado
# Ambas producen el mismo resultado
print(factorial_rec(5))   # Output: 120
print(factorial_iter(5))  # Output: 120Comparación:
| Aspecto | Recursión | Iteración | 
|---|---|---|
| Legibilidad | Más clara para problemas naturalmente recursivos | Más directa para bucles simples | 
| Memoria | Usa el call stack (puede causar stack overflow) | Menos memoria | 
| Rendimiento | Generalmente más lenta | Generalmente más rápida | 
| Casos de uso | Árboles, grafos, divide y vencerás | Bucles simples, procesamiento secuencial | 
Fibonacci Optimizado con Memoización
La recursión puede optimizarse con memoización (cache de resultados):
# Sin memoización (muy lento para n > 35)
def fib_lento(n):
    if n <= 1:
        return n
    return fib_lento(n - 1) + fib_lento(n - 2)
# Con memoización usando decorador
from functools import lru_cache
@lru_cache(maxsize=None)
def fib_rapido(n):
    if n <= 1:
        return n
    return fib_rapido(n - 1) + fib_rapido(n - 2)
# Comparación
import time
inicio = time.time()
print(fib_lento(35))
print(f"Sin memo: {time.time() - inicio:.2f}s")  # ~5 segundos
inicio = time.time()
print(fib_rapido(35))
print(f"Con memo: {time.time() - inicio:.4f}s")  # ~0.0001 segundosLímite de Recursión en Python
Python tiene un límite de recursión por defecto (generalmente 1000):
import sys
# Ver el límite actual
print(sys.getrecursionlimit())  # Output: 1000
# Ejemplo que excede el límite
def recursion_infinita(n):
    return recursion_infinita(n + 1)
try:
    recursion_infinita(0)
except RecursionError as e:
    print(f"Error: {e}")
    # Output: Error: maximum recursion depth exceeded
# Modificar el límite (con precaución)
sys.setrecursionlimit(2000)
print(sys.getrecursionlimit())  # Output: 2000⚠️ Advertencia: Aumentar el límite puede causar crashes si no hay suficiente memoria.
Casos Prácticos de Recursión

1. Validador de Palíndromos
def es_palindromo(texto):
    """Verifica si un texto es palíndromo recursivamente"""
    # Limpiar el texto
    texto = texto.lower().replace(" ", "")
    # Caso base: texto vacío o de un carácter
    if len(texto) <= 1:
        return True
    # Caso recursivo: comparar extremos
    if texto[0] != texto[-1]:
        return False
    return es_palindromo(texto[1:-1])
# Ejemplos
print(es_palindromo("reconocer"))        # True
print(es_palindromo("anita lava la tina"))  # True
print(es_palindromo("python"))           # False2. Generador de Permutaciones
def permutaciones(elementos):
    """Genera todas las permutaciones de una lista"""
    # Caso base: un solo elemento
    if len(elementos) <= 1:
        return [elementos]
    # Caso recursivo
    perms = []
    for i, elem in enumerate(elementos):
        # Elementos restantes
        resto = elementos[:i] + elementos[i+1:]
        # Permutaciones del resto
        for perm in permutaciones(resto):
            perms.append([elem] + perm)
    return perms
# Ejemplo
letras = ['A', 'B', 'C']
for p in permutaciones(letras):
    print(p)
# Output:
# ['A', 'B', 'C']
# ['A', 'C', 'B']
# ['B', 'A', 'C']
# ['B', 'C', 'A']
# ['C', 'A', 'B']
# ['C', 'B', 'A']3. Búsqueda Binaria Recursiva
def busqueda_binaria(lista, objetivo, inicio=0, fin=None):
    """Busca un elemento en una lista ordenada recursivamente"""
    if fin is None:
        fin = len(lista) - 1
    # Caso base: elemento no encontrado
    if inicio > fin:
        return -1
    # Punto medio
    medio = (inicio + fin) // 2
    # Caso base: elemento encontrado
    if lista[medio] == objetivo:
        return medio
    # Caso recursivo: buscar en mitad correspondiente
    if lista[medio] > objetivo:
        return busqueda_binaria(lista, objetivo, inicio, medio - 1)
    else:
        return busqueda_binaria(lista, objetivo, medio + 1, fin)
# Ejemplo
numeros = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
print(busqueda_binaria(numeros, 13))  # Output: 6
print(busqueda_binaria(numeros, 8))   # Output: -1Errores Comunes y Cómo Evitarlos
1. Olvidar el Caso Base
# ❌ MAL: Recursión infinita
def contar_mal(n):
    print(n)
    contar_mal(n + 1)  # ¡Nunca termina!
# ✅ BIEN: Con caso base
def contar_bien(n, limite):
    if n > limite:  # Caso base
        return
    print(n)
    contar_bien(n + 1, limite)2. No Avanzar Hacia el Caso Base
# ❌ MAL: No progresa
def suma_mal(lista):
    if len(lista) == 0:
        return 0
    return lista[0] + suma_mal(lista)  # ¡Siempre la misma lista!
# ✅ BIEN: Reduce el problema
def suma_bien(lista):
    if len(lista) == 0:
        return 0
    return lista[0] + suma_bien(lista[1:])  # Lista más pequeña3. No Retornar el Resultado Recursivo
# ❌ MAL: No retorna el resultado
def factorial_mal(n):
    if n <= 1:
        return 1
    factorial_mal(n - 1)  # Falta el return
# ✅ BIEN: Retorna el resultado
def factorial_bien(n):
    if n <= 1:
        return 1
    return n * factorial_bien(n - 1)4. Modificar Variables Globales
# ❌ MAL: Usa variable global
total = 0
def suma_global(lista):
    global total
    if len(lista) == 0:
        return total
    total += lista[0]
    return suma_global(lista[1:])
# ✅ BIEN: Parámetro acumulador
def suma_acumulador(lista, acumulador=0):
    if len(lista) == 0:
        return acumulador
    return suma_acumulador(lista[1:], acumulador + lista[0])Buenas Prácticas con Recursión
1. Usar Recursión para Problemas Naturalmente Recursivos
# Bueno para: estructuras jerárquicas
def calcular_tamano_directorio(ruta):
    """Calcula el tamaño total de un directorio"""
    import os
    tamano = 0
    for elemento in os.listdir(ruta):
        ruta_completa = os.path.join(ruta, elemento)
        if os.path.isfile(ruta_completa):
            tamano += os.path.getsize(ruta_completa)
        elif os.path.isdir(ruta_completa):
            tamano += calcular_tamano_directorio(ruta_completa)
    return tamano2. Documentar Claramente los Casos
def quicksort(lista):
    """
    Ordena una lista usando quicksort.
    Casos:
    - Base: Lista vacía o de un elemento → ya ordenada
    - Recursivo: Particionar alrededor de un pivote
    """
    # Caso base
    if len(lista) <= 1:
        return lista
    # Caso recursivo
    pivote = lista[len(lista) // 2]
    menores = [x for x in lista if x < pivote]
    iguales = [x for x in lista if x == pivote]
    mayores = [x for x in lista if x > pivote]
    return quicksort(menores) + iguales + quicksort(mayores)
print(quicksort([3, 6, 8, 10, 1, 2, 1]))
# Output: [1, 1, 2, 3, 6, 8, 10]3. Considerar Alternativas Iterativas para Rendimiento
# Recursivo (elegante pero lento)
def suma_rec(n):
    if n == 0:
        return 0
    return n + suma_rec(n - 1)
# Iterativo (más eficiente)
def suma_iter(n):
    total = 0
    for i in range(n + 1):
        total += i
    return total
# O mejor aún, usar fórmula matemática
def suma_formula(n):
    return n * (n + 1) // 2
# Los tres dan el mismo resultado
print(suma_rec(100))      # 5050
print(suma_iter(100))     # 5050
print(suma_formula(100))  # 5050Recursión de Cola (Tail Recursion)
La recursión de cola ocurre cuando la llamada recursiva es la última operación:
# No es recursión de cola (operación después de la recursión)
def factorial_normal(n):
    if n <= 1:
        return 1
    return n * factorial_normal(n - 1)  # Multiplicación después
# Recursión de cola (con acumulador)
def factorial_cola(n, acumulador=1):
    if n <= 1:
        return acumulador
    return factorial_cola(n - 1, n * acumulador)  # Última operación
print(factorial_normal(5))    # 120
print(factorial_cola(5))      # 120Nota: Python no optimiza automáticamente la recursión de cola, pero el patrón puede ser más claro.
Pepitas de conocimiento:
La recursión es una herramienta poderosa en Python que permite:
✅ Resolver problemas complejos dividiéndolos en subproblemas
✅ Trabajar con estructuras jerárquicas (árboles, grafos)
✅ Implementar algoritmos elegantes (quicksort, búsqueda binaria)
✅ Simplificar código para problemas naturalmente recursivos
Recuerda:
- Siempre define un caso base claro
- Asegúrate de avanzar hacia el caso base
- Considera el límite de recursión de Python
- Evalúa si una solución iterativa es más apropiada
- Usa memoización para optimizar recursiones costosas
Profundiza en Python
Si quieres dominar la recursión y otros conceptos avanzados de Python, te recomiendo mi libro Python a fondo, donde encontrarás:
- Capítulos dedicados a algoritmos recursivos
- Técnicas de optimización avanzadas
- Casos de uso en el mundo real
- Ejercicios prácticos con soluciones
Artículos relacionados:

 
		 
		 
		