Recursão Não-Linear e Linearização
Outras classificações de recursão incluem recursão mútua (odd?
e even?
definidas anteriormente) e recursão múltipla - onde a função é invocada em seu próprio corpo mais de uma vez.
Um exemplo famoso de recursão múltipla binária é a função que calcula os números da sequência de Fibonacci:
(define (fib n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1))
(fib (- n 2))))))
A árvore de chamadas recursivas gerada é bem maior nesse caso:
(fib 4)
(+ (fib 3) (fib 2))
(+ (fib 2) (fib 1) ) (+ (fib 1) (fib 0))
(+ (fib 1) (fib 0)) 1 1 0
1 0
(+ 1 0) 1 (+ 1 0)
(+ 1 1) 1
(+ 2 1)
3
Para estimar a complexidade do processo, observe que o número de vezes em que o caso base da recursão é atingido na execução de (fib n)
é exatamente igual ao (n+1)ésimo número de Fibonacci.
Isso significa que esse procedimento é calculado em tempo exponencial proporcional à \( \phi ^ {n+1} \), onde \(\phi\) é o número de ouro.
Um primeiro passo para otimizar a computação é expressar o procedimento em uma iteração linear:
(define (fibo n)
(let iter ((n n) (prev 1) (curr 0))
(if (= n 0) curr
(iter (- n 1) curr (+ prev curr)))))
Compare o tempo de execução de
fib
efibo
para n = 45.
Aproveite para tentar transformar o procedimentofac
em uma iteração linear.
Alguns processos lineares podem ser otimizados ainda mais se explorarmos alguma propriedade que permita realizar, de uma vez, uma certa operação equivalente a múltiplas iterações.
;; recursao O(2^(n-1)) para base 2
;; 2^n = 2 * 2^(n-1) = 2^(n-1) + 2^(n-1)
(define (2^ n)
(cond ((= n 0) 1)
((= n 1) 2)
(else (+ (2^ (- n 1))
(2^ (- n 1))))))
;; recursao O(n)
;; b^n = b * b^(n-1)
(define (^ b n)
(if (= n 0) 1 (* b (^ b (- n 1)))))
;; iteracao O(n)
;; b^n = b * b^(n-1)
(define (^ b n)
(let iter ((n n) (prod 1))
(if (= n 0) prod
(iter (- n 1) (* b prod)))))
;; recursao O(log2(n))
;; b^n = (b^(n/2))^2
(define (^ b n)
(cond ((= n 0) 1)
((even? n) (square (^ b (halve n))))
(else (* b (^ b (- n 1))))))
;; iteracao O(log2(n))
;; b^n = (b^2)^(n/2)
(define (^ b n)
(define (iter b n prod)
(cond ((= n 0) prod)
((even? n) (iter (square b) (halve n) prod))
(else (iter b (- n 1) (* b prod)))))
(if (< n 0)
(iter (/ 1 b) (- n) 1)
(iter b n 1)))
;; PS:
(define (halve x) (ash x -1))
(define (square x) (* x x))
Tente aplicar a mesma técnica para computar a multiplicação de dois inteiros
b
en
em tempo logarítmico. O procedimento resultante deve se assemelhar ao algoritmo de multiplicação egípcia/russa.