edubun
Português

Recursão

Mover uma Torre de Hanói de 64 discos leva 18 446 744 073 709 551 615 movimentos; uma função recursiva descreve a solução em quatro linhas.

Uma função que chama a si mesma

Alguns problemas contêm uma cópia menor de si mesmos. O fatorial de 5, escrito 5!, é 5 × 4 × 3 × 2 × 1 = 120. Olhe de novo: é 5 × (4 × 3 × 2 × 1), ou seja, 5 × 4!. Em geral n! = n × (n − 1)!, então para calcular um fatorial você pode calcular um fatorial menor e multiplicar. Uma função que resolve um problema chamando a si mesma numa versão menor é recursiva.

function factorial(n) {
  if (n <= 1) return 1;          // caso base: para aqui
  return n * factorial(n - 1);   // caso recursivo: um problema menor
}
console.log(factorial(5)); // mostra 120

Toda função recursiva precisa de duas partes. O caso base é uma entrada pequena o bastante para ser respondida direto, sem outra chamada: aqui, o fatorial de 1 é 1. O caso recursivo diminui o problema e chama a função de novo. Cada chamada precisa chegar mais perto do caso base. Se não chegar, por exemplo se você escrever factorial(n + 1) por engano, as chamadas nunca terminam.

A pilha de chamadas

Como o computador acompanha todas essas chamadas inacabadas? Quando factorial(5) roda, ele chega a 5 * factorial(4) e precisa esperar a resposta. O computador põe a chamada que espera numa pilha, um monte em que o último item colocado é o primeiro a sair. factorial(4) espera factorial(3), e assim por diante até factorial(1), que devolve 1 na hora. Aí a pilha se desfaz: factorial(2) devolve 2, factorial(3) devolve 6, factorial(4) devolve 24 e factorial(5) devolve 120.

A pilha tem tamanho limitado. Uma recursão que nunca chega ao caso base continua acrescentando chamadas até o espaço acabar, e o JavaScript para com um erro; no Chrome e no Node.js a mensagem é "RangeError: Maximum call stack size exceeded". O mesmo limite faz com que uma recursão muito profunda, de centenas de milhares de níveis, fique melhor escrita como laço.

A Torre de Hanói

A Torre de Hanói é um quebra-cabeça publicado pelo matemático francês Édouard Lucas em 1883. Há três pinos e uma pilha de discos de tamanhos diferentes no primeiro pino, o maior embaixo. O objetivo é levar a pilha inteira para o terceiro pino. Você só pode mover um disco por vez, só o disco de cima de um pino, e nunca pôr um disco maior sobre um menor.

Pensar passo a passo para onde vai cada disco fica confuso rápido. Pensar de forma recursiva deixa tudo curto. Para mover n discos de A para C: primeiro tire do caminho os n − 1 discos de cima, de A para B. Depois mova o maior disco de A para C. Depois mova os n − 1 discos de B para cima dele, em C. Mover n − 1 discos é o mesmo quebra-cabeça, um tamanho menor, e mover um disco é o caso base. Para 3 discos isso dá 7 movimentos; o laboratório mostra a pilha de chamadas esperando a cada movimento.

function hanoi(n, from, to, via) {
  if (n === 0) return;
  hanoi(n - 1, from, via, to);
  console.log("disk " + n + ": " + from + " -> " + to);
  hanoi(n - 1, via, to, from);
}
hanoi(3, "A", "C", "B");  // 7 movimentos
Carregando

Arraste para girar. Role ou faça pinça para dar zoom.

Três discos, sete movimentos. A pilha de chamadas à direita lista as chamadas de hanoi que esperam este movimento terminar. Experimente 4 e 5 discos.

Quantos movimentos levam n discos? Mover n discos custa dois movimentos de n − 1 discos mais um: M(n) = 2M(n − 1) + 1, com M(1) = 1. Isso dá 1, 3, 7, 15, 31, então M(n) = 2ⁿ − 1, e nenhuma solução consegue usar menos. Dez discos levam 1023 movimentos. Sessenta e quatro discos levam 2⁶⁴ − 1 = 18 446 744 073 709 551 615 movimentos; a um movimento por segundo, isso dá cerca de 585 bilhões de anos. É o crescimento O(2ⁿ) da unidade anterior.

Recursão, laços e trabalho repetido

Tudo o que se escreve com recursão pode ser escrito com um laço e uma pilha própria, e casos simples como o fatorial muitas vezes ficam mais claros como laços. A recursão brilha quando o próprio problema tem forma de árvore: dividir uma lista no merge sort, explorar pastas dentro de pastas ou buscar num labirinto, que é o assunto da última unidade. Uma armadilha é o trabalho repetido. Os números de Fibonacci 0, 1, 1, 2, 3, 5, 8, … seguem fib(n) = fib(n − 1) + fib(n − 2). Escrito direto como recursão, fib(10) = 55 faz 177 chamadas, fib(20) faz 21 891 e fib(30) faz 2 692 537, porque os mesmos valores menores são calculados de novo e de novo. Lembrar as respostas já encontradas, ou construir de baixo para cima com um laço, acaba com esse desperdício.

