Entendendo recursão

Pessoal bom dia,

Seguindo a seguinte questão da apostila da caelum:

Fibonacci fibonacci = new Fibonacci(); for (int i = 1; i <= 6; i++) { int resultado = fibonacci.calculaFibonacci(i); System.out.println(resultado); }

Aqui imprimirá a sequência de Fibonacci até a sexta posição, isto é: 1, 1, 2, 3, 5, 8�.
Este método calculaFibonacci não pode ter nenhum laço, só pode chamar ele mesmo como método.
Pense nele como uma função, que usa a própria função para calcular o resultado.

A única coisa que consegui fazer é:

[code]int calculaFibonacci (int i){

}

[/code]

Não entendi como o calculaFibonacci só vai chamar ele mesmo e ainda vai calcular os números da estrutura para gerar a sequencia.

Alguem pode me ajudar por favor?

Bom, o código para calcular uma Sequência de Fibonnaci via recursão é amplamente conhecido, então não vejo necessidade de postá-lo aqui. Você pode pesquisá-lo na internet, se é isso que deseja.

Se o que deseja é entender recursão, note que o conceito é um método/função/procedimento que chama ela mesma.

Escrevendo de novo: uma função/método recursivo é um método que executa a si mesmo.

Escrevendo de novo: uma função/método recursivo é um método que executa a si mesmo.

Escrevendo de novo: uma função/método recursivo é um método que executa a si mesmo.

O link da Wikipedia é um pouco mais matemático que o necessário. Este outro link também, mas possui alguns exemplos de aplicação em programação.

Mas vamos lá.

Por uma função/método que chama a si mesmo, entenda um código assim:

public tipo fazAlgumaCoisa(tipo valorQualquer){ fazAlgumaCoisa(valorQualquer); }

Note que a função fazAlgumaCoisa() chama a si própria.

Obviamente, a chamada a função fazAlgumaCoisa() deve ser interrompida em algum momento, senão nunca obteremos o resultado da execução da função fazAlgumaCoisa(), além de obter algum erro. O erro depende da linguagem e das operações realizadas pela função. Pode falta de memória (OutOfMemoryError), estouro de pilha (StackOverFlow), etc.

Então precisamos criar uma condição de parada para a chamada a função fazAlgumaCoisa(). Se essa condição for atingida, ao invés de chamar a função fazAlgumaCoisa(), nós retornamos algum valor (ou mesmo não fazemos nada, dependendo do caso). Nossa função fica:

public tipo fazAlgumaCoisa(tipo valorQualquer){ if (atingiu_a_condicao_de_parada){ return valorQualquer; } else{ return fazAlgumaCoisa(valorQualquer); } }

E é isso. Essa é a estrutura comum de uma função recursiva. Vamos a um exemplo para fixar a ideia. Ao invés de fibonnaci, vamos somar alguns números inteiros (outro exemplo clássico). Somaremos os números de 0 até X. Então:

  • a função recebe um parâmetro X (100, por exemplo), que é o número até o qual nós desejamos somar;
  • somaremos todos os números de 1 até X, então nosso critério de parada pode ser quando o número a ser somado já é X ou é 1. Optaremos pelo 1;
  • para não somarmos novamente o mesmo número (e ficarmos presos e uma repetição infinita), devemos alterar o número antes de chamar novamente a função recursiva. Podemos incrementar ou decrementar o número. Como vamos de X até um, vamos decrementar o número até que ele seja um, o que ativa nossa condição de parada.

public int somaNumeros(int numero){ if (numero == 1){ return numero; }else{ return numero + somaNumeros(numero-1); } }

Veja que na linha:

return numero + somaNumeros(numero-1);

a adição só é executada quando a função somaNumeros(numero-1) retorna, então a função fica aguardando nesse ponto até que essa função retorne.

Abraço.

int calculaFibonacci (int i){ return i <= 1 ? i : calculaFibonacci(i-1) + calculaFibonacci(i-2); }

De certa forma eu estava procurando a resposta, mas queria entender pra tentar fazer.

Agradecido alexmonassa.

TerraSkilll,

To tentando entender isso, então na linha "return numero + somaNumeros(numero-1); " ele não vai realizar a soma aos poucos, só vai ficar chamando o método diversas vezes até chegar ao 1 e então somar, certo?

Se for, achei difícil pensar em uma solução assim, exercitar essa capacidade de abstrair rs.

