Diferença entre recursão de cauda e recursão estrutural
8
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.
Que pesquisa você fez? Como você entende a recursão da cauda? Por que você acha que é semelhante à recursão estrutural?
Jonathan fundido
Alguma chance de você adicionar o que viu que o fez pensar que eles podem ser iguais ou muito semelhantes? Isso pode ajudar os instrutores a esclarecê-lo ao ensinar os conceitos às pessoas.
user541686
Respostas:
28
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.
É falso que a recursão da cauda seja apenas sobre listas, imaginadas ou reais. É perfeitamente possível ter recursão da cauda sobre árvores binárias, por exemplo. Eu posso ver por que alguém pensaria que é porque o resto da lista é sua "cauda".
Andrej Bauer
@AndrejBauer Vou me sentir adequadamente envergonhado com isso quando entender exatamente o que está errado. Parece tautológico que a recursão da cauda do formulário 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.
Ryan Reich
Nem todas as chamadas recursivas de cauda são dessa forma. Por exemplo, pode haver várias chamadas recursivas finais em ramificações diferentes (de instruções de caso ou algumas delas). É mais natural pensar naqueles como selecionar um caminho através de uma árvore . Observe também que as chamadas são recursivas de cauda (ou não); portanto, você pode ter 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 42e f g (n + 1) = f (f . g) n. As possibilidades são infinitas, e algumas são úteis.
Andrej Bauer
@AndrejBauer A pergunta era sobre recursão de cauda, e não apenas chamadas de cauda , então eu não consideraria o 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 . gnã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)
Ryan Reich
Na verdade, eu diria que o exemplo é de recursão recíproca mútua , ou seja 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.
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):
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:
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.
fonte
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
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.
fonte
f x = (stuff defining x'); f x'
seja igual à sequência de nós em uma lista vinculada definida comol = 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 def
é 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)
comf g 0 = g 42
ef 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 porquef . 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 chamadof
, 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.