Teoria

Fala ae! Aqui é o Samuel do Responde Aí, e hoje a gente vai aprender sobre funções recursivas, uma ferramenta poderosa que vai nos ajudar bastante em diversos problemas!

Pra entender o que são essas funções, nada melhor do que começarmos com um exemplo, então bora lá!

Função recursiva

Vamos analisar a função o l a _ m u n d o ( ) e ver o que acontece quando a chamamos no nosso código:

Começamos na linha 5 indicando pro C++ que teremos uma função chamada o l a _ m u n d o ( ) no código. Depois, nas linhas 7 a 11 temos o nosso código principal, a única coisa que ele faz é chamar a função, que ainda precisa ser criada.

Então, a função o l a _ m u n d o ( ) é definida nas linhas 14 a 18, primeiro ela imprime uma mensagem, mas repara como na última linha essa função é chamada novamente dentro da sua própria rotina. Dessa forma, quando a chamamos dentro da m a i n ( ) , ela inicia a sua rotina printando “Ola mundo!” e depois se chama novamente, então printa “Ola mundo!” e se chama novamente... esse processo continua e seria infinito, mas o próprio computador faz com que o programa seja encerrado com o retorno de - 1 .

Uma função como essa, que chama ela mesma dentro de sua rotina, é chamada de função recursiva.

Uso de uma função recursiva

Vamos analisar a seguinte sequência:

1 ,   1 ,   2 ,   3 ,   5 ,   8 ,   13 ,   21 ,   34 ,   55 , …

Eai, pegou a referência?

Essa é a sequência de Fibonacci, muito famosa por aparecer na natureza em diversas formas, desde o formato da concha de caramujo até as presas de um elefante. Nela, cada novo termo da sequência é a soma dos dois termos anteriores, então sua fórmula de recursão é dada por F n = F n - 1 + F n - 2 , onde F n é o n-ésimo termo da função e n = 2 ,   3 ,   4 ,   …

Imagine que você precisa do valor correspondente ao F 40 . Tentar calcular ele na mão seria algo muito trabalhoso! Logo, que tal montarmos um programa que nos ajude nisso?

Vamos tentar montar um algoritmo para calcular um termo genérico dessa sequência. Para isso, vamos declarar a função f i b o n a c c i , que receberá um inteiro não negativo n , e retornará o n-ésimo termo, que será o F n .

Quando vamos trabalhar com recursividade, o primeiro passo seria definir os valores iniciais. Nesse caso, vamos definir que F 0 = F 1 = 1 . Isso já será o suficiente para começarmos.

Perceba que, dessa forma, quando for enviado 0 ou 1 para a função, ele retornará o valor 1, que seria equivalente aos valores iniciais da sequência.

Agora vamos implementar a recursividade. O termo F n será a soma da própria função f i b o n a c c i aplicada aos dois termos anteriores, n - 1 e n - 2 :

Agora podemos verificar como o nosso código está funcionando e calcular o F 40 :

Aqui, quando definimos f 40 = f i b o n a c c i ( 40 ) na linha 09, o programa faz o cálculo usando a função que definimos anteriormente, pra isso, ele precisa dos valores de f i b o n a c c i ( ) para n - 1 e n - 2 , que nesse ponto valem 39 e 38 , então a função f i b o n a c c i ( ) é chamada novamente, o que faz gerar novos valores de n - 1 e n - 2 . Esse processo vai se repetindo até que a função chegue nos valores de n = 0 e n = 1 que definimos no início.

Todo esse processo é feito nos “bastidores” do C++ e como saída, temos apenas o valor que pedimos: F 40 !

Loop infinito

No primeiro caso mostrado da função o l a _ m u n d o , temos um exemplo de uma péssima forma de se utilizar o método recursivo, pois acabamos caindo num loop infinito, no qual o programa vai ficar rodando aquela função indefinidamente.

Para a recursão, esse tipo de caso não é favorável, portanto, devemos evitá-lo. Mas como fazer isso?

Bom, a solução varia de caso pra caso. Possíveis soluções seriam reestruturar o algoritmo de recursão para que não caia num loop infinito ou impor uma quantidade limite de vezes que a função pode ser chamada.

Vamos pegar a seguinte sequência como exemplo:

2 ,   5 ,   8 ,   11 ,   14 ,   17 , …

Essa sequência é uma Progressão Aritmética (P.A ), cujo termo inicial é 2 e a razão é 3 .