A melhor maneira de entenderes a recursão é veres este post.

O link está direcionando para esse meu post.

Pelo teu comentário posso concluir então que ainda não entendes o que é recursão.

Agora entendi.

De forma simplificada, é isso sim. As chamadas de função são empilhadas para execução até que alguma delas retorne, e daí cada uma delas vai executar seu cálculo e retornar para a anterior, até que não haja mais funções a serem executadas. Voltando ao meu exemplo, se fizermos somaNumeros(5) , as chamadas de função ficam:

somaNumeros(5) + somaNumeros(4) + somaNumeros(3) + somaNumeros(2) + somaNumeros(1)

Como somaNumeros(1) retorna o próprio número (1, no caso), as chamadas de função ficam:

somaNumeros(5) + somaNumeros(4) + somaNumeros(3) + somaNumeros(2) + 1

Com isso, somaNumeros(2) pode executar. Ele executa a soma de numero (que é 2) + o resultado de somaNumeros(1) (que é 1), resultando em 3:

somaNumeros(5) + somaNumeros(4) + somaNumeros(3) + 2 + 1

E a função somaNumeros(2) encerra e retorna para somaNumeros(3), que agora pode executar:

somaNumeros(5) + somaNumeros(4) + somaNumeros(3) + 3

E assim sucessivamente, até acabar as chamadas de função:

somaNumeros(5) + somaNumeros(4) + 6

somaNumeros(5) + 10

5 + 10

O que retorna 15 no final.

Recursão é algo que você pode não usar sempre, mas é bom saber que existe, pois pode ser útil em determinados casos. Continue estudando.

Abraço.

Muito obrigado pela explicação detalhada, to anotando certas coisas pra exercitar mais e botei recursão :wink:

Para bolar uma função recursiva, tem que pensar em 2 coisas:

a) A condição de parada;
b) A condição que se repete.

No caso da sequencia de Fibonnaci, o a condição que se repete descreve a sequencia:
“Um número qualquer na sequencia de Fibonacci é dado pela soma dos dois números anteriores a ele”.

Portanto, na sequencia:

0o termo - 0
1o termo- 1
2o termo - 1
3o termo - 2
4o termo - 3
5o termo - 5
6o termo - 8

Sabemos que 5 é a soma de 3+2. 3 veio da soma de 2+1 e assim por diante. Passando isso para termos, podemos dizer que o termo N é a soma do termo N-1 (o anterior) com N-2 (o anterior do anterior), certo? Essa é a condição que se repete.

Ok, usando essa condição, qual seria a condição de parada? Isso é geralmente dado pelas condições que vem “por definição”. Por exemplo, por definição o 0o termo da sequencia é zero. E também por definição o 1o termo é 1. Essa é a condição de parada.

Assim, com as duas regras:

Fib(0) = 0 //Condição de parada Fib(1) = 1 //Condição de parada Fib(N) = FIB(N-1) + FIB(N-2) //Repetição

Nós temos tudo o que precisamos para elaborar a função recursiva:

int fib(int n) { if (n == 0) return 0; //Por definição if (n == 1) return 1; //Por definição return fib(n-1) + fib(n-2); }

Outro exemplo. Uma função recursiva para calcular fatorial. O fatorial de um número é dado por:
5! = 54321
Agora, observe que isso é o mesmo que escrever:
5! = 5 * 4!
Que por sua vez é:
4! = 4 * 3!

Essa é a condição de repetição. E qual é a condição de parada? Por definição, o fatorial de 0 é 1. Há quem argumente também que o fatorial de 1 é 1 por definição.

Portanto:
Fat(0) = 1
Fat(1) = 1
Fat(N) = N * FAT(N-1);

public int fat(int n) { if (n == 0 || n == 1) return 1; //Por definição return n * fat(n-1); }

A execução de fat(5) faria:
fat(5) = return 5 * fat(4):
entra em fat(4) = return 4 * fat(3);
entra em fat(3) = return 3 * fat(2);
entra em fat(2) = return 2 * fat(1);
entra em fat(1) = return 1;
volta para fat(2) = return 2 * 1 = 2;
volta para fat(3) = return 3 * 2 = 6;
volta para fat(4) = return 4 * 6 = 24;
volta para fat(5) = return 5 * 24 = 120;

Puts Vini, obrigado, ajudou a clarear mais o que os colegas acima disseram. Vou deixar essas explicações favoritadas aqui para futuras dúvidas!