Pular para conteúdo

Recursão e Recorrências

A recursão resolve um problema usando soluções para instâncias menores do mesmo problema. Um design recursivo válido precisa de casos base e progresso em direção a eles.

A pilha de chamadas

static long factorial(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("n must be non-negative");
    }
    return n < 2 ? 1L : Math.multiplyExact(n, factorial(n - 1));
}

A definição matemática é clara, mas Java não garante eliminação de chamada em cauda. O método usa Θ(n) quadros de pilha e transborda long para n relativamente pequeno; correção matemática não remove limites de máquina.

Recorrências

Uma recorrência relaciona o custo para tamanho n a custos menores.

Recorrência Resultado típico Exemplo
T(n) = T(n - 1) + Θ(1) Θ(n) Recursão linear
T(n) = T(n/2) + Θ(1) Θ(log n) Busca binária
T(n) = 2T(n/2) + Θ(n) Θ(n log n) Merge sort
T(n) = T(n - 1) + Θ(n) Θ(n²) Particionamento mal balanceado

Intuição de árvore de recursão

Para merge sort, cada nível realiza Θ(n) total de trabalho de fusão. Existem Θ(log n) níveis, portanto o total é Θ(n log n).

O Teorema Mestre aplica-se a recorrências da forma T(n) = aT(n/b) + f(n) sob suas condições declaradas de regularidade. Não lida diretamente com T(n - 1) ou tamanhos de subproblemas desiguais arbitrários.

Memoização

Recursão fibonacciana ingênua repete subproblemas e leva tempo exponencial. Memoização armazena resultados, reduzindo o trabalho a Θ(n) tempo e Θ(n) espaço. A programação dinâmica bottom-up pode remover a recursão preservando a mesma estrutura de dependência. O guia dedicado de memoização explica a correção das chaves, o ciclo de vida, a concorrência e a distinção em relação a um cache de aplicação.

Exercícios

  1. Desenhar a árvore de recursão para T(n) = 3T(n/2) + Θ(n).
  2. Converter factorial recursivo a método iterativo.
  3. Identificar caso base e medida de progresso em busca binária recursiva.