Uma possível relação recursiva para uma PA é 2 a n = a n - 1 + a n + 1 ou ainda a n   =   ( a n - 1   +   a n + 1 ) / 2 . Vamos montar a função recursiva para essa PA. Para isso, vamos definir os dois primeiros termos como a 0 = 2 e a 1 = 5 . De forma semelhante a função de f i b o n a c c i , teremos:

No entanto, caso colocássemos essa função para rodar do jeito que está, teríamos um problema! Vamos colocar n = 5 , por exemplo. A função P A ( 5 ) irá chamar as funções P A ( 4 ) e P A ( 6 ) . Ao ser chamada, a função P A ( 4 ) irá chamar a função P A ( 5 ) , que chamará a função P A ( 4 ) novamente, que chamará a função P A ( 5 ) ...

Isso irá continuar indefinidamente, ou seja, caímos num loop infinito.

Uma maneira de contornarmos isso, seria reescrever a relação de recursividade de tal forma que ela dependa apenas de valores anteriores:

2 a n = a n - 1 + a n + 1

a n + 1 = 2 a n - a n - 1

Poderíamos ainda reescrever dessa forma:

a n = 2 a n - 1 - a n - 2

Com isso, temos o seguinte algoritmo:

E agora sim!

Dessa forma, não temos mais aquele problema do loop infinito!

Agora que já entendemos como funcionam as funções recursivas, como usá-las e, principalmente, como não usá-las, vamos partir para alguns exercícios de fixação.

Uso de uma função recursiva

Loop infinito

Exercícios Resolvidos

Exercício Resolvido #1

Crie uma função f a c t o r i a l em C++ que recebe um número inteiro n e retorna, de forma recursiva, o valor de n ! (fatorial de n ).

Passo 1

Eai, tudo beleza?

Vamos começar lembrando o que é recursão! A recursividade é um mecanismo que permite uma função chamar a si mesma. E isso vai ser bem conveniente no caso do fatorial, você já já vai ver porquê.

Nessa questão, vamos iniciar declarando uma função chamada f a c t o r i a l . E os parâmetros dessa função vão ser as variáveis   n e n !

Como ambos n e n ! são valores inteiros, podemos declarar tanto os parâmetros, quanto o retorno da função como i n t .

Passo 2

Agora, vamos definir os casos iniciais da nossa recursão, que são os casos em que a função não chama a si mesma. No caso da função fatorial, sabemos que:

  • 0! = 1
  • 1! = 1

Isso será o suficiente para estabelecermos o caso inicial para a nossa função.

Passo 3

Vamos estabelecer a relação de recursão. Vamos analisar o 7 ! para isso.

7 ! = 7 * 6 * 5 * 4 * 3 * 2 * 1

Perceba que 6 * 5 * 4 * 3 * 2 * 1 = 6 ! , então podemos escrever 7 ! como 7 * 6 !

O 6 ! , por sua vez, pode ser escrito como 6 * 5 !

O 5 ! pode ser escrito como 5 * 4 ! , e assim segue.

Dessa forma, temos

n ! = n * n - 1 !

Percebeu como a gente tem um fatorial dentro de outro fatorial dentro de outro fatorial...?

É por isso que a recursão é interessante aqui, pois vamos chamar a função dentro dela mesma!

Implementando isso no nosso código, temos:

A função vai chamar a si mesma, até que N seja igual a 0 ou 1.

Resposta

Exercício Resolvido #2

Desenvolva, de forma recursiva, uma função que recebe um inteiro n e retorna F ( n ) que é dado por:

  • F 1 = 1
  • F 2 = 2
  • F n = 2 * F n - 1 + 3 * F ( n - 2 )

Passo 1

Eae, tranquilo?

Vamos analisar a função proposta chamando-a de F . Essa função recebe um argumento inteiro n e retorna um valor também inteiro, logo, podemos declarar F e o seu argumento como i n t .

Passo 2

Agora, vamos colocar as condições iniciais.

Pelo enunciado nós temos os valores de F para n = 1 e n = 2 , então vamos colocar um condicional usando um i f - e l s e pra quando n assumir esses valores, a função F retornar os valores que queremos:

Passo 3

Por fim, temos que adicionar a recorrência para os casos diferentes de n = 1 e n = 2 .

Nesse caso, vamos usar a equação dada: F n = 2 * F n - 1 + 3 * F ( n - 2 )

Resposta

Exercício Resolvido #3

A função de Ackermann é definida para valores inteiros e não negativos m e n da seguinte forma:

  • A m , n = n + 1 , se m = 0
  • A m , n = A m - 1 ,   1 , se m > 0 e n = 0
  • A m ,   n = A ( m - 1 ,   A m , n - 1 ) , se m > 0 e n > 0

