Caso base
Un’istanza risolvibile direttamente, senza altre chiamate.
Algoritmi · Tecniche di progettazione
La ricorsione trasforma un problema in copie più piccole dello stesso problema. Il codice può essere breve; l’esecuzione, però, costruisce una struttura concreta di chiamate, variabili locali e risultati sospesi.
Per risolvere un’istanza non serve immaginare tutte le chiamate insieme. Si assume che la funzione sappia già risolvere un’istanza più piccola, poi si usa quel risultato per costruire la soluzione corrente.
Un’istanza risolvibile direttamente, senza altre chiamate.
Riduce il problema e combina il risultato restituito.
Ogni chiamata deve avvicinarsi davvero al caso base.
Questo ragionamento è vicino all’induzione matematica: caso base, ipotesi su un problema più piccolo e passo induttivo.
Ogni chiamata sospesa occupa un frame. Il frame conserva parametri, variabili locali, punto in cui riprendere e spazio per il valore restituito. Lo stack segue la regola LIFO: l’ultima chiamata entrata è la prima a terminare.
La chiamata più recente è in alto ↑
Scegli un esempio e avanza un’operazione alla volta. L’albero mostra tutte le chiamate; lo stack mostra soltanto la catena attiva in quell’istante.
fact(n)if n ≤ 1 return 1return n · fact(n − 1)fib(n)if n ≤ 1 return nleft ← fib(n − 1)right ← fib(n − 2)return left + right0! = 1
n! = n · (n−1)! per n > 0
T(n) = T(n−1) + Θ(1) = Θ(n). Una chiamata per ogni valore da n a 1.
Θ(n) frame simultanei prima che inizi la risalita.
Per fact(5), la discesa costruisce 5 · fact(4), poi 4 · fact(3) e così via. Solo raggiunto il caso base iniziano le moltiplicazioni: 1, 2, 6, 24, 120.
F₀ = 0, F₁ = 1
Fₙ = Fₙ₋₁ + Fₙ₋₂
T(n) = T(n−1) + T(n−2) + Θ(1) = Θ(φⁿ), dove φ ≈ 1,618.
Θ(n): l’albero è grande, ma nello stack vive un solo cammino alla volta.
Fibonacci ricalcola molte volte fib(3), fib(2) e gli stessi casi base. Una cache indicizzata da n trasforma ogni sottoproblema dopo il primo in un accesso costante. Attiva “Usa memoization” nel laboratorio: i nodi viola terminano leggendo un risultato già noto.
tempo · Θ(n) stack
tempo · Θ(n) stack + cache
tempo · Θ(1) spazio
| Preferisci ricorsione | Preferisci iterazione |
|---|---|
| La struttura è naturalmente ricorsiva: alberi, directory, divide et impera, backtracking. | La profondità può essere molto grande o dipende da input non fidati. |
| Il codice rispecchia direttamente una definizione e resta più verificabile. | Esiste uno stato compatto e ripetitivo, come due soli valori consecutivi di Fibonacci. |
| La profondità è logaritmica o comunque controllata. | Overhead delle chiamate e memoria dello stack sono rilevanti. |
La tail recursion non risolve automaticamente il problema: JavaScript e PHP non garantiscono in generale l’eliminazione del frame ricorsivo. Se la profondità può essere elevata, usa un ciclo o uno stack esplicito.
f(n) richiama f(n) o allontana n dal caso base.