Recursivedade com vetores

14 respostas
Odyo

Bom,

Comecei a ver alguns exemplos de recursividade ( Fibonnacci, Fatorial etc … )
e gostaria de uma ajuda com uma dúvida que tive …

é possível preencher um vetor usando recursividade ?
alguém pode me dar um exemplo ? rascunhei algumas
tentativas aqui e não me vem nada a cabeça …

Alguma sugestão de bons exercícios para trabalhar com
recursão ??

14 Respostas

cassio

Perfeitamente possível. Pensei em algo bastante simples, dá uma olhada e veja se entende

public class RecursaoVetor {	
	/**
		Exemplo que preenche um vetor com os valores
		iguais a cada indice.
	*/
	private static void preencheVetor(int[] vetor, int pos) {
		if(pos == vetor.length - 1) return;
		vetor[pos] = pos;
		preencheVetor(vetor, pos + 1);
	}
	
	public static void main(String args[]) {
		int[] vetor = new int[10];
		preencheVetor(vetor, 0);
		for(int i = 0; i < vetor.length; ++i)	System.out.print(i + " ");
	}	
}
Odyo

entendi.

eu posso falar que um método recursivo funciona como uma laço ?
Que consome mais memória …

tô apanhando um pouco pra pensar recursivamente … !

cassio

Funciona de forma parecida a um laço, se você pensar no aspecto repetitivo de uma chamada recursiva. Repetitivo pois uma função (ou método) recursivo chama a si mesma n vezes, até que uma dada condição seja satisfeita e a função (ou método) retorne.
O consumo de memória realmente é um pouco maior, pois as chamadas são empilhadas e liberadas da memória somente quando a condição trivial (quando a função pára de chamar a si mesma) é alcançada e todas as chamadas retornam.

T

Outra forma de preencher o vetor usando recursividade.

A idéia é a seguinte:

  • Se o vetor tiver apenas 1 elemento, atribua um valor a esse elemento.
  • Para preencher um vetor, você precisa preencher suas duas metades.
class PreencherVetorRecursivo {

	/**
	 * @param vec O vetor a ser preenchido
	 * @param start A posição inicial a ser preenchida
	 * @param end A posição final a ser preenchida MAIS UM (como o método String.substring)
	 * @param val O valor que deve ser usado para preencher o vetor.
	 */
	public static void preencher (int[] vec, int start, int end, int val) {
		if (start >= end) return; // 0 elementos, ou então chamada inválida
		if (start + 1 == end) { // ou seja, só um elemento
			vec[start] = val;
		} else {
			// Preencher a primeira metade
			preencher (vec, start, (start + end) / 2, val);
			// Preencher a segunda metade
			preencher (vec, (start + end) / 2, end, val);
		}
	}

    public static void main(String[] args) {
	    int[] valores = new int[1234];
		final int THE_ANSWER = 42;
		// Efetuando o preenchimento recursivo...
	    preencher (valores, 0, valores.length, THE_ANSWER);
		// Aqui estamos checando para ver se todos os valores são realmente 42
		// - não se esqueça que em Java, ao alocar um vetor os valores são 0 ou null.
		boolean ok = true;
		for (int i = 0; i < valores.length; ++i) {
		    if (valores [i] != THE_ANSWER) {
				ok = false;
				break;
			}
		}
		if (!ok) {
			System.out.println ("Não conseguiu preencher o vetor adequadamente");
		} else {
			System.out.println ("O vetor foi adequadamente preenchido.");
		}
	}
}
T

Estou dando esse exemplo de “quebrar o vetor no meio” porque ele se parece um pouco com o quicksort, que também funciona de forma parecida.
Muitos problemas recursivos se resolvem quebrando o problema em pedaços, até que o problema seja suficientemente pequeno para ser resolvido de maneira simples.
No exemplo que lhe passei, eu quebrei o problema em 2, e cada subproblema em 2, até que o problema fosse bem pequeninho (preencher apenas 1 posição do vetor).

Mantu

