Existe alguma diferença entre recursão estrutural e recursão de cauda ou ambos são iguais? Vejo que, nessas duas recursões, a função recursiva é chamada no subconjunto dos itens orignais.
Existe alguma diferença entre recursão estrutural e recursão de cauda ou ambos são iguais? Vejo que, nessas duas recursões, a função recursiva é chamada no subconjunto dos itens orignais.
Respostas:
Recursão estrutural: chamadas recursivas são feitas em argumentos estruturalmente menores .
Recursão de cauda: a chamada recursiva é a última coisa que acontece.
Não há exigência de que a recursão da cauda deva ser chamada em um argumento menor. De fato, muitas vezes as funções recursivas da cauda são projetadas para repetir para sempre. Por exemplo, aqui está uma recursão trivial da cauda (não muito útil, mas é uma recursão da cauda):
def f(x):
return f(x+1)
Na verdade, temos que ter um pouco mais de cuidado. Pode haver várias chamadas recursivas em uma função e nem todas elas precisam ser recursivas de cauda:
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
Fala-se de chamadas recursivas de cauda . Uma função cujas chamadas recursivas são todas recursivas na cauda é chamada de função recursiva na cauda.
A recursão da cauda é um caso muito simples de recursão estrutural, em que a estrutura em questão é uma lista vinculada . No idioma que você provavelmente está usando principalmente, esta lista provavelmente não está literalmente no código; pelo contrário, é uma "lista de chamadas para a função" conceitual, um conceito que pode não ser possível expressar como escrito usando essa linguagem. Em Haskell (minha linguagem), qualquer chamada de função recursiva de cauda pode realmente ser substituída por ações de seqüenciamento em uma lista literal cujos elementos são literalmente "chamadas para uma função", mas isso provavelmente é uma coisa da linguagem funcional.
A recursão estrutural é uma maneira de operar em um objeto definido como um composto de outros objetos (possivelmente compostos). Por exemplo, uma árvore binária é um objeto que contém referências a duas árvores binárias ou está vazio (portanto, é um objeto definido recursivamente ). Menos auto-referencialmente, um par (t1, t2) contendo dois valores de alguns tipos t1 e t2 admite recursão estrutural, embora t1 e t2 também não precisem ser pares. Essa recursão assume a forma
ação no par = combinação dos resultados de outras ações em cada elemento
o que não parece muito profundo.
Geralmente, uma recursão estrutural não pode ser recursiva de cauda, embora qualquer tipo de recursão possa ser reescrita como recursão de cauda (prova: se você executar a recursão original, as ações serão concluídas em uma determinada ordem; portanto, a recursão é equivalente a executar essa sequência específica de ações, que, como discuti anteriormente, é recursão de cauda).
A árvore binária ou o exemplo de par acima demonstram isso: no entanto, você organiza as chamadas recursivas nos subobjetos, apenas uma delas pode ser a última ação; possivelmente nenhum deles seja, se seus resultados forem combinados de alguma maneira (digamos, adição). Como Andrej Bauer diz em sua resposta, isso pode acontecer mesmo com apenas uma chamada recursiva, desde que o resultado seja modificado. Em outras palavras, para todos os tipos de objetos que não sejam efetivamente listas vinculadas (apenas um subobjeto até o fim), a recursão estrutural não é recursiva final.
f x = (stuff defining x'); f x'
seja igual à sequência de nós em uma lista vinculada definida como l = modify f : l
(no estilo estado-mônada de Haskell). Não era apenas a semelhança terminológica para mim. Quanto à recursão da cauda sobre árvores binárias, você poderia elaborar? Só consigo pensar no fato da linearização do meu segundo último parágrafo.
f (f x)
onde a chamada externa de f
é recursiva de cauda. Como isso se encaixa na visão de que se trata de listas? Aqui está outro exemplo: f : (Int -> Int) -> (Int -> Int)
com f g 0 = g 42
e f g (n + 1) = f (f . g) n
. As possibilidades são infinitas, e algumas são úteis.
f (f x)
aplicável: na avaliação do f externo, o interno não é um chamado de cauda (a menos que f seja a identidade). Se-declarações podem ser trivialmente reescrito não em galho na chamada cauda: if (c) then f a else f b == let x = if (c) then a else b in f x
. O último exemplo é inválido porque f . g
não verifica; mesmo assim, ainda não seria uma recursão de cauda: f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)
não é um chamado f
, mas um lambda genuinamente diferente. (próximo)
f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }
, mas se você incluir isso na discussão, qualquer função recursiva, final ou não, é admissível e o termo se torna sem sentido.