O documento discute conceitos de programação como variáveis dinâmicas, abstração e recursividade. Ele introduz tópicos como sistemas analógicos versus digitais, computadores, software, linguagens de programação e compiladores. Também explica variáveis dinâmicas versus estáticas, alocação dinâmica de memória, estruturas de dados dinâmicas e ponteiros. Por fim, aborda abstração, tipos abstratos de dados, interfaces, classes e métodos abstratos.
3. Introdução
Alguns tópicos a serem discutidos
inicialmente:
◦ Sistema analógico x digital
◦ Computador
◦ Software
◦ Linguagem de programação
◦ Compilador
◦ Dinâmica x estática
4. Introdução
Sistema analógico
◦ Pode assumir infinitos valores dentro de
determinados intervalos
Sistema digital
◦ Assume valores discretos binários (0 ou 1)
Etimologia: latim -> digitus -> significa dedo
Quant. finita de dedos -> sistema decimal (10
dedos)
5. Introdução
Computador
◦ Máquina que realiza processamento de dados
◦ Sistema físico que realiza algum tipo de
computação
◦ Arquitetura -> digital -> John von Neumann
Memória
instruções
Unidade
Unidade de
Lógica
Controle
Aritmética
dados
E/S
6. Introdução
Software
◦ Seqüência de instruções a serem seguidas
pelo computador para alcançar um resultado.
Executado pelo processador ou máquina virtual;
Construído por uma linguagem de programação
Linguagem de Programação
◦ Conjunto de regras usada para transformar
os comandos de um programa em instruções
para o computador
7. Introdução
Compilador
◦ Programa que, a partir do código escrito em uma
linguagem (código-fonte), cria um programa escrito
em outra linguagem (código-objeto).
Análise léxica, sintática e semântica.
Dinâmica
◦ Parte da física que estuda os movimentos e suas
causas (Mecânica).
◦ Iniciada por Newton e presente na segunda lei:
A taxa de variação no tempo da quantidade de movimento de
um objeto é igual à soma das forças aplicadas no mesmo.
8. Variáveis Dinâmicas
Estático x Dinâmico
◦ A verificação do tipo de dado é feita de forma
estática em tempo de compilação, ou de
forma dinâmica em tempo de execução.
Ponteiro
◦ Tipo de dado cujo valor se refere
diretamente a um outro valor alocado em
outra área de memória, através de seu
endereço.
9. Variáveis Dinâmicas
Ponteiro é o principal tipo de dado
utilizado na alocação dinâmica de
variáveis.
Em Java, esta alocação é implícita, sem
descrição direta dos ponteiros e seus
endereços de memória.
Essa responsabilidade fica por conta da
ferramenta presente na JVM: Coletor de
Lixo (Garbage Colector, ou somente
GC).
10. Variáveis Dinâmicas
Em C:
#include <stdio.h>
void altera (int *n) { *n = 120; }
int main(){
int x = 24;
int *endereco = &x; /* o operador ‘&’ (referência) retorna o endereço de
uma variável */
printf(“%d n”, x); /* descreve valor de x */
printf(“%p n”,endereco); /* descreve o endereço de x */
altera(&x); /* passa o endereço de x como referência, para alteração */
printf(“%d n”,x); /* mostra o novo valor de x */
printf(“%p %p n”,endereco,&x); /* note que o endereço de x não foi
alterado*/
return 0;
}
11. Variáveis Dinâmicas
Em Java:
public class Principal{
public static void main(String args[]){
Ponto p1, p2;
p1 = new Ponto();
p1.x = 3; p1.y = 1;
p2 = p1; // atribuindo referência ao objeto p1
p1.x = 5;
System.out.println(“Valores de p1: ”+p1.x+“ e ”+p1.y);
System.out.println(“Valores de p2: ”+p2.x+“ e ”+p2.y);
}
}
class Ponto{
public int x;
public int y;
Ponto(){}
}
12. Variáveis Dinâmicas
Atribuir uma referência faz com que
ambas apontem para o mesmo objeto.
Qualquer alteração afeta as duas variáveis.
p1 3 1 p2 ...
x y
p1 3 1 p2 p2 = p1;
x y
p1 5 1 p2 p1.x = 5;
x y
13. Variáveis Dinâmicas
Em Java, todos os tipos primitivos são
representados por valores:
◦ char c; c = ‘a’;
◦ int x, y; x = y = 3;
Os tipos compostos (arrays e classes) utilizam
referências:
◦ String s; s = “if672”;
◦ Ponto p; p = new Ponto(3,1); p.x = x;
◦ Integer n; n = new Integer(3);
◦ ArrayList v; v = null;
14. Alocação dinâmica de memória
A memória só é requisitada no momento
em que o objeto é necessário, evitando o
desperdício. Isso ocorre através do
operador new:
◦ Ponto p = new Ponto(800,600);
◦ Parâmetros do Construtor
A memória é alocada para o novo objeto,
e sua posição armazenada em uma
referência (ponteiro).
15. Alocação dinâmica de memória
Código-Fonte Memória
int[ ] a; a b
1 int[ ] b;
...
a = new int[7];
b = new int[4];
...
a = b;
1
16. Alocação dinâmica de memória
Código-Fonte Memória
int[ ] a; a b
int[ ] b;
...
a = new int[7];
2 b = new int[4]; int [ 7 ]
...
a = b;
int [ 4 ]
2
17. Alocação dinâmica de memória
Código-Fonte Memória
int[ ] a; a b
int[ ] b;
...
a = new int[7];
b = new int[4]; int [ 7 ]
...
3 a = b;
int [ 4 ]
3
Não há mais ponteiro para essa região. O que acontece com ela?
18. Alocação dinâmica de memória
É possível então desalocar as variáveis antes de
eliminar a última referência para ela. Em Java,
isso não é necessário, pois o garbage collector
libera a memória automaticamente.
◦ Para chamar diretamente a GC: System.gc();
Destruição de objetos em Java:
◦ explícita: objeto = null;
◦ implícita: contexto (gerenciamento pelo GC)
fim da execução do método;
ocorrência de uma exceção;
perca da referencia: objeto2 = objeto1;
19. Alocação dinâmica de memória
Programa Memória Heap
Objeto X
Y = X
Objeto Y Objeto pode
ficar sem
referência
Coletor de lixo: retira da memória objetos não usados.
Exemplo:
Ator a3 = new Ator(“Claudia”,22, ‘f’);
System.out.println(a3.nome);
a3 = null;
System.out.println(a3.nome);
Exceção: java.lang.NullPointerException
20. Estruturas de dados dinâmicas
Listas encadeadas
◦ São coleções de itens de dados “colocados em fila” e as
inserções e exclusões podem ser feitas em qualquer lugar (início
ou final);
Pilhas
◦ Inserções e exclusões são feitas apenas em uma extremidade
(parte superior – topo – LIFO). São de importante uso em
compiladores e sistemas operacionais.
Filas
◦ As inserções são feitas na parte de trás (cauda) de uma fila e as
exclusões são feitas a partir da parte da frente (cabeça) –
FIFO.
21. Estruturas de dados dinâmicas
Arrays x Listas
◦ Arrays podem ocupar espaço desnecessário
na memória, mas seu acesso é feito
diretamente.
◦ Listas ocupam apenas o espaço necessário,
mas é preciso espaço extra para armazenar as
referências.
Além disso, seu acesso é seqüencial.
22. Estruturas de dados dinâmicas
Classes auto-referenciais: contêm uma variável de
instância que faz referência a outro objeto do mesmo
tipo de classe.
class No{
private String dado;
private No proxNo; // próximo Nó – referência
public No (String dado) { /* corpo do construtor */
setProxNo(null); }
public void setDado (String dado) { /* corpo do método */ }
public String getDado () { }
public void setProxNo (No prox) { }
public No getProxNo () { }
}
23. Estruturas de dados dinâmicas
Os programas podem encadear objetos auto-
referenciais para formar estrutura de
dados úteis como listas, filas, pilhas e árvores.
Permitindo assim uma alocação dinâmica de
memória:
◦ capacidade de um programa obter mais espaço de
memória durante a execução para armazenar
novos nós e liberar espaço não mais necessário.
O limite para alocação dinâmica pode ser tão
grande quanto a quantidade de memória
física disponível no computador.
24. Estruturas de dados dinâmicas
Listas: a construção é feita a partir de
ponteiros/referências.
5 8 1 4
Geralmente contém uma referência para o primeiro
elemento da lista (No inicio), a partir do qual todos os
outros poderão ser acessados.
L 3 1 2 X
Dado armazenado Referência para o Indicador do
no nó atual próximo elemento fim da lista
da lista
25. Estruturas de dados dinâmicas
Inserção em listas:
◦ Se a lista estiver vazia:
L X 3 X
◦ Caso contrário, inserir no fim da lista:
L 3 1 2 X
último nó? último nó? último nó?
9 X
NÃO! NÃO! SIM!
novo nó
26. Estruturas de dados dinâmicas
Para inserir um novo nó entre outros dois:
... 4 7 ...
anterior anterior.next
5 X
novoNo
anterior.next
No novoNo = new No(5);
novoNo.next = anterior.next;
anterior.next = novoNo;
27. Estruturas de dados dinâmicas
Remoção em listas:
◦ Para excluir um nó entre outros dois:
... 1 X 9 2 ...
anterior nó atual anterior.next.next
anterior.next
anterior.next = anterior.next.next;
29. Abstração
Tipo de Dados
◦ Caracteriza o conjunto de valores a que uma
constante pertence, ou que podem ser
assumidos por uma variável ou expressão, ou
que podem ser gerados por uma função.
30. Abstração
Tipo Abstrato de Dados
◦ Pode ser visto como um modelo matemático,
acompanhado das operações definidas sobre
o modelo. O conjunto dos inteiros
acompanhado das operações de adição,
subtração e multiplicação forma um exemplo
de um tipo abstrato de dados.
31. Abstração x Encapsulamento
Abstração
◦ Cliente não precisa saber mais do que o
especificado na interface para usar em uma
classe;
Encapsulamento
◦ Cliente não consegue saber nada sobre uma
classe além do que está expresso em sua
interface.
33. Interface - Exemplo
package aula.abstracao;
public interface Forma {
// calcula a área
public abstract double area();
// devolve o nome da figura
public abstract String getNome();
}
34. Interface - Exemplo
package aula.abstracao;
public class Ponto implements Forma{
private int x,y;
public Ponto(){ setPonto(0,0); }
public Ponto(int coordX, int coordY){ setPonto(coordX,coordY); }
public void setPonto(int coordX, int coordY){
setX(coordX); setY(coordY);
}
(...) /** métodos de acesso aos atributos privados */
public String toString(){ return "["+x+", "+y+"]"; } // herda de Object
public double area(){ return 0.0; } // segue a interface
public String getNome(){ return "Ponto"; } // segue a interface
}
35. Interface - Exemplo
package aula.abstracao;
public class Circulo implements Forma{
private int x,y; private double raio;
public Circulo(){ setCirculo(0,0,0.0); }
public Circulo(int coordX, int coordY, double r){
setCirculo(coordX, coordY, r); }
public void setCirculo(int coordX, int coordY, double r){
setX(coordX); setY(coordY); setRaio(r);
}
(...) /** métodos de acesso aos atributos privados */
public String toString(){ return "Centro = ["+x+", "+y+"] e Raio = "+raio;
}
public double area(){ return Math.PI*raio*raio; } // segue a interface
public String getNome(){ return "Círculo"; } // segue a interface
}
36. Interface - Exemplo
package aula. abstracao;
public class TesteInterface {
public static void main(String args[]){
// cria formas
Ponto ponto = new Ponto(7,11);
Circulo circulo = new Circulo(22,8,3.5);
Ponto ponto2 = new Ponto(81,25);
// cria array de formas
Forma arrayDeFormas[] = new Forma[3];
// aponta arrayDeFormas[0] para o ojbeto da subclasse Ponto
arrayDeFormas[0] = ponto;
arrayDeFormas[1] = circulo;
arrayDeFormas[2] = ponto2;
(...)
37. Interface - Exemplo
String output = " ";
// obtém o nome e a representação de cada forma
for(int i = 0; i < arrayDeFormas.length; i++){
output += "nn"+arrayDeFormas[i].getNome()+
": "+arrayDeFormas[i].toString()+
"nÁrea = "+arrayDeFormas[i].area();
}
System.out.println(output);
}
}
38. Interface - Exemplo
Saída:
Ponto: [7, 11]
Área = 0.0
Círculo: Centro = [22, 8] e Raio = 3.5
Área = 38.48451000647496
Ponto: [81, 25]
Área = 0.0
39. Classes e métodos abstratos
Classes abstratas:
◦ Classes que são demasiadamente gerais para criar
objetos reais.
◦ Utilizadas somente como superclasses abstratas para
subclasses concretas e para declarar variáveis de
referência.
◦ Muitas hierarquias de herança têm superclasses
abstratas que ocupam os poucos níveis superiores.
◦ Palavra-chave abstract:
Utilize para declarar uma classe abstract.
Também utilize para declarar um método
abstract:
As classes abstratas normalmente contêm um ou mais
métodos abstratos.
Todas as subclasses concretas devem sobrescrever todos os
métodos abstratos herdados.
40. Classes e métodos abstratos
Classe Iteradora:
◦ Pode percorrer todos os objetos em uma
coleção, como um array ou um ArrayList.
◦ Os iteradores são freqüentemente utilizados na
programação polimórfica para percorrer uma
coleção que contém referências a objetos
provenientes de vários níveis de uma hierarquia.
41. Classes e métodos abstratos
Uma classe abstrata declara atributos e
comportamentos comuns das várias classes em
uma hierarquia de classes.
Em geral, uma classe abstrata contém um ou
mais métodos abstratos que as subclasses
devem sobrescrever se as subclasses precisarem
ser concretas.
Variáveis de instância e métodos concretos de
uma classe abstrata estão sujeitos às regras
normais da herança.
42. Classes e métodos abstratos
Tentar instanciar um objeto de uma classe
abstrata é um erro de compilação.
Não implementar os métodos abstratos de uma
superclasse em uma subclasse é um erro de
compilação, a menos que a subclasse também
seja declarada abstract.
43. Classes e métodos abstratos
Uma subclasse pode herdar a ‘interface’ ou
‘implementação’ de uma superclasse.
◦ Hierarquias projetadas para a herança de
implementação tendem a ter suas funcionalidades na
parte superior da hierarquia
cada nova subclasse herda um ou mais métodos que foram implementados
em uma superclasse e a subclasse utiliza essas implementações de
superclasse.
◦ As hierarquias projetadas para a herança de interface
tendem a ter suas funcionalidades na parte inferior da
hierarquia
uma superclasse especifica um ou mais métodos abstratos que devem ser
declarados para cada classe concreta na hierarquia;
e as subclasses individuais sobrescrevem esses métodos para fornecer
implementações específicas de subclasses.
44. Classes e métodos abstratos
Forma
Ponto
Ponto
Circulo
Circulo
Cilindro
Cilindro
Herança de Herança de
Implementação Interface
45. Classes e métodos abstratos
Superclasse Abstrata:
public abstract class Forma {
private int x; private int y;
public Forma(int x, int y){
this.x = x; this.y = y;
}
public void setX(int x){ this.x = x; }
public void setY(int y){ this.y = y; }
public int getX(){ return this.x; }
public int getY(){ return this.y; }
public String toString(){
return String.format("(%d, %d)", getX(), getY());
}
public abstract String getNome(); // método abstrato
}
46. Classes e métodos abstratos
public abstract class FormaBidimensional extends Forma{
private int dimensao1;
private int dimensao2;
public FormaBidimensional(int x, int y, int d1, int d2){
super(x, y);
dimensao1 = d1;
dimensao2 = d2;
}
public void setDimensao1(int d1){ dimensao1 = d1; }
public void setDimensao2(int d2){ dimensao2 = d2; }
public int getDimensao1(){ return dimensao1; }
public int getDimensao2(){ return dimensao2; }
public abstract int getArea(); // método abstrato
}
47. Classes e métodos abstratos
public class Circulo extends FormaBidimensional {
public Circulo(int x, int y, int raio){
super(x, y, raio, raio);
}
//método sobrecarregado de Forma
public String getNome(){ return "Círculo"; }
//método sobrecarregado de FormaBidimensional
public int getArea(){ return (int) (Math.PI*getRaio()*getRaio()); }
public int getRaio(){ return getDimensao1(); }
public void setRaio(int raio){ setDimensao1(raio); }
public String toString(){
return String.format("%s %s: %dn", super.toString(), "raio", getRaio());
}
}
48. Classes e métodos abstratos
public class Quadrado extends FormaBidimensional {
public Quadrado(int x, int y, int lado){
super(x, y, lado, lado);
}
//método sobrecarregado de Forma
public String getNome(){ return "Quadrado"; }
//método sobrecarregado de FormaBidimensional
public int getArea(){ return getLado()*getLado(); }
public int getLado(){ return getDimensao1(); }
public void setLado(int raio){
setDimensao1(raio);
setDimensao2(raio);
}
public String toString(){
return String.format("%s %s: %dn", super.toString(), "lado", getLado());
}
}
49. Classes e métodos abstratos
public class TesteForma {
public static void main(String args[ ]){
Forma formas[ ] = new Forma[3];
formas[0] = new Circulo(22,88,4);
formas[1] = new Quadrado(71,96,10);
formas[2] = new Circulo(8,89,2);
System.out.println();
for(int ind = 0; ind < formas.length; ind++){
System.out.printf("%s: %s",formas[ind].getNome(),formas[ind].toString());
FormaBidimensional forma2D = (FormaBidimensional) formas[ind];
System.out.printf("Área de %s é %sn", formas[ind].getNome(),
forma2D.getArea());
System.out.println();
}
}
}
50. Classes e métodos abstratos
Saída – exemplo:
Círculo: (22, 88) raio: 4
Área de Círculo é 50
Quadrado: (71, 96) lado: 10
Área de Quadrado é 100
Círculo: (8, 89) raio: 2
Área de Círculo é 12
52. Introdução
Programas anteriores estruturados como
métodos chamam uns aos outros de uma
maneira hierárquica e disciplinada.
Métodos recursivos:
◦ chamam a si mesmos,
◦ úteis para alguns problemas a fim de definir
uma chamada ao próprio método; e
◦ podem ser chamados direta ou indiretamente
por um outro método.
53. Conceitos de recursão
Elementos recursivos de solução de problemas:
◦ Caso básico:
Método recursivo só é capaz de resolver o caso mais simples — o caso
básico.
Se o método for chamado com o caso básico, o método retorna um
resultado.
◦ Se o método for chamado com um problema mais complexo, o
problema será dividido em duas partes — uma parte que o método
sabe o que fazer e uma outra que o método não sabe o que fazer
(denominada chamada recursiva ou passo de recursão).
◦ Chamada recursiva/passo de recursão:
Deve assemelhar-se ao problema original, porém ser um pouco mais simples
ou a menor versão.
O método chama uma cópia atualizada dele mesmo a fim de trabalhar em um
problema menor.
Normalmente, inclui uma instrução return
Recursão indireta:
◦ O método recursivo chama um outro método que, conseqüentemente,
faz uma chamada de volta ao método recursivo.
54. Exemplo que utiliza recursão:
Fatoriais
Fatorial de n, ou n! é o produto
◦ n · (n – 1) · (n – 2) · … · 1
◦ Com 1! igual a 1 e 0! definido como 1.
Pode ser resolvido recursiva ou iterativamente (não-
recursivamente).
Solução recursiva utiliza o relacionamento a seguir:
◦ n! = n · (n – 1)!
Recursão infinita – chamadas recursivas são feitas
continuamente até que a memória tenha sido exaurida.
◦ É causada omitindo o caso básico ou
escrevendo um passo de recursão que não
converge com o caso básico.
56. public class FactorialCalculator {
// método fatorial recursivo
public long factorial( long number ) {
if ( number <= 1 ) // testa caso básico
return 1; // casos básicos: 0! = 1 e 1! = 1
else // passo de recursão
return number * factorial( number ‐ 1 );
} // fim do método fatorial
// gera saída de fatoriais para valores 0‐10
public void displayFactorials() {
// calcula os fatoriais de 0 a 10
for ( int counter = 0; counter <= 10; counter++ )
System.out.printf( "%d! = %dn", counter, factorial( counter ) );
} // fim do método displayFactorials
} // fim da classe FactorialCalculator
57. public class FactorialCalculator {
// método fatorial recursivo
public long factorial( long number ) {
if ( number <= 1 ) // testa caso básico Caso básico retorna 1
return 1; // casos básicos: 0! = 1 e 1! = 1
Passo de recursão divide o problema em duas
else // passo de recursão
partes: com uma o método sabe como fazer;
return number * factorial( number ‐ 1 );
com outra, não
} // fim do método fatorial
// gera saída de fatoriais para valores 0‐10
Método de porção sabe o que fazer Chamada recursiva: O método portion não
public void displayFactorials() {
sabe como fazer; a menor versão do problema
// calcula os fatoriais de 0 a 10 original
for ( int counter = 0; counter <= 10; counter++ )
System.out.printf( "%d! = %dn", counter, factorial( counter ) );
} // fim do método displayFactorials
} // fim da classe FactorialCalculator
Chamada original ao método
recursivo
58. public class FactorialTest { // calcula fatoriais de 0‐10
public static void main( String args[] ) {
FactorialCalculator factorialCalculator = new FactorialCalculator();
factorialCalculator.displayFactorials();
} // fim de main
} // fim da classe FactorialTest Calcula e exibe fatoriais
Saída: Nota:
0! = 1 •Omitir o caso básico ou escrever o passo de
1! = 1 recursão incorretamente provoca erro!
2! = 2
3! = 6 •Se de alguma forma isso não convirja para o caso
4! = 24 básico pode causar um erro de lógica conhecido
5! = 120 como recursão infinita.
6! = 720 •Neste caso, as chamadas recursivas são feitas
7! = 5040 continuamente até acabar a memória.
8! = 40320
9! = 362880 •Isso é análogo ao problema de um loop infinito em
10! = 3628800 uma solução iterativa (não recursiva).
59. Exemplo que utiliza recursão: Série
de Fibonacci
A série de Fibonacci inicia com 0 e 1 e tem a propriedade de que
cada número de Fibonacci subseqüente é a soma dos dois números
de Fibonacci anteriores.
A série ocorre na natureza; a taxa de números de Fibonacci
sucessivos converge de acordo com a taxa ou a média áurea.
Fibonacci, série definida recursivamente como:
◦ fibonacci(0) = 0
◦ fibonacci(1) = 1
◦ fibonacci(n) = fibonacci(n – 1) + fibonacci(n – 2)
Solução recursiva para cálculo de valores de Fibonacci resulta na
explosão das chamadas de métodos recursivos.
60. public class FibonacciCalculator {
// declaração recursiva do método fibonacci Dois casos básicos
public long fibonacci( long number ) {
if ( ( number == 0 ) || ( number == 1 ) ) // casos básicos
return number;
else // passo de recursão
return fibonacci( number ‐ 1 ) + fibonacci( number ‐ 2 );
} // fim do método fibonacci
public void displayFibonacci() {
for ( int counter = 0; counter <= 10; counter++ ) Duas chamadas recursivas
System.out.printf( "Fibonacci of %d is: %dn", counter, fibonacci( counter ) );
} // fim do método displayFibonacci
} // fim da classe FibonacciCalculator
Chamada original ao método
recursivo
61. public class FibonacciTest {
public static void main( String args[] ) {
FibonacciCalculator fibonacciCalculator = new FibonacciCalculator();
fibonacciCalculator.displayFibonacci();
} // fim de main
} // fim da classe FibonacciTest Calcula e exibe os valores de
Fibonacci
Saída:
Fibonacci of 0 is: 0
Fibonacci of 1 is: 1
Nota:
Fibonacci of 2 is: 1
Fibonacci of 3 is: 2
Evite programas recursivos no
Fibonacci of 4 is: 3
estilo Fibonacci, porque resultam
Fibonacci of 5 is: 5
em uma ‘explosão’ exponencial de
Fibonacci of 6 is: 8
chamadas de método.
Fibonacci of 7 is: 13
Fibonacci of 8 is: 21
Fibonacci of 9 is: 34
Fibonacci of 10 is: 55
63. Recursão e a pilha de chamadas do
método
Pilha de chamadas de método utilizadas para monitorar
chamadas ao método e variáveis locais dentro de uma
chamada de método.
Assim como ocorre com a programação não-recursiva,
chamadas de métodos recursivos são colocadas na
parte superior da pilha das chamadas de método.
À medida que retornam as chamadas ao método
recursivo, seus registros de ativação são retirados da
pilha e as chamadas recursivas prévias continuam a
executar.
Método atual em execução sempre é o método cujo
registro de ativação está na parte superior da pilha.
66. Recursão versus iteração
Qualquer problema que possa ser resolvido de modo
recursivo também pode ser resolvido iterativamente.
Tanto a iteração como a recursão utilizam uma
instrução de controle.
◦ A iteração utiliza uma instrução de repetição.
◦ A recursão utiliza uma instrução de seleção.
Iteração e recursão envolvem um teste de terminação.
◦ A iteração termina quando a condição de
continuação do loop falha.
◦ A recursão termina quando um caso básico é
alcançado.
A recursão pode demandar muito tempo de
processador e espaço de memória, mas normalmente
fornece uma solução mais intuitiva.
67. Recursão versus iteração
Qualquer problema que pode ser resolvido de modo
recursivo também pode ser resolvido iterativamente
(não recursivamente).
Uma abordagem recursiva em geral é preferida
sobre uma abordagem iterativa quando a abordagem
recursiva espelha mais naturalmente o problema e
resulta em um programa mais fácil de entender e
depurar.
Uma abordagem recursiva pode ser freqüentemente
implementada com menos linhas de código. Outra
razão de escolher uma abordagem recursiva é que uma
iterativa talvez não seja aparente.
68. public class FactorialCalculator {
// declaração recursiva de método factorial
public long factorial( long number ) {
long result = 1;
// declaração iterativa de método factorial
for ( long i = number; i >= 1; i‐‐ )
result *= i;
return result; Solução iterativa utiliza a repetição
controlada por contador
} // fim do método factorial
// gera saída de fatoriais para valores 0‐10
public void displayFactorials() {
// calcula os fatoriais de 0 a 10
for ( int counter = 0; counter <= 10; counter++ )
System.out.printf( "%d! = %dn", counter, factorial( counter ) );
} // fim do método displayFactorials
} // fim da classe FactorialCalculator
69. public class FactorialTest {
// calcula fatoriais de 0‐10
public static void main( String args[] ) {
FactorialCalculator factorialCalculator = new FactorialCalculator();
factorialCalculator.displayFactorials();
} // fim de main
} // fim da classe FactorialTest
Saída: Nota:
0! = 1 Evite utilizar a recursão em situações que
1! = 1 requerem alto desempenho. Chamadas
2! = 2 recursivas levam tempo e consomem
3! = 6 memória adicional.
4! = 24
5! = 120 Ter acidentalmente um método não recursivo
6! = 720 chamando a si próprio seja direta ou
7! = 5040 indiretamente por outro método pode causar
8! = 40320 recursão infinita.
9! = 362880
10! = 3628800
70. Permutações de string
Permutações de uma string de texto – todas as
diferentes strings que podem ser criadas reorganizando
os caracteres da string original.
As palavras criadas a partir das permutações são
conhecidas como anagramas.
Solução recursiva: Remover um dos caracteres, localizar
permutações dos caracteres remanescentes (caso
recursivo), combina permutações com o caractere que
foi removido.
Caso básico: Localizar permutações para apenas um
caractere – o próprio caractere é a única permutação.
Qualquer string fornece n! permutações para n
caracteres.
71. public class Permutation {
// declaração recursiva do método permuteString
private void permuteString(String beginningString, String endingString ) { removidos
Caso básico: Combinar caracteres
(beginningString) com endingString, que é
// caso básico: se string a permutar tiver comprimento menor que ou igual
somente um caractere
// 1, exibe apenas essa string concatenada com beginningString
if ( endingString.length() <= 1 )
System.out.println( beginningString + endingString );
else { // passo de recursão: permuta endingString para cada caractere em endingString
Remove um caractere; localizaremos permutações
for ( int i = 0; i < endingString.length(); i++ ){ os caracteres remanescentes
para
try {
// cria nova string para permutar eliminando o caractere no índice i
String newString = endingString.substring( 0, i ) + endingString.substring( i + 1 );
// chamada recursiva com uma nova string a ser permutada
Chamada recursiva: localizar permutações para os caracteres
remanescentes e, então, reanexar os caracteres removidos
// e uma string inicial a ser concatenada, que inclui o caractere no índice i
permuteString( beginningString + endingString.charAt( i ), newString );
} catch ( StringIndexOutOfBoundsException exception ) {
exception.printStackTrace(); } // fim do catch
} // fim do for
} // fim do else
} // fim do método permuteString
} // fim da classe Permutation
72. public class PermutationTest {
public static void main( String args[] ) {
Saída:
Scanner scanner = new Scanner( System.in );
Permutation permutationObject = new Permutation(); math
maht
System.out.print( "Enter a string: " );
mtah
String input = scanner.nextLine(); // recupera String a permutar mtha
mhat
// permuta String
mhta
permutationObject.permuteString( "", input ); amth
amht
} // fim de main
atmh
} // fim da classe PermutationTest athm
ahmt
ahtm
tmah
Chamada inicial ao método recursivo; ainda não há tmha
caracteres removidos, assim o primeiro argumento é “” tamh
tahm
thma
tham
hmat
hmta
hamt
hatm
htma
htam
73. Torres de Hanói
Problema clássico: Sacerdotes no Extremo Oriente
estão tentando mover uma pilha de discos de um pino
para outro. Um dos discos deve ser movido em um
determinado momento; em nenhum momento um disco
maior pode ser posicionado acima de um disco menor.
Solução recursiva:
◦ Mova os n – 1 discos do pino 1 para o pino 2, utilizando o pino 3
como área de armazenamento temporário.
◦ Mova o último disco (o maior) do pino 1 para o pino 3.
◦ Mova os n – 1 discos do pino 2 para o pino 3, utilizando o pino 1
como área de armazenamento temporário.
Caso básico: Quando somente um disco precisa ser
movido, nenhuma área de armazenamento temporário
é necessária; o disco é simplesmente movido.
75. public class TowersOfHanoi {
int numDisks; // número de discos a serem movidos
public TowersOfHanoi( int disks ) {
numDisks = disks;
} // fim do construtor TowersOfHanoi
// move recursivamente os discos pelas torres
public void solveTowers( int disks, int sourcePeg, int destinationPeg, int tempPeg ) {
// caso básico – apenas um disco a ser movido
Caso básico: simplesmente exibe o movimento
if ( disks == 1 ) {
System.out.printf( "n%d ‐‐> %d", sourcePeg, destinationPeg );
return; compartimento 3 como a área de
Move n-1 discos do compartimento 1 Usa o
para o compartimento 2 armazenamento temporário
} // fim do if
// passo de recursão ‐‐ move o disco para tempPeg, e depois para destinationPeg
// move ( disks ‐ 1 ) discos de sourcePeg para tempPeg recursivamente
Move o último disco no compartimento 1 1, sourcePeg, tempPeg, destinationPeg );
solveTowers( disks ‐
para o compartimento 3
// move o último disco de sourcePeg para destinationPeg
Usa o compartimento 1 como a
área de armazenamento temporário
System.out.printf( "n%d ‐‐> %d", sourcePeg, destinationPeg );
Move n-1 discos do compartimento 2
// ‐ move ( disks ‐ 1 ) discos de tempPeg para destinationPeg
para o compartimento 3
solveTowers( disks ‐ 1, tempPeg, destinationPeg, sourcePeg );
} // fim do método solveTowers
} // fim da classe TowersOfHanoi
76. public class TowersOfHanoiTest {
public static void main( String args[] ) {
int startPeg = 1; // valor 1 utilizado para indicar startPeg na saída
int endPeg = 3; // valor 3 utilizado para indicar endPeg na saída
int tempPeg = 2; // valor 2 utilizado para indicar tempPeg na saída
int totalDisks = 3; // número de discos
TowersOfHanoi towersOfHanoi = new TowersOfHanoi( totalDisks );
// chamada não‐recursiva inicial: move todos os discos.
towersOfHanoi.solveTowers( totalDisks, startPeg, endPeg, tempPeg );
} // fim de main
} // fim da classe TowersOfHanoiTest
Faz a chamada inicial ao método
Saída: recursivo
1 ‐‐> 3
1 ‐‐> 2
3 ‐‐> 2
1 ‐‐> 3
2 ‐‐> 1
2 ‐‐> 3
1 ‐‐> 3
77. Fractais
Um fractal – uma figura geométrica que
freqüentemente pode ser gerada a partir de um
padrão repetido recursivamente por um número
infinito de vezes.
Padrão aplicado a cada segmento da figura original.
Benoit Mandelbrot introduziu o termo ‘fractal’,
juntamente com especificidades de como os fractais
são criados e de suas aplicações práticas.
◦ Ajuda a melhor entender os padrões na natureza,
o corpo humano e o universo.
◦ Forma de arte popular.
78. Fractais
Propriedade auto-semelhante – os fractais têm essa
propriedade na eventualidade de que, quando
subdivididos em partes, cada um se assemelhe a uma
cópia de tamanho reduzido do todo.
Se a parte for uma cópia exata do original, dizemos que
o fractal é estritamente auto-semelhante.
Toda vez que um padrão é aplicado, dizemos que o
fractal está em um novo nível ou profundidade.
Exemplos de fractais: Curva de Koch, Floco de neve de
Koch.
80. Retorno recursivo
Reversão recursiva – processo de utilização da recursão
para retornar ao ponto de decisão anterior.
Se um conjunto de chamadas recursivas não resultar em
uma solução, o programa volta ao ponto de decisão e
toma uma decisão diferente, resultando freqüentemente
em outro conjunto de chamadas recursivas.
Exemplos:
◦ Problema do labirinto.
◦ Problema das Oito Rainhas.