Pra você ter noção do quanto um algoritmo recursivo consome mais memória que um não-recursivo, e já que vc quer um exercício, faça algo assim, pra brincar e testar:
Cria aí uma classe que exiba na tela os elementos 1, 2, 3 … 10, 15, 20, 25, 30, 40 de uma Fibonacci. Só que nesse seu programa, crie dois métodos distintos para buscar o n-ésimo elemento da série de Fibonacci, um recursivo e outro não recursivo. No main da sua classe, faça o programa então exibir algo assim na tela:

Utilizando algoritmo NÃO-recursivo:
Elemento 1 = 1
Elemento 2 = 1
Elemento 3 = 2
Elemento 4 = 3
Elemento 5 = 5
Elemento 6 = 8
Elemento 7 = 13
Elemento 8 = 21
Elemento 9 = 34
Elemento 10 = 55
Elemento 15 = 610
Elemento 20 = 6765
Elemento 25 = 75025
Elemento 30 = 832040
Elemento 35 = 9227465
Elemento 40 = 102334155
=======================
Utilizando algoritmo recursivo:
...

Divirta-se!

T

Existem maneiras e maneiras de fazer a recursividade. Nem sempre a recursividade usa assim mais memória.

O jeito que o Cassio lhe passou “explode” em Java quando o número de elementos do vetor é por volta de 90.000; o meu jeito (quebrando o problema sempre em “metades”) “explode” bem mais devagar. Na prática, meu jeito não deve explodir a pilha (stack) do Java - isso porque o maior tamanho de um vetor em Java são 2^31 elementos e com esse valor uso apenas 32 níveis de recursão, em vez de 2^31 níveis de recursão.

T

Nem sempre métodos recursivos são tão lentos quanto se pensa. É questão de saber algumas técnicas.
A que vou mostrar agora é a tal de “memoizing”. Rode este programa e você vai ver que Fibonacci não é tão lento assim recursivamente.

import java.util.*;

/**
 * "Memoizing" (que eu vou traduzir por "memorização") é uma técnica de acelerar programas
 * recursivos guardando resultados já calculados.
 * Embora existam maneiras mais simples e eficientes de calcular os números de Fibonacci, vou
 * mostrar uma técnica genérica que não exige mexer muito no seu algoritmo.
 * Como vocês devem saber:
 * fibonacci (0) = 0
 * fibonacci (1) = 1
 * fibonacci (n) = fibonacci (n - 1) + fibonacci (n - 2)
 */
class FibonacciComMemorizacao {
	
	Map<Integer,Integer> preCalculado = new TreeMap<Integer,Integer>();
	
	public FibonacciComMemorizacao() {
		preCalculado.put (0, 0); // fibonacci (0) = 0
		preCalculado.put (1, 1); // fibonacci (1) = 1
	}
	
	public int fibonacci (int n) {
		if (n == 0) {
			return 0;
		} else if (n == 1) {
			return 1;
		} else if (n > 1) { // fibonacci (n) = fibonacci (n - 1) + fibonacci (n - 2)
			Integer f = preCalculado.get (n); // vamos ver se já foi calculado antes...
			if (f != null) {
				// volto o valor pré-calculado!
				return f;
			} else {
				// Não tem jeito, tem de calcular pela fórmula recursiva
				f = fibonacci (n - 1) + fibonacci (n - 2);
				// E agora vamos guardar o resultado
				preCalculado.put (n, f);
				return f;
			}
		} else {
			throw new IllegalArgumentException ("Definido apenas para os números positivos.");
		}
	}