recursãoUma função que resolve um problema chamando a si mesma numa versão menor
caso baseUma entrada respondida direto, que faz as chamadas pararem
caso recursivoA parte que diminui o problema e chama de novo
pilha de chamadasO monte de chamadas esperando uma resposta, último a entrar, primeiro a sair
estouro de pilha (stack overflow)Chamadas esperando demais: RangeError em JavaScript
fatorial n!n × (n − 1) × … × 1; 5! = 120
Torre de HanóiLucas, 1883: 2ⁿ − 1 movimentos para n discos
  1. Quanto é 6! (6 fatorial)?
  2. Quantos movimentos a Torre de Hanói leva com 4 discos?
  3. Quantos movimentos com 10 discos?
  4. O que o caso base faz?
    • Responde direto a uma entrada pequena e interrompe a cadeia de chamadas
    • Faz a função rodar mais rápido
    • Chama a função com uma entrada maior
    • Mostra o resultado
  5. O que acontece se uma função recursiva nunca chega ao caso base?
    • Devolve 0
    • A pilha de chamadas enche e o programa para com um erro
    • Fica num laço silencioso para sempre, sem erro
    • O JavaScript escolhe um caso base para você
  6. Em que ordem uma pilha tira os itens?
    • Último a entrar, primeiro a sair
    • Primeiro a entrar, primeiro a sair
    • O menor primeiro
    • Ao acaso
  7. Enquanto factorial(5) está calculando factorial(1), quantas chamadas de factorial estão na pilha, contando essa?
  8. Para mover n discos de A para C, qual é o primeiro passo do plano recursivo?
    • Mover os n − 1 discos de cima de A para B
    • Mover o maior disco para C
    • Mover o menor disco para C
    • Mover todos os discos para B
  9. Quem publicou o quebra-cabeça da Torre de Hanói em 1883?
    • Leonhard Euler
    • Édouard Lucas
    • Tony Hoare
    • Euclides
  10. O fib recursivo simples faz 177 chamadas para fib(10). Quanto é fib(10)?
  11. Por que o fib recursivo simples é lento?
    • Calcula os mesmos valores menores muitas vezes
    • A recursão é sempre lenta
    • As somas custam caro
    • Não tem caso base
Pontos-chave
  • Uma função recursiva chama a si mesma numa versão menor do problema.
  • Ela precisa de um caso base que interrompa as chamadas.
  • As chamadas que esperam ficam na pilha de chamadas, último a entrar, primeiro a sair.
  • Torre de Hanói: 2ⁿ − 1 movimentos; 64 discos levariam cerca de 585 bilhões de anos a um movimento por segundo.
  • Cuidado com o trabalho repetido, como no Fibonacci recursivo simples.
Fontes deste curso (11)
  • MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
  • lecture notes 1 Introduction, 3 Sorting, 9 Breadth-First Search, 10 Depth-First Search (ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes) and Fall 2011 lecture 13 BFS notes (ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011)
  • Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (MIT Press)
  • with the CLRS-based Khan Academy algorithms unit by Thomas Cormen and Devin Balkcom of Dartmouth (khanacademy.org/computing/computer-science/algorithms: binary search, selection sort analysis, merge sort, quicksort overview and analysis)
  • MacTutor History of Mathematics, Al-Khwarizmi (mathshistory.st-andrews.ac.uk/Biographies/Al-Khwarizmi: word algorithm from the Latin title Algoritmi de numero Indorum)
  • D. E. Joyce, Euclid's Elements Book VII Propositions 1 and 2 (mathcs.clarku.edu/~djoyce/elements/bookVII)
  • Wolfram MathWorld, Merge Sort (mathworld.wolfram.com/MergeSort.html: proposed by John von Neumann in 1945, after Knuth 1998 p. 159)
  • Stanford CS "Tony Hoare: Quicksort" (cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/tony-hoare/quicksort.html: devised while a student at Moscow State University for Russian-English machine translation with a tape dictionary, programmed in Algol 60 and published 1961; the year 1959 from the Wikipedia Quicksort article and its cited sources)
  • Bodleian Libraries, Ada Lovelace (blogs.bodleian.ox.ac.uk/adalovelace: 1843 paper, Note G Bernoulli numbers)
  • Fourmilab text of Menabrea's Sketch with Lovelace's notes (fourmilab.ch/babbage/sketch.html)
  • Seven Bridges of Konigsberg (Euler 1736) and Tower of Hanoi (Edouard Lucas, 1883) checked against the Wikipedia articles and their cited sources.

Data de verificação: 2026-10-17