A recursão é uma técnica de programação onde uma função chama a si mesma para resolver um problema dividindo-o em subproblemas menores e semelhantes. Em Python, a recursão é uma ferramenta elegante e poderosa, especialmente útil para problemas que podem ser decompostos naturalmente em versões menores de si mesmos.
Se você está começando agora, é importante primeiro dominar os fundamentos das funções em Python, pois a recursão depende diretamente do conceito de funções e seus parâmetros. Neste guia completo, você vai entender como a recursão funciona, quando usá-la e como evitar armadilhas comuns como estouro de pilha.
O Que é Recursão?
Recursão ocorre quando uma função define seu comportamento em termos de si mesma. Imagine um espelho refletindo outro espelho — a imagem se repete infinitamente, a menos que haja uma condição de parada. Em computação, toda função recursiva precisa de dois elementos fundamentais:
- Caso base: a condição que interrompe a recursão
- Caso recursivo: a chamada da função para uma versão menor do problema
Um exemplo clássico e simples é o cálculo do fatorial de um número. O fatorial de n (representado como n!) é o produto de todos os inteiros positivos menores ou iguais a n. Matematicamente, n! = n × (n-1)!, com 0! = 1. Essa definição já é naturalmente recursiva.
def fatorial(n):
if n <= 1:
return 1
return n * fatorial(n - 1)
print(fatorial(5)) # 120
Quando chamamos fatorial(5), a função se chama sucessivamente com fatorial(4), fatorial(3), fatorial(2), fatorial(1) — que retorna 1 (caso base). Então cada chamada retorna multiplicando o resultado, formando: 1 × 2 × 3 × 4 × 5 = 120.
Como a Pilha de Chamadas Funciona
Cada chamada recursiva é empilhada na call stack (pilha de chamadas) do Python. A cada nova chamada, um novo quadro (frame) é adicionado ao topo da pilha. Quando o caso base é atingido, os quadros começam a ser desempilhados do topo para baixo, retornando os resultados.
Visualizando a execução de fatorial(5):
fatorial(1) → retorna 1
fatorial(2) → 2 * 1 = 2
fatorial(3) → 3 * 2 = 6
fatorial(4) → 4 * 6 = 24
fatorial(5) → 5 * 24 = 120
Cada nível da pilha consome memória. Para recursões muito profundas, o Python lança um RecursionError quando o limite de recursão é excedido. Por padrão, esse limite é 1000 chamadas, mas pode ser ajustado com sys.setrecursionlimit().
import sys
print(sys.getrecursionlimit()) # 1000
sys.setrecursionlimit(3000) # aumenta para 3000
Segundo a documentação oficial do Python, modificar o limite de recursão deve ser feito com cautela, pois valores muito altos podem causar crash no interpretador devido ao estouro da pilha C.
Caso Base e Caso Recursivo
Todo problema resolvido com recursão deve ter ao menos um caso base bem definido. Sem ele, a função se chamaria infinitamente até causar um estouro de pilha. O caso base é a menor instância do problema, que pode ser resolvida diretamente sem recorrer à recursão.
def contagem_regressiva(n):
if n == 0: # caso base
print("Fogo!")
return
print(n) # ação
contagem_regressiva(n - 1) # caso recursivo
contagem_regressiva(5)
No exemplo acima, quando n chega a 0, a recursão para. Cada chamada reduz n em 1, garantindo que o caso base será eventualmente alcançado, desde que n seja um número não negativo.
Fibonacci com Recursão
A sequência de Fibonacci é um dos exemplos mais usados para ensinar recursão. Nela, cada termo é a soma dos dois anteriores, com F(0) = 0 e F(1) = 1.
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 55
Embora elegante, essa implementação tem complexidade exponencial O(2ⁿ). Para n = 40, ela faz milhões de chamadas recursivas. É um exemplo clássico de onde a recursão ingênua é ineficiente. Para melhorar, podemos usar memoização:
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_otimizado(n):
if n <= 1:
return n
return fibonacci_otimizado(n - 1) + fibonacci_otimizado(n - 2)
print(fibonacci_otimizado(100)) # 354224848179261915075
O decorador @lru_cache armazena resultados já calculados, reduzindo a complexidade para O(n). A Real Python tem um excelente tutorial aprofundado sobre recursão e memoização em Python.
Recursão vs Iteração
Toda função recursiva pode ser reescrita como uma função iterativa (usando loops), e vice-versa. A escolha entre uma abordagem e outra depende de vários fatores:
| Característica | Recursão | Iteração |
|---|---|---|
| Legibilidade | Mais intuitiva para problemas recursivos por natureza | Pode ser mais difícil de entender em problemas complexos |
| Memória | Consome mais memória (pilha de chamadas) | Consome menos memória |
| Performance | Pode ser mais lenta (overhead de chamadas de função) | Geralmente mais rápida |
| Risco de erro | RecursionError se muito profunda | Loop infinito se mal escrita |
Para problemas como atravessar árvores, processar estruturas aninhadas ou implementar algoritmos de divisão e conquista, a recursão oferece uma solução mais natural e legível. Segundo a GeeksforGeeks, a recursão é particularmente útil para problemas que podem ser definidos em termos de si mesmos.
Recursão de Cauda (Tail Recursion)
Recursão de cauda é uma forma especial de recursão onde a chamada recursiva é a última operação executada pela função. Em linguagens funcionais, isso permite otimizações onde o compilador reusa o mesmo quadro de pilha, evitando o crescimento da call stack. Infelizmente, o Python não implementa otimização de recursão de cauda.
# Recursão de cauda (não otimizada em Python)
def fatorial_cauda(n, acumulador=1):
if n <= 1:
return acumulador
return fatorial_cauda(n - 1, n * acumulador)
O criador do Python, Guido van Rossum, explicou em um artigo que a otimização de recursão de cauda não foi implementada porque prejudicaria a pilha de depuração (stack traces menos informativos). Portanto, em Python, prefira iteração para problemas que exigiriam recursão muito profunda. O Programiz tem exemplos práticos que ilustram bem essa limitação.
Limites da Recursão em Python
O Python define um limite máximo de profundidade para recursão, evitando que um programa consuma toda a memória disponível. Esse limite é controlado pela função sys.getrecursionlimit().
import sys
Verificando o limite atual
print(f"Limite atual: {sys.getrecursionlimit()}")
Aumentando o limite (com cuidado!)
sys.setrecursionlimit(5000)
Verificando se um valor pode causar estouro
def verifica_profundidade(n):
try:
return verifica_profundidade(n + 1)
except RecursionError:
return n
print(f"Profundidade máxima alcançável: {verifica_profundidade(0)}")
O FAQ oficial do Python aborda questões relacionadas a parâmetros de funções e recursão, explicando as nuances do comportamento da linguagem.
Problemas Práticos com Recursão
Busca Binária Recursiva
A busca binária é um algoritmo de divisão e conquista que encontra um elemento em uma lista ordenada em tempo O(log n). Sua implementação recursiva é elegante e direta:
def busca_binaria(lista, alvo, esquerda, direita):
if esquerda > direita:
return -1 # caso base: elemento não encontrado
meio = (esquerda + direita) // 2
if lista[meio] == alvo:
return meio # caso base: elemento encontrado
elif lista[meio] > alvo:
return busca_binaria(lista, alvo, esquerda, meio - 1)
else:
return busca_binaria(lista, alvo, meio + 1, direita)
numeros = [1, 3, 5, 7, 9, 11, 13, 15]
print(busca_binaria(numeros, 7, 0, len(numeros) - 1)) # 3
Merge Sort Recursivo
O Merge Sort é outro algoritmo clássico de divisão e conquista que utiliza recursão para ordenar elementos com complexidade O(n log n):
def merge_sort(lista):
if len(lista) <= 1:
return lista # caso base
meio = len(lista) // 2
esquerda = merge_sort(lista[:meio])
direita = merge_sort(lista[meio:])
return merge(esquerda, direita)
def merge(esquerda, direita):
resultado = []
i = j = 0
while i < len(esquerda) and j < len(direita):
if esquerda[i] <= direita[j]:
resultado.append(esquerda[i])
i += 1
else:
resultado.append(direita[j])
j += 1
resultado.extend(esquerda[i:])
resultado.extend(direita[j:])
return resultado
print(merge_sort([38, 27, 43, 3, 9, 82, 10])) # [3, 9, 10, 27, 38, 43, 82]
A FreeCodeCamp possui um tutorial completo sobre recursão em Python com mais exemplos práticos como esses.
Torre de Hanoi
A Torre de Hanoi é um quebra-cabeça matemático que se resolve de forma quase trivial com recursão, mas é complexo iterativamente:
def torre_hanoi(n, origem, destino, auxiliar):
if n == 1:
print(f"Mover disco 1 de {origem} para {destino}")
return
torre_hanoi(n - 1, origem, auxiliar, destino)
print(f"Mover disco {n} de {origem} para {destino}")
torre_hanoi(n - 1, auxiliar, destino, origem)
torre_hanoi(3, "A", "C", "B")
Para 3 discos, o algoritmo executa 7 movimentos. Para n discos, são necessários 2ⁿ - 1 movimentos. A solução recursiva espelha diretamente a definição matemática do problema, mostrando o poder da recursão.
Quando Usar Recursão em Python
A recursão é mais adequada quando:
- O problema pode ser decomposto naturalmente em subproblemas menores e similares
- A profundidade da recursão é previsível e limitada (dezenas, não milhares de níveis)
- A versão iterativa seria significativamente mais complexa
- Você está processando estruturas de dados recursivas (árvores, grafos, JSON aninhado)
A Wikipedia oferece uma visão abrangente da recursão na ciência da computação, incluindo suas aplicações em algoritmos e estruturas de dados.
Evite recursão quando:
- A profundidade pode ser grande e imprevisível
- A performance é crítica e há overhead de chamadas de função
- Uma solução iterativa simples existe
Boas Práticas com Recursão
- Sempre defina um caso base claro — sem ele, sua recursão nunca termina
- Garanta que o caso base será alcançado — cada chamada recursiva deve aproximar-se do caso base
- Use memoização —
@lru_cacheou@cache(Python 3.9+) para evitar recálculos - Prefira iteração para problemas profundos — mais de 500 níveis de recursão podem ser arriscados
- Documente a lógica recursiva — a recursão pode ser difícil de ler sem comentários adequados
- Teste com valores pequenos primeiro — verifique o caso base, depois aumente gradualmente
- Considere alternativas — para problemas como travessia de árvores, iteradores podem ser mais eficientes
Ferramentas para Depurar Recursão
Depurar funções recursivas pode ser desafiador. Algumas técnicas úteis incluem:
import sys
from functools import wraps
def debug_recursao(func):
"""Decorator que mostra a profundidade da recursão"""
nivel = [0]
@wraps(func)
def wrapper(*args, **kwargs):
indent = " " * nivel[0]
print(f"{indent}→ {func.__name__}({args})")
nivel[0] += 1
try:
resultado = func(*args, **kwargs)
print(f"{indent}← {func.__name__}({args}) = {resultado}")
return resultado
finally:
nivel[0] -= 1
return wrapper
@debug_recursao
def fatorial(n):
if n <= 1:
return 1
return n * fatorial(n - 1)
fatorial(4)
O W3Schools oferece uma introdução simples e direta à recursão em Python, ideal para quem está começando.
Conclusão
A recursão é uma ferramenta indispensável no arsenal de qualquer programador Python. Embora não seja a solução ideal para todos os problemas, dominar a recursão permite escrever código mais elegante e expressivo para uma ampla gama de problemas, especialmente aqueles envolvendo estruturas de dados recursivas e algoritmos de divisão e conquista.
Lembre-se de que a prática leva à perfeição. Comece com problemas simples como fatorial e Fibonacci, depois avance para algoritmos mais complexos como busca binária, Merge Sort e Torre de Hanoi. Com o tempo, você desenvolverá a intuição necessária para identificar quando a recursão é a ferramenta certa para o trabalho.
Para continuar aprendendo, explore outros tópicos fundamentais como listas em Python, que são frequentemente usadas em conjunto com algoritmos recursivos, e aprofunde-se em estruturas de dados como árvores e grafos, onde a recursão brilha. A documentação oficial do Python é um recurso indispensável para qualquer desenvolvedor.