    public static void main(String[] args) {
		FibonacciComMemorizacao fcm = new FibonacciComMemorizacao();
		Integer[] valores = {
			1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40
		};
		/*
		Valores esperados: (veja a seqüência http://www.research.att.com/~njas/sequences/A000045)
Fibonacci (1) = 1
Fibonacci (2) = 1
Fibonacci (3) = 2
Fibonacci (4) = 3
Fibonacci (5) = 5
Fibonacci (6) = 8
Fibonacci (7) = 13
Fibonacci (8) = 21
Fibonacci (9) = 34
Fibonacci (10) = 55
Fibonacci (15) = 610
Fibonacci (20) = 6765
Fibonacci (25) = 75025
Fibonacci (30) = 832040
Fibonacci (35) = 9227465
Fibonacci (40) = 102334155		
		 */
		for (int i = 0; i < valores.length; ++i) {
			System.out.println ("Fibonacci (" + valores[i] + ") = " + fcm.fibonacci (valores[i]));
		}
	}
}
T

Eu usei, no “memoizing”, um Map<Integer,Integer> em vez de um int[] (como você também poderia fazer) para exemplificar uma técnica genérica.
É que nem sempre os valores recursivos são assim tão contíguos, como é o caso do Fibonacci.

T

Sugestão: modifiquem meu programa para usar BigInteger em vez de long. Se você calcular fibonacci (10000), vai ver que o valor é:

Fibonacci (10000) = 336447648764317832666216120051075433103021484606800639065647
69974680081442166662368155595513633734025582065332680836159373734790483865268263
04089246305643188735454436955982749160660209988418393386465273130008883026923567
36131351175792974378544137521305205043477016022647583189065278908551543661595829
87279682987510631200575428783453215515103870818298969791613127856265033195487140
21428753269818796204693609787990035096230229102636813149319527563022783762844154
03605844025721143349611800230912082870460889239623288354615057765832712525460935
91128203925285393434620904245248929403901706233888991085841065183173360437470737
90855263176432573399371287193758774689747992630583706574283016163740896917842637
86242128352581128205163702980893320999057079200643674262023897831114700540749984
59250360633560933883831923386783056136435351892133279732908133732642652633989763
92272340788292817795358057099369104917547080893184105614632233821746563732124822
63830921032977016480547262438423748624114530938122065649140327510866433945175121
61526545361333111314042436854805106765843493523836959653428071768775328348234345
55736671973139274627362910821067928078471803532913117677892465908993863545932789
45237776744061922403376386740040213303432974969020283281459334188268176838930720
03634795623117103101291953169794607632737589253530772552375943788434504067715555
77905645044301664011946258097221672975861502696844314695203461493229110597067624
32685159928347098912847067408620085871350162603120719031720860940812983215810772
82076353186624611278245537208532365305775956430072517744315051539600905168603220
34916322264088524885243315805153484962243484829938090507048348244932745373262456
77558790891871908036620580095947431500524025327097469953187707243768259074199396
32265984147498193609285223945039707165443156421328157688908058783183404917434556
27052022356484649519611246026831397097506938264870661326450766507461151267752274
86215986425307112984411826226610571635150692600298617049454250474913781151541399
41550671256271197133252763631939606902895650288268608362241082050562430701794976
171121233066073310059947366875
Odyo

Grato a todos pela ajuda !
:smiley:

vou continuar praticando aqui até melhorar !

dei uma olhada no algoritmo do QuickSort e nao gostei muito da cara dele !
:shock:

T

Para praticar com algo prático mesmo (que você vai sempre usar), tente listar os subdiretórios de um diretório, recursivamente.

Acho que há alguns exemplos desse aqui no fórum; encontre-os e veja como foram feitos.

Abdon

Thingol,

Existe algum forma de fazer a pratica que vc sugere de uma forma mais elegante como por exemplo a tecnica de divisão,  que eu acredito que a de memorização não vai rolar para este caso.
      Fazendo na força bruta, da forma que o cassio indicou eu fiz numa boa, so que com mais elegancia eu não consegui, pois não tive nenhuma boa idéia de algoritmo.
T

Sr (ou sra.) Ovelha (eu desliguei todas as figuras do GUJ, até por uma questão de equanimidade e não consigo ver seu avatar):

Qual é o seu problema? Em recursão não existem “melhores práticas”; e há diferentes maneiras de fazer a mesma coisa.

Criado 14 de fevereiro de 2008
Ultima resposta 15 de fev. de 2008
Respostas 14
Participantes 5