Faça uma função recursiva para implementá-la. Qual o valor de A(3,2)?

Passo 1

Eae, tudo tranquilo?

Para a função proposta nesse enunciado, teremos como parâmetros dois valores, m e n , ambos inteiros não negativos, assim como o valor a ser retornado pela função, então, podemos declarar esses valores como u n s i g n e d   i n t .

Passo 2

Vamos colocar os casos iniciais para a função de Ackermann. Temos o caso em que m = 0 , no qual será retornado n + 1 , e em que n = 0 e m > 0 , no qual será retornado A ( m - 1 ,   1 )

Repare que não é necessário especificar que m > 0 no segundo caso, pois m foi definido como u n s i g n e d   i n t , logo, temos m ≥ 0 e o caso de m = 0 já foi considerado no primeiro condicional, ou seja, se a função não terminou no primeiro condicional, significa que m é necessariamente maior que 0.

Passo 3

Agora, vamos acrescentar a recursividade dada por:

A m ,   n = A ( m - 1 ,   A m , n - 1 ) , se m > 0 e n > 0

Similar ao caso anterior, não há necessidade de especificar as condições de m > 0 e n > 0 , pois são as únicas condições que sobraram para serem analisadas.

Perceba que é possível passar a própria função A ( m ,   n - 1 ) como um parâmetro, pois o retorno de A foi declarado como do mesmo tipo dos parâmetros de A .

Passo 4

Por fim, basta calcularmos o valor de A ( 3,2 ) . Para isso, vamos incluir a biblioteca i o s t r e a m e imprimir na tela tal valor.

Resposta

Para o valor de A(3,2), temos:

Exercício Resolvido #4

Escreva uma função recursiva que calcule o número de grupos distintos com k pessoas que podem ser formados a partir de um conjunto de   n pessoas. A definição abaixo da função C o m b ( n , k ) define as regras:

  • C o m b n ,   k = n , se k = 1
  • C o m b n ,   k = 1 , se k = n
  • C o m b n ,   k =   C o m b n - 1 ,   k - 1 +   C o m b n - 1 ,   k , se 1 < k < n

Caso sejam passados valores de k e n que não estejam especificados nas condições acima, faça a função retornar 0.

Passo 1

Eae, tudo tranquilo?

Nessa questão, estamos trabalhando com conceitos de combinatória e, por isso, devemos trabalhar com valores inteiros, pois a combinação de termos é melhor definida dessa forma. Nesse contexto, vamos declarar a função c o m b com os dois argumentos, n e k , e o retorno do tipo i n t .

Passo 2

Agora, vamos colocar as condições iniciais que foram dadas. Caso k = 1 , a função retorna n e caso k = n , ela retorna 1 .

Passo 3

A seguir, vamos adicionar a relação de recorrência dessa função, que é dada por:

C o m b n ,   k =   C o m b n - 1 ,   k - 1 +   C o m b n - 1 ,   k , se 1 < k < n

Por fim, vamos acrescentar o caso de nenhuma das condições anteriores serem atendidas. Como foi definido no enunciado, caso isso aconteça, a função deve retornar 0 .

Resposta

Exercício Resolvido #5

Escreva uma função recursiva que calcule a soma dos primeiros n cubos:

S ( n )   =   1 3   +   2 3   +   . . .   +   n 3

Chame essa função de S _ r e c u r s i v o . Crie também uma função que calcule essa soma de forma iterativa (usando laços de repetição, como f o r e w h i l e ) e chame-a de S _ i t e r a t i v o .

Passo 1

Eae, tudo tranquilo?

Vamos iniciar essa questão focando na função S _ r e c u r s i v o . Pra isso vamos relembrar que a recursividade é um mecanismo que permite uma função chamar a si mesma.

Para começarmos a trabalhar com essa função, precisamos determinar a recursão que será utilizada e os casos iniciais.

Para encontrarmos a nossa relação de recursividade, vamos escrever a função S ( n ) em função de S ( n - 1 ) :

S n = n 3 + S ( n - 1 )

Essa é a nossa relação de recursividade. Ela vai funcionar tipo um loop. Como, ao chamar um S _ r e c u r s i v o ( n ) , será chamado sempre um valor menor de n até que n = 1 , precisamos estabelecer nosso caso inicial, que é o caso que a função não vai chamar a si mesma ( n = 1 ). Esse vai ser o S _ r e c u r s i v o ( 1 ) = 1 .

Com base nisso, vamos declarar essa função com ambos, retorno e parâmetro, como u n s i g n e d   i n t , pois só serão utilizados valores positivos nessa função.

Passo 2

Já tendo definido o valor inicial para a nossa função de recursão, S 1 = 1 , vamos colocar isso no nosso código usando um condicional:

A seguir, vamos colocar a relação de recursão. E, para calcular o n 3 , vamos incluir a biblioteca m a t h . h e utilizar a função p o w para calcular potências.

Passo 3

Agora, vamos focar na função S _ i t e r a t i v o . Como ele também irá trabalhar apenas com valores interos positivos, vamos declarar essa função e o seu parâmetro como u n s i g n e d   i n t .

A seguir, para calcularmos a soma dos cubos por meio de um laço de repetição, é necessário criar uma variável para acumular o valor dessa soma, que deverá ter valor inicial nulo. Vamos chamá-la de S .

Vamos usar um laço de f o r para que S acumule o valor de cada cubo, e esse loop precisa fazer n iterações. Então vamos definir a variável i começando do 1 e indo até o n .

Por fim, basta retornarmos o valor de S ao término do laço de f o r .

Resposta

Exercício Resolvido #6

Seja a somatória abaixo. Faça uma função recursiva para realizar o cálculo.

∑ i = 1 n i 2

Passo 1

Eai, tudo beleza?

Nessa questão, nós temos que calcular uma soma de termos de forma recursiva. Uma boa maneira de fazermos isso, é fazermos uma função que recebe um valor i e chama ela mesma, mas enviando um valor i - 1 . Para que a função pare a recursividade, podemos fazer com que ela retorne 1 , caso o valor enviado para ela seja 1 , já que esse é o menor termo do somatório que está no enunciado.

Para começar o nosso código, vamos declarar a nossa função S o m a D e Q u a d r a d o s :

Passo 2

Dentro dessa função, a primeira coisa que vamos fazer é colocar o condicional para verificar se i é igual a 1 . Nesse caso, nós retornamos 1 , encerrando a função.

Passo 3

Caso a função não esteja encerrada, ou seja, i não é 1 , vamos retornar i 2 + S o m a D e Q u a d r a d o s ( i - 1 ) . Dessa forma, ao final da recursão, essa função retornará o somatório que queremos.

Resposta

Exercício Resolvido #7

Os números de Catalan são definidos pela seguinte recursão

  • C ( n )   =   1 , se n   =   0
  • C n = 2 2 n - 1 n + 1 C ( n   -   1 ) , se n   >   0

Alguns números desta sequência são: 1 ,   1 ,   2 ,   5 ,   14 ,   42 ,   132 ,   429 ,   1430 ,   4862 …

Faça uma função recursiva que receba um número N e retorne o N-ésimo número de Catalan.

Passo 1

Eai, tudo beleza?

Nessa questão, nós temos que calcular os números da sequência de Catalan usando recursividade. Nesse caso, vamos iniciar a nossa função checando se o valor passado como parâmetro é igual a 0 . Caso seja, vamos retornar 1 , assim como o enunciado nos mostra funciona a sequência de Catalan. Esse será o caso que vai determinar o fim da recursividade.

Passo 2

Agora, vamos implementar a recursividade da sequência. Para isso, vamos fazer o nosso programa retornar 2 2 n - 1 n + 1 C a t a l a n ( n   -   1 ) . Dessa forma, vamos enviar sempre um número menor para a nossa função até que ele seja igual a 0 e a recursividade seja encerrada.

Resposta

Exercício Resolvido #8

Escreva, usando a linguagem C++, uma função recursiva, S o m a S e r i e ( i , j , k ) , que devolva a soma da série de valores do intervalo [ i , j ] , com incremento k .

Passo 1

Eai, tudo tranquilo contigo?

Nessa questão, nós temos que fazer uma função que soma uma série de valores que podem ir de i até j , variando de k em k . Para fazer isso, primeiramente vamos pensar no caso em que vamos encerrar a recursão. Essa recursão deve ser encerrada no caso de i + k ser maior do que o final do intervalo, ou seja j . Nesse caso, vamos retornar o somente o valor de i :

Passo 2

A seguir, vamos implementar a nossa recursividade. Nela, vamos retornar o valor de i somado ao valor de S o m a S e r i e ( i + k ,   j ,   k ) . Desse modo, a função vai retornar o menor valor do intervalo somado à função recebendo o valor do novo intervalo com o incremento, que vai retornar o menor valor desse intervalo ( i + k ) + a função iniciando no intervalo i + 2 k ... e esse processo vai continuar até que o valor passado no inicia do intervalo somado ao incremento k seja maior que j .

Resposta