Differenza tra ricorsione della coda e ricorsione strutturale


8

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.


7
Che ricerca hai fatto? Qual è la tua comprensione della ricorsione della coda? Perché pensi che sia simile alla ricorsione strutturale?
Jonathan Cast il

Qualche possibilità che potresti aggiungere ciò che hai visto che ti ha fatto pensare che potrebbero essere uguali o molto simili? Potrebbe aiutare gli istruttori a chiarirlo quando insegnano i concetti alle persone.
user541686,

Risposte:


28
  1. Ricorsione strutturale: le chiamate ricorsive sono fatte su argomenti strutturalmente più piccoli .

  2. 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.



I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
DW

-1

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.


1
È falso che la ricorsione della coda riguardi solo elenchi, immaginati o reali. Ad esempio, è perfettamente possibile ricorrere alla coda sugli alberi binari. Vedo perché qualcuno potrebbe pensare che sia perché il resto della lista è la sua "coda".
Andrej Bauer,

@AndrejBauer Mi sentirò adeguatamente imbarazzato per questo quando capirò esattamente cosa c'è che non va. Sembra tautologico che la ricorsione della coda della forma 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.
Ryan Reich,

Non tutte le chiamate ricorsive della coda sono di quel tipo. Ad esempio, possono esserci più chiamate ricorsive di coda in diversi rami (di dichiarazioni di casi, o alcune di queste). È più naturale pensare a quelli come selezionare un percorso attraverso un albero . Si noti inoltre che le chiamate sono ricorsive di coda (o meno), quindi si potrebbe avere 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 42e f g (n + 1) = f (f . g) n. Le possibilità sono infinite e alcune sono utili.
Andrej Bauer,

@AndrejBauer La domanda riguardava la ricorsione della coda piuttosto che solo le chiamate di coda , quindi non prenderei in considerazione l' 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 . gnon 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)
Ryan Reich il

In realtà direi che l'esempio è la ricorsione della coda reciproca , vale a dire 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.
Ryan Reich,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.