C'è qualche differenza tra ricorsione strutturale e ricorsione in coda o sono entrambe uguali? Vedo che in entrambe queste ricorsioni, la funzione ricorsiva viene chiamata nel sottoinsieme degli elementi originali.
C'è qualche differenza tra ricorsione strutturale e ricorsione in coda o sono entrambe uguali? Vedo che in entrambe queste ricorsioni, la funzione ricorsiva viene chiamata nel sottoinsieme degli elementi originali.
Risposte:
Ricorsione strutturale: le chiamate ricorsive sono fatte su argomenti strutturalmente più piccoli .
Ricorsione della coda: la chiamata ricorsiva è l' ultima cosa che succede.
Non è necessario che la ricorsione della coda sia chiamata su un argomento più piccolo. In effetti, abbastanza spesso le funzioni ricorsive della coda sono progettate per essere ripetute per sempre. Ad esempio, ecco una banale ricorsione della coda (non molto utile, ma è la ricorsione della coda):
def f(x):
return f(x+1)
In realtà dobbiamo stare un po 'più attenti. In una funzione possono essere presenti diverse chiamate ricorsive e non tutte devono essere ricorsive di coda:
def g(x):
if x < 0:
return 42 # no recursive call
elif x < 20:
return 2 + g(x - 2) # not tail recursive (must add 2 after the call)
else:
return g(x - 3) # tail recursive
Si parla di chiamate ricorsive di coda . Una funzione le cui chiamate ricorsive sono tutte ricorsive della coda viene quindi chiamata funzione ricorsiva della coda.
La ricorsione della coda è un caso molto semplice di ricorsione strutturale, in cui la struttura in questione è un elenco collegato . Nella lingua che probabilmente stai usando principalmente, questa lista non è letteralmente nel codice; piuttosto, è un "elenco di chiamate alla funzione" concettuale, un concetto che potrebbe non essere possibile esprimere come scritto usando quella lingua. In Haskell (il mio linguaggio), qualsiasi chiamata di funzione ricorsiva alla coda può effettivamente essere sostituita da azioni di sequenziamento in un elenco letterale i cui elementi sono letteralmente "chiamate a una funzione", ma questa è probabilmente una cosa di linguaggio funzionale.
La ricorsione strutturale è un modo di operare su un oggetto definito come un composto di altri oggetti (possibilmente compositi). Ad esempio, un albero binario è un oggetto contenente riferimenti a due alberi binari o è vuoto (quindi è un oggetto definito in modo ricorsivo ). Meno autoreferenzialmente, una coppia (t1, t2) contenente due valori di alcuni tipi t1 e t2 ammette la ricorsione strutturale, sebbene t1 e t2 non debbano essere anche coppie. Questa ricorsione prende la forma
azione sulla coppia = combinazione dei risultati di altre azioni su ciascun elemento
che non sembra molto profondo.
Accade spesso che una ricorsione strutturale non possa essere ricorsiva di coda, sebbene qualsiasi tipo di ricorsione possa essere riscritta come ricorsione di coda (prova: se si esegue la ricorsione originale, le azioni vengono completate in un certo ordine; pertanto, la ricorsione equivale a eseguire quella particolare sequenza di azioni, che come ho discusso in precedenza, è la ricorsione della coda).
L'albero binario o l'esempio di coppia sopra lo dimostrano: comunque disponi le chiamate ricorsive sugli oggetti secondari, solo uno di essi può essere l'ultima azione; forse nessuno dei due lo è, se i loro risultati sono combinati in qualche modo (diciamo, aggiunta). Come dice Andrej Bauer nella sua risposta, ciò può accadere anche con una sola chiamata ricorsiva, purché il risultato venga modificato. In altre parole, per ogni tipo di oggetto diverso da quelli che sono elenchi effettivamente collegati (solo un oggetto secondario fino in fondo), la ricorsione strutturale non è ricorsione di coda.
f x = (stuff defining x'); f x'
sia la stessa del sequenziamento dei nodi in un elenco collegato definito come l = modify f : l
(nello stile di monade dello stato di Haskell). Non era solo la somiglianza terminologica per me. Per quanto riguarda la ricorsione della coda sugli alberi binari, potresti elaborare? Posso solo pensare al fatto di linearizzazione dal mio ultimo ultimo paragrafo.
f (f x)
dove la chiamata esterna di f
è ricorsiva di coda. Come si adatta alla vista che si tratta di elenchi? Ecco un altro esempio: f : (Int -> Int) -> (Int -> Int)
con f g 0 = g 42
e f g (n + 1) = f (f . g) n
. Le possibilità sono infinite e alcune sono utili.
f (f x)
applicabilità: nella valutazione della f esterna, quella interna non è una chiamata di coda (a meno che f non sia l'identità). Se-dichiarazioni possono essere banalmente riscritti non ramo nella chiamata coda: if (c) then f a else f b == let x = if (c) then a else b in f x
. L'ultimo esempio non è valido perché f . g
non verifica il tipo; anche così, non sarebbe ancora una ricorsione della coda: f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)
non è un richiamo a f
, ma un lambda veramente diverso. (successivo)
f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }
, ma se lo porti nella discussione, allora qualsiasi funzione ricorsiva, coda o no, è ammissibile e il termine diventa insignificante.