Você está na página 1de 22

3a Lista de Exerccios e algumas notas

Estrutura de Dados I
Recursividade simples
Entrega no dia .... (veja no Blackboard)
JM Josko, LCD Gonalves
6 de abril de 2016

NO APOSTILA.
NO SUBSTITUI A LEITURA DE LIVROS.
NO DESOBRIGA AO ESTUDO DE LIVROS.
APENAS UMA LISTA DE EXERCCIOS!
Apenas contm um quick start para comear a codificar, e tenta cobrir alguns detalhes omitidos nos livros.

SUMRIO

SUMRIO

Sumrio
1 Regras para a entrega

2 Algumas notas sobre recurso

3 Implementao de mtodos recursivos simples


3.1 Recurso mtua ou indireta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Problemas com a recorrncia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.1 A pilha da mquina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.2 Caso base omitido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.3 O(s) caso(s) base existe(m) mas a sequncia de chamadas recursivas no converge para
ele(s). Ou, o(s) caso(s) base existe(m) mas a sequncia pode no convergir para ele(s).
3.2.4 Muito espao na rea de pilha requerido . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.5 Nmero de reclculos excessivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5
6
7
8
9
9
11
11

4 Exercises diversos (no a lista) com arrays e strings


4.1 Com arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Com strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14
14
16

5 Exercises (a lista)
5.1 Recurso simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 Recurso em arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.3 Recurso em Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21
21
21
21

SUMRIO

1 Regras para a entrega

1 Regras para a entrega


1. Os exerccios que no necessitam de rodar numa mquina:
so manuscritos (feitos mo).
devem vir com o enunciado e com a numerao que aparece na lista (no precisa ser manuscrito);
devem estar na mesma sequncia dos enunciados na lista.
a resposta deve vir aps cada um dos enunciados.
exerccios com alternativas: devem ser entregues com a resoluo/justificativa e no apenas com a alternativa marcada.
podem ser resolvidos com o mtodo a sua escolha, exceto quando indicado ao contrrio. Escolha sempre
o mtodo mais simples tal que responda pergunta.
devem estar todos num mesmo pdf: a digitalizao pode ser feita com celular ou assemelhado. O resultado final deve ser legvel.
devem conter o nome dos integrantes na primeira pgina de resoluo (escrevam os nomes antes de
digitalizar).
2. Os exerccios que necessitam de rodar numa mquina (cdigos):
tipos, mtodos estticos de uso geral no relativos ao tipo e cdigos de teste em arquivos separados.
tipos, abstratos ou no, em um nico .java, sem main()s.
mtodos estticos, em um nico .java, sem main()s.
cdigos de teste, apenas o main(),
Testes de diversos mtodos podem estar em um nico main(), mencionando qual o exerccio: E1.22,
E1.44, etc. Coloque um comentrio antes da chamada do mtodo.
todo o .java deve conter os nomes completos e RGMs dos integrantes
os cdigos devem estar indentados (nem leio caso no estejam)
3. Todos os exerccios
entregar apenas os no resolvidos
devem ter os nomes completos e RGMs dos integrantes dos grupos num arquivo .txt separado na raiz do
zip. Destaque o menor RGM;
Devem estar contidos em um nico zip: 1 pdf para os exercicios manuscritos, alm dos restantes
4. Regras gerais de orientao
grupos de 2 a 4 integrantes
tamanho mximo do zip entregue = 3MBytes.
Listas iguais, sem nenhum capricho, com resolues que s quem fez entende (se entende), que subvertem
alguma das regras: 0. Bom portugus nas justificativas.
um grupo mantm os mesmos integrantes durante todo o curso. O integrante de um grupo pertence
apenas quele grupo. No caso de integrantes promscuos, 0 para os grupos (que so tambm promscuos
e portanto) envolvidos.

Para esta lista, entregar


Seja n o nmero do integrante com o menor RGM do grupo (incluindo-se dgitos de controle e afins
se for o caso).
Se n for par, entregar apenas os exerccios pares das Sees 5.2 e 5.3 (Ex: E5.32 um exercicio
par)
Se n for mpar, entregar apenas os exerccios mpares das Sees 5.2 e 5.3 (Ex: E5.11 um exercicio
mpar)

SUMRIO

2 Algumas notas sobre recurso

2 Algumas notas sobre recurso


A recurso1 no somente uma ideia matemtica elegante mas algo que faz inclusive parte da nossa linguagem. Recorrer significa definir algo parcialmente em termos desse algo, e considerada uma tcnica
matemtica poderosa, especialmente utilizada em demonstraes por induo.
Os exemplos mais simples e familiares consistem de definies de funes matemticas2 . Por exemplo,
E. 2.1 Fatorial: n! = n (n 1) (n 2) (n 3) . . . 1
{
1
,n = 0
n! =
n (n 1)! , n > 0
E. 2.2 Potncia positiva (n 0) de x: xn = x x x . . . x (o x aparece n vezes) ou 1 se n = 0.
{
1
,n = 0
xn =
x xn1 , n > 0

(2.3)

E. 2.4 Sequncia de Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, . . .
{
1
, n = 0 ou n = 1
fibn =
fibn1 + fibn2 , n > 1
E. 2.5 Srie harmnica: h(n) = 1 + 1/2 + 1/3 + . . . + 1/n
{
0
,n 0
h(n) =
1
h(n 1) + n , n > 0
A abordagem tpica da recurso consiste na reaplicao da frmula at que o valor conhecido da frmula
seja encontrado. Por exemplo, para o fatorial
3! = 3 2! = 3 2 1! = 3 2 1 0! = 3 2 1 1
e para a sequncia de Fibonacci
fib3 = fib2 + fib1 = fib2 + 1 = fib1 + fib0 + 1 = 1 + 0 + 1 = 2
1
Sobre as diferenas entre recurso e recorrncia: A raiz latina da palavra recurso recursio, o que significa o ato ou processo
de retornar para trs ou de voltar para trs. Recorrncia vem de recurrere (Recurro, Recurrere, Recurri, Recursus) (de re- (para
trs, novamente) + curro (correr). recursio um substantivo e recorrer um verbo (que pode ser substantivado por recorrncia).
Muitos dizem que recorrncia, recursividade e recurso so coisas diferentes, computacionalmente falando, enquanto significam
exatamente o mesmo. Recurso o substantivo para recorrer, em portugus. Atravs do Google (dei uma checada certa vez, phaz
tempo) recurrence trees fornece cerca de 1k resultados (inclusive vindo de sites de Cornell e de outras universidades americanas)
e recursion trees fornece cerca de 7k resultados, mostrando uma preferncia pela palavra recurso no caso da representao de
relaes (recorrentes) por rvores. recursion relations fornece cerca de 73k resultados e recurrence relations cerca de 250k
resultados, mostrando agora a preferncia pela palavra recorrncia. Enfim, no h diferena.
2
Tambm conhecidas por frmulas. A palavra funo incorreta mas culturalmente utilizada. Funes, no sentido estrito,
so conjuntos de uplas ordenadas que podem ser estabelecidas atravs de uma frmula.

SUMRIO

3 Implementao de mtodos recursivos simples

e assim por diante. Em todas as definies anteriores, o objeto frmula define seu prprio clculo pela
atravs da reduo do argumento em 1 (ou em 1 e 2, como o caso da sequncia de Fibonacci). Ou seja, o
problema de tamanho n reduzido para subproblemas de tamanho n-1 (ou etcs. Fibonacci).
Colocado de forma simplista, um mtodo recursivo quando no corpo de sua definio existe uma ou
mais chamadas a ele mesmo. Todo algoritmo recursivo (ou uma frmula recursiva) tem duas partes:
Solues Triviais (ou os casos base, ou as condies de parada da recurso): so dadas por definio,
isto , no necessitam de recurso para serem obtidas. So os critrios de parada da recurso. Quando
no definidos de forma adequada, acarretam em estouros de pilha numa implementao ou mesmo
deixam o programa em loop eterno.
Solues Recursivas (ou passos de reduo ou partes recursivas do algoritmo): so as partes do
problema que recorrem a sua prpria definio. Em geral: desejvel que o problema original seja
reduzido a problemas menores e em nmero pequeno. Os argumentos usados como controle tem
que convergir para as solues triviais. Para os exemplos anteriores, o argumento n decrementado
a cada chamada.
Em geral, a recorrncia pode comparecer num mtodo R tambm de forma mtua ou indireta. Na forma
indireta, R chama mtodos Ci , onde pelo menos 1 deles volta a chamar R.

3 Implementao de mtodos recursivos simples


As implementaes mais simples de mtodos recorrentes so aquelas obtidas pela simples transcrio da
formulao matemtica. Observe tambm que geralmente os casos base sempre comparecem no incio do
mtodo para esses casos mais simples. De modo geral, os casos base podem estar distribudos no corpo do
mtodo sendo que cada um deles marca o incio de uma restrio incrementalmente maior do domnio dos
argumentos.
E. 3.1 Potncia xn : Dada a relao de recorrncia 2.3, considerando-se x e n inteiros positivos ou nulos3 , o
mtodo praticamente escreve-se sozinho, bastando apenas transcrever a relao de recorrncia
int pow(int x, int n) {
if (n == 0) return 1;
return x * pow(x, n - 1);
}

Nesse exemplo, o caso base n = 0 e a convergncia para ele assegurada j que a chamada recorrente
incumbe-se de decrementar o argumento de controle n. Claro que se n < 0 quando da primeira chamada, o mtodo entra em loop que encerra com um estouro de pilha de argumentos atravs da exceo
StackOverflowError. Nos cdigos apresentados, geralmente evita-se a escrita de elses aps os returns
dos casos base4 de modo a que o cdigo no fique poludo.

3
Mais precisamente, 00 no 1 e sim uma indeterminao. Para o exerccio, a definio fornecida fica simples o bastante, embora
incorreta.
4
O que deve ser considerada uma m prtica de acordo com o manual dos gurus das boas prticas.

SUMRIO

3.1

Recurso mtua ou indireta

E. 3.2 Algoritmo de Euclides: Dada a relao de recorrncia, considerando-se m e n inteiros positivos ou


nulos,
{
m
,n = 0
mdc(m, n) =
mdc(n, m mod n) , caso contrrio
novamente basta transcrever a relao de recorrncia
int mdc(int m, int n) {
if (n == 0) return m;
return mdc(n, m % n);
}

O caso base n = 0. Quanto convergncia, ela obtida pela anlise de m % n. m % n converge para 0, que
o novo argumento n passado. Por maiores que sejam dois nmeros m e n, por exemplo 1047 e 38, o resto
da diviso sempre menor que o menor deles. No exemplo, mdc(1047, 38)= mdc(38, 1047%38)= ....
Etcs., pode ser provado que o algoritmo O(log d), onde d o nmero de dgitos do menor nmero (coloco
a prova na prxima verso da lista).

3.1 Recurso mtua ou indireta


Recurso mtua ou indireta uma forma de recorrncia onde dois objetos definem suas caractersticas em
termos um do outro.
E. 3.3 O exemplo clssico de recurso mtua a definio mtua de nmero par e mpar, utilizando-se
apenas subtraes.
Def. 3.4 Nmero par: Se n vale 0 ento par. Caso no, n par se n - 1 for mpar.

Def. 3.5 Nmero mpar: Se n vale 1 ento mpar. Caso no, n mpar se n - 1 for par.

Estabelecemos as relaes de recorrncia, tomando o cuidado de nos certificarmos que convergem para o
caso base.
{
{
verdadeiro
,n = 0
verdadeiro , n = 1
par(n) =
mpar(n) =
mpar(n 1) , caso contrrio
par(n 1) , caso contrrio
A codificao imediata

SUMRIO

3.2 Problemas com a recorrncia

public class MutualRecursionDemo01 {


private MutualRecursionDemo01 () { }
public static boolean isEven(int n) {
return n == 0 ? true : isOdd(n - 1);
}
public static boolean isOdd(int n) {
return n == 1 ? true : isEven(n 1);
}
public static void main(String [] args) {
System.out.println(isEven (3));
}
}

Em certos casos possvel que recorrncias mtuas possam ser eliminadas fazendo-se a substituio direta
de uma relao recorrente na outra. Ou simplesmente repensar-se os casos base. No ltimo caso, por
exemplo,

public class MutualRecursionRemovedDemo01 {


public class MutualRecursionRemovedDemo01 {
private MutualRecursionRemovedDemo01 () { }
private MutualRecursionRemovedDemo01 () { }
public static boolean isEven(int n) {
if (n == 0) return true;
if (n == 1) return false;
return isEven(n - 2);
}

public static boolean isEven(int n) {


if (n == 0) return true;
return !isEven(n - 1);
}

ou

public static boolean isOdd(int n) {


if (n == 1) return true;
if (n == 0) return false;
return isOdd(n - 2);
}

public static boolean isOdd(int n) {


if (n == 1) return true;
return !isOdd(n - 1);
}
public static void main(String [] args) {
System.out.println(isEven (3));
}

public static void main(String [] args) {


System.out.println(isEven (3));
}

no caso de 2 casos base serem utilizados ou apenas 1.


Enfim.

3.2 Problemas com a recorrncia


Mini-FAQ: Podemos pensar nas seguintes questes pertinentes:
Q: A recurso deixar meu cdigo mais rpido?
A: No.

SUMRIO

3.2 Problemas com a recorrncia

Q: Ento por que utilizar recurso?


A: Algumas vezes os cdigos ficam bem mais simples! Em outras vezes, meio impensvel partida
resolver um problema sem usar recurso.
Q: Essa impensabilidade vista neste texto?
A: No. Todos os cdigos deste texto so inteis. So apenas um meio para aprender-se os detalhes
bsicos (eventualmente alguns mais finos) sobre recurso.
Em geral, mtodos recursivos pecam em termos de eficincia mas conseguem deixar o cdigo compacto,
claro, limpo e com as intenes de codificao explcitas.
Embora tenham todas essas virtudes, diversos problemas em sua codificao so frequentes: o tempo
de execuo, tamanho necessrio de rea de pilha e possveis reclculos desnecessrios. Em relao aos
reclculos, sendo o tempo de execuo funo do nmero de chamadas no cdigo, no caso da sequncia
de Fibonacci necessrio um tempo de clculo ligeiramente menor que, e da ordem de, O(2n ), ou seja,
exponencial.
Vejamos algumas consideraes.
3.2.1 A pilha da mquina
Ao nvel da mquina, existem apenas processos iterativos para a carga e execuo de instrues de mHeap
quina. Todas as chamadas de mtodos so imple(Objetos)
mentadas utilizando-se uma pilha, tambm denominada de pilha de ativao. A pilha mantm o registro do endereo de retorno, argumentos, possiMemria
No
velmente das flags e registradores da CPU e das vavariveis locais
utilizada
riveis locais do ponto de onde foi chamado o margumentos
Registro
todo. A toda essa estrutura complexa da chamada
mtodo n
estado da CPU
BP
(flags,
...
de um mtodo denomina-se de registro de ativao
registradores)
Pilha
endereo de
ou, simplesmente, de ativao. De modo bem transde ativao
Registro
retorno (PC)
mtodo 2
parente, toda a chamada de um mtodo resulta na
topo anterior
PC
(apontador base,
Programa
Registro
carga de um registro de ativao na pilha; e todo
BP)
mtodo 1
valor de
o retorno de um mtodo resulta na remoo do reretorno
OS/VM
gistro do topo da pilha, o qual possui o endereo
no qual o cdigo deve resumir a sua execuo. Em
baixo nvel (assembly), CALLs fazem com que a CPU Figura 1: Esquema da pilha de ativao utilizada pelos
empilhe o endereo de retorno (PC); e RETs com mtodos
que haja o desempilhamento e a execuo do cdigo resume-se no PC desempilhado.
Quanto de memria ocupa um registro de ativao? Depende obviamente da quantidade de variveis
locais ao mtodo que efetua a chamada, por exemplo. Para todos os fins prticos, diremos apenas que uma
chamada requer espao O(1) de pilha. A estrutura de um registro de ativao tambm dependente da
mquina e do compilador. Uma estrutura tpica de registros de ativao mostrada na Fig. 1
Segundo essa vizualizao, a ideia de recursividade til apenas para o programador. Um mtodo
8

SUMRIO

3.2 Problemas com a recorrncia

recursivo, como qualquer outro, utiliza essa abordagem baseada em pilhas e em ativaes, sendo que, em
particular, uma ativao para o mesmo mtodo colocada na pilha; para a mquina, entretanto, isso no faz
nenhuma diferena. A nica diferena notria entre iterao e recurso ocorre quando os casos base de um
mtodo recursivo no so atingidos e haver a gravao de uma dada quantidade de registros de ativao
at que a rea de pilha estoure; em contrapartida, para um mtodo iterativo a situao anloga leva a um
looping infinito que apenas interrompido com a interveno do usurio. Em suma, um mtodo recorrente
tem que ser escrito de forma a convirja para os casos base (e que isso ocorra bem antes de um estouro); e
mtodos iterativos devem ter os critrios de finalizao dos loops bem definidos.
3.2.2 Caso base omitido
No exemplo abaixo, o caso base no foi adicionado. Em princpio, o mtodo deveria computar nmeros de
uma srie harmnica
public class HarmonicDemo01 {
private HarmonicDemo01 () { }
public static double harmonic(int n) {
return harmonic(n 1) + 1.0 / n;
}
public static void main(String [] args) {
System.out.println(harmonic (3));
}
}

A partir da chamada em main(), o mtodo encarrega-se de decrementar n para a prxima chamada a


harmonic() mas nunca um caso base atingido (porque no foi obviamente includo no cdigo). As diversas chamadas recorrentes so anotadas na pilha do Java e, em determinada altura, uma exceo de estouro
de pilha, StackOverflowError, emitida.
3.2.3 O(s) caso(s) base existe(m) mas a sequncia de chamadas recursivas no converge para ele(s). Ou,
o(s) caso(s) base existe(m) mas a sequncia pode no convergir para ele(s).
Esse dois problemas tem como sintoma um estouro de pilha, embora a causa possa ser um problema com
a definio deficiente dos casos base ou com as redues.
Por exemplo,

SUMRIO

3.2 Problemas com a recorrncia

public class HarmonicDemo02 {


private HarmonicDemo02 () { }
public static double harmonic(int n) {
if (n == 1) return 1.0;
return harmonic(n) + 1.0 / n;
}
public static void main(String [] args) {
System.out.println(harmonic (3));
}
}

harmonic(3) chamada, harmonic() tem o caso base mas nunca n reduzido na chamada recorrente.
Nesse caso, harmonic() chamada recorrentemente com n = 3 at que um estouro de pilha acontea.
Mesmo um caso base bem formado no evita que haja estouro de pilha. Por exemplo, o cdigo abaixo
public class FactorialDemo01 {
private FactorialDemo01 () { }
public static double fact(int n) {
if (n == 1) return 1.0;
return n * fact(n - 1);
}
public static void main(String [] args) {
System.out.println(fact (0));
}
}

est bem escrito e determina o fatorial para qualquer nmero acima de n = 1. Entretanto, a sequncia de
chamadas recursivas para 0! nunca atinge o caso base. Mesmo sintoma (estouro de pilha), causa diferente:
a falta de verificao do domnio dos argumentos, ou seja, a falta do prcondicionamento. Para sanar-se
isso, cria-se um segundo mtodo (denominado de helper ou auxiliar) e coloca-se esse mtodo disposio
do usurio: o mtodo checa a prcondio e o mtodo recorrente chamado apenas se os argumentos
estiverem dentro do domnio. Ficaria
public class FactorialDemo01 {
private FactorialDemo01 () { }
public static double fact(int n) {
if (n < 1)
throw new IllegalArgumentException("Fatoriais apenas para n >= 1.0");
return _fact(n);
}
private static double _fact(int n) {
if (n == 1) return 1.0;
return n * fact(n - 1);
}
}

10

SUMRIO

3.2 Problemas com a recorrncia

Obviamente private s faz sentido se os mtodos _fact() e fact() estiverem escritos em uma classe separada. Sendo assim, o usurio ficaria restrito a utilizar fact().
3.2.4 Muito espao na rea de pilha requerido
Tudo OK agora, casos base e passos de reduo. Agora o usurio requer que seja calculado harmonic(5000)
. Quando um mtodo chamado carregado na rea de pilha um registro: a informao necessria para o
prximo mtodo, outras informaes e para onde o mtodo chamado deve retornar. A cada chamada um
registro adicionado na pilha, a cada retorno um registro removido da pilha. No caso de um mtodo
recorrente, se a profundidade da recorrncia grande (depende da rea inicial de pilha do Java. No caso,
algo aproximadamente 5000 ou maior), para o cdigo abaixo
...
public static double harmonic(int n) {
if (n == 1) return 1.0;
return harmonic(n - 1) + 1.0 / n;
}
...

um estouro de pilha ocorrer tambm! Note que harmonic(5000) chama harmonic(4999) que chama
que chama harmonic(2) que chama harmonic(1). So 4999 chamadas recorrentes antes do primeiro retorno. na rea de pilha haver cerca de 5000 registros (das chamadas recorrentes mais o da chamada
harmonic(5000). Mesma causa, sintoma diferente e novamente um estouro de pilha: desta vez poor espao insuficiente (o mesmo StackOverflowError).
Como orientao prtica, um mtodo que resolve um problema de tamanho n no pode usar O(n) de
espao de pilha.
3.2.5

Nmero de reclculos excessivos

A utilizao de um mtodo recorrente torna-se proibitiva quando existem reclculos ou redundncias de


operaes envolvidas. No caso mais simples, reclculos so efetuados. Um dos mtodos campees de
reclculos, tipicamente ilustrado como um uso de recorrncia (e s vezes no dito que um mau uso) o
clculo dos valores da sequncia de Fibonacci.
A implementao dessa ideia recursiva, um primor da ineficincia, pode ser realizada pelo cdigo abaixo
A sequncia de chamadas funo fib() foi esquematizada pela rvore esquerda do cdigo.
Interprete-se como fib(6) chama fib(5) e fib(4); fib(5) chama fib(4) e fib(3), e assim por diante.
Note que o clculo de fib(6) envolve fib(5) e fib(4). A rvore de fib(4) repetida 2 vezes. Por outro
lado, a rvore de fib(2) est repetida 5 vezes! Esses so os reclculos mencionados.
Vale salientar que o esforo computacional para o clculo de fib(n) o mesmo que o esforo de fib(n
-1) somado quele de fib(n-2). Note tambm que
2 fib(n 1) > fib(n) = fib(n 1) + fib(n 2) > 2 fib(n 2)

11

SUMRIO

3.2 Problemas com a recorrncia

public class FibonacciDemo01 {


f ib(6)
f ib(5)
f ib(4)
f ib(3)
f ib(2)
f ib(1)

f ib(3)
f ib(2)

f ib(1)

f ib(1)

f ib(0)

private FibonacciDemo01 () { }

f ib(4)

f ib(2)
f ib(1)

f ib(0)

f ib(3)
f ib(1)

f ib(2)
f ib(1)

public static long fib(int n) {


if (n == 0 || n == 1) return 1;
return fib(n - 1) + fib(n - 2);
}

f ib(2)
f ib(1)

f ib(1)

f ib(0)

f ib(0)

public static void main(String [] args) {


System.out.println(fib (6));
}

f ib(0)

Figura 2: Representao grfica das chamadas recursivas feitas por fib(6)


ou seja, fib(n) cresce exponencialmente com razo5 menor que 2 (o esforo para o clculo da sequncia
tambm!).
Na prtica, o nmero de chamadas recorrentes pode ser contado atravs do nmero de fib()s na rvore
(na rvore, leia-se fib() ao invs de fib(), responsvel por 24 chamadas recorrentes. O nmero de vezes
que fib(1) e fib(0) so chamados 13, exatamente o nmero fib(6); ou seja, fib(n) requer fib(n)
somas de 1. Ou seja, o nmero de vezes que 1 somado cresce exponencialmente com a prpria razo da
sequncia de Fibonacci!
O tempo necessrio para o clculo recorrente da sequncia de Fibonacci, em relao a uma verso iterativa, pode ser estimado ao executar-se o seguinte cdigo. Deve ser suficiente para demonstrar o efeito do
reclculo excessivo.
5

Cresce com n , sendo a famosa razo urea = (1 +

5)/2 1.62. Detalhes em mathworld.wolfram.com ou no wiki

12

SUMRIO

3.2 Problemas com a recorrncia

public class FibonacciDemo02 {


public static long iterativeFib(int n){
long r = 1, res = 1;
for (int i = 1; i <= n; i++) {
long q = r;
r = res;
res = r + q;
}
return res;
}
public static long recursiveFib(int n) {
return n == 0 || n == 1 ? 1 : recursiveFib(n -1) + recursiveFib(n - 2);
}
public static void main(String [] args) {
// Verso iterativa
for (int i = 0; i <= 60; i++)
System.out.println(i + " " + iterativeFib(i));
// Verso recursiva
for (int i = 0; i <= 60; i++)
System.out.println(i + " " + recursiveFib(i));
}
}

Os reclculos podem obviamente ser evitados memorizando-se os valores anteriores j calculados. Eventualemnte uma melhoria intil, j que o mtodo iterativo continuaria sendo mais rpido.

13

SUMRIO

4 Exercises diversos (no a lista) com arrays e strings

4 Exercises diversos (no a lista) com arrays e strings


Uma das partes mais difceis da escrita de um mtodo recorrente que envolva arrays a escrita da frase que
descreve o mtodo. Para ilustrar passemos escrita do selectionSort() recorrente.

4.1 Com arrays


E. 4.1 [*] selectionSort() recursivo: No um bom exemplo de uso de recorrncia: um exerccio apenas.
A formulao do problema de ordenar um array recorrentemente tendo como base o Selection Sort foi j
passada em aula. Como seria feita a formulao do problema? Qual a frase inicial difcil de ser escrita?
Considerando uma lista (de floats, por exemplo) list[] de tamanho n e desordenada, busca-se o
menor elemento do array entre start e end (que delimitam a parte desordenada) e o trocamos com o
elemento em start. Procede-se ento ordenao do array entre start + 1 e end.
Ou seja, a maioria dos problemas que envolvem ordenao de arrays ou operaes em arrays contm o
ndice inicial onde a operao deve ser realizada e o ndice final. Apesar de no serem sempre recorrentes,
exceto talvez o quickSort(), diversos mtodos utilitrios de java.lang.String e de java.util.Arrays usam dois
indices para delimitar a faixa de operao do mtodo. No caso particular, o mtodo teria como cabealho
void selectionSort(float [] list , int start , int end)

e start e end sero os argumentos de controle da recorrncia. Note que de pouco ou nada serve list.length.
Teramos portanto, sem o caso base
void selectionSort(float [] list , int start , int end) {
// Falta o caso base
...
// Busca pelo menor entre start e end
int imin = start;
for (int i = start; i <= end; ++i)
if (list[i] < list[imin ])
imin = i;
// Troca de list[imin] por list[start]
float tmp
= list[start ];
list[start] = list[imin ];
list[imin] = tmp;
// Procede ao selection sort entre start + 1 e end
selectionSort(list , start + 1, end);
}

ou utilizam-se os mtodos j passados em sala de aula

14

SUMRIO

4.1 Com arrays

void selectionSort(float [] list , int start , int end) {


// Falta o caso base
...
// Busca pelo menor entre start e end
int imin = findMin(list , start , end);
// Troca de list[imin] por list[start]
swap(list , start , imin);
// Procede ao selection sort entre start + 1 e end
selectionSort(list , start + 1, end);
}
void swap(float [] list , int i, int j) {
float tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
int findMin(float [] list , int start , int end) {
int imin = start;
for (int i = start; i <= end; ++i)
if (list[i] < list[imin ])
imin = i;
return imin;
}

Quem o caso base? Note que somado 1 a start e que aparentemente start convergir at end. Na
prtica, no necessrio ordenar um elemento (ou seja, quando start == end) e da forma-se o caso base
void selectionSort(float [] list , int start , int end) {
// Caso base
if (start == end)
return;
// Busca pelo menor entre start e end
...
}

Para a resoluo dos exerccios estaria bom at aqui. Note que entretanto o mtodo recorrente no trata
dos casos de list[] ser um null passado ou mesmo que start > end e outras situaes para o tratamento
de usurios distrados e chamadas de outros mtodos. Um tapa adicional pode ser efetuado e o cdigo
pode ficar mais profissional como
public void selectionSort(float [] list , int start , int end) {
// Prcondies
if (list == null || start < 0 || end < 0 || start >= list.length
|| end >= list.length)
return;
_selectionSort (list , start , end);
}
private void _selectionSort(float [] list , int start , int end) {
// Caso base
if (start >= end)
return;
// Busca pelo menor entre start e end
...
}

15

SUMRIO

4.2 Com strings

onde o mtodo selectionSort() o mtodo disponibilizado para o usurio e faz a verificao da prcondio. Escolheu-se nada fazer no caso de
list == null || start < 0 || end < 0 || start >= list.length || end >= list.length

mas poderia ser outra coisa. Deve ser especificada essa escolha no manual ou feita de acordo com o
especificado). No caso de estar tudo OK, _selectionSort() chamado. Note que o caso base foi mudado
para start >= end para nada fazer se start > end.

4.2 Com strings


Tabelinha de mtodos de Strings. Cortezia da Oracle.
char

charAt(int index)

int
String

length()
substring(int beginIndex)

String

substring(int beginIndex, int endIndex)

Retorna o caracter da posio index da String.


IndexOutOfBoundsException se o ndice invlido (negativo ou length())
Retorna o comprimento da String.
Retorna uma string entre a posio beginIndex(inclusive) at o ltimo caracter.
Se beginIndex for igual a length(), retorna uma string vazia.
IndexOutOfBoundsException se beginIndex invlido (negativo ou > length
())
Exemplos:
"unhappy".substring(2) retorna "happy"
"Harbison".substring(3) retorna "bison"
"emptiness".substring(9) retorna ""
Retorna uma substring da posio beginIndex(inclusive) at endIndex
(exclusive)
IndexOutOfBoundsException se beginIndex < 0, ou endIndex > length(),
ou beginIndex > endIndex
Exemplos:
"hamburger".substring(4, 8) retorna "urge"
"smiles".substring(1, 5) retorna "mile"

E. 4.1 [null] Escreva um mtodo recursivo


int countChar(String s, char ch)
que conta quantos caracteres ch existem na String s. Retorna 0 caso s = null ou s a String vazia.
R:Se s tem tamanho 0 ou s = null, retorna 0. Caso contrrio, retorna 1 se na posio 0 da String tem o ch
+ a contagem de caracteres a partir da posio 1 (countChar(s.substring(1), ch)) . Ou, retorna 0 se na
posio 0 da string no tem o ch + a contagem de caracteres a partir da posio 1
public static int countChar(String s, char ch) {
if (s == null || s.length () == 0 )
return 0;
if (s.charAt (0) == ch)
return 1 + countChar(s.substring (1), ch);
return countChar(s.substring (1), ch);
}

Como o argumento s no converge para null, pode-se remov-lo do processo recursivo. Fica, dando aquele
capricho,
16

SUMRIO

4.2 Com strings

public static int countChar(String s, char ch) {


if (s == null)
return 0;
return _countChar(s, ch);
}
private static int countChar(String s, char ch) {
if (s.length () == 0 )
return 0;
if (s.charAt (0) == ch)
return 1 + countChar(s.substring (1), ch);
return countChar(s.substring (1), ch);
}

E. 4.2 [*] Um palndromo6 uma frase ou palavra que igual se lida da esquerda para a esquerda ou da
esquerda para a direita. Exemplos so "radar", "able was I ere I saw elba" e a prpria string vazia
"". Escreva um mtodo recursivo que determine se uma String um palndromo ou no.
R:Mais um makin off de exercise. Nos exemplos fornecidos existem no-letras. Inicialmente escrevamos
um mtodo que decide se um caracter uma letra e, por simplicidade, apenas minsculas no acentuadas
private static boolean isLetter(char ch) {
return 'a' <= ch && ch <= 'z';
}

Tudo feito na raa. Existe o mtodo isLetter() em java.lang.Character, caso lhe interesse.
Para escrever o mtodo recursivo existem diversas formas: pode-se usar substring()s ou no. Faamos
a verso no: o mtodo ir requerer como argumentos de controle um ndice para o final da String e outro
para o comeo; e mais um helper adicional. Despejo a ideia inicial no cdigo
boolean isPalindrome(String s) {
if (s == null) return true;
return isPalindrome(s, 0, s.length () - 1);
}
boolean isPalindrome(String s, int f, int t) {
... // Caso base
if (! isLetter(s.charAt(f)))
return isPalindrome(s, f+1, t );
if (! isLetter(s.charAt(t)))
return isPalindrome(s, f , t-1);
// Temos em f e em t dois caracteres
...
}
6

Tem um monto no wiki: O Gal. Leno Roca, porta da cidade, a portador relata fatal erro da tropa e d dica da tropa a
Coronel Lago, Reverta verbo, O vivo breve , Sabe bem am-lo o Lama, Me beba se verbo vivo, O breve atrever., O saco no
nosso nono caso, Leben Sie mit Siegreits Rune. Deine Zier sei dies. Reize nie den Urstiergeist im Eisnebel, Sumitis a vetitis;
sitit is, sitit Eva, sitimus, rsrsr.

17

SUMRIO

4.2 Com strings

onde foi assumido que null um palndromo (o que poderia ser false caso o enunciado pedisse. Ou no.).
Caso os caracteres s.charAt(t) e s.charAt(f) sejam iguais, verifica-se a String que vai de f+1 at t-1.
Caso no, no um palndromo. Assim
boolean isPalindrome(String s) {
if (s == null) return true;
return isPalindrome(s, 0, s.length () - 1);
}
boolean isPalindrome(String s, int f, int t) {
... // Caso base
if (! isLetter(s.charAt(f)))
return isPalindrome(s, f+1, t );
if (! isLetter(s.charAt(t)))
return isPalindrome(s, f , t-1);
// Temos em f e em t duas letras
if (s.charAt(t) == s.charAt(f))
return isPalindrome(s, f+1, t-1);
return false;
}

Para chegar-se nesse ltimo teste necessrio que f >= t. E sendo assim a chamada far com que f < t
no caso de f == t. o caso base! Note que naturalmente a String vazia contemplada como palndromo,
sem nunca essa condio ter sido forada/considerada! Finalmente
public static boolean isPalindrome(String s) {
if (s == null) return true;
return isPalindrome(s, 0, s.length () - 1);
}
private static boolean isPalindrome(String s, int f, int t) {
if (f > t)
// Caso base
return true;
// Tratamento dos casos de "no letra "
if (! isLetter(s.charAt(f)))
return isPalindrome(s, f+1, t );
if (! isLetter(s.charAt(t)))
return isPalindrome(s, f , t-1);
// Temos em f e em t duas letras
if (s.charAt(t) == s.charAt(f))
return isPalindrome(s, f+1, t-1);
return false;
// Caso base: s. charAt (t) != s. charAt (f)
}

** FIM DO EXERCISE ** mas como a gente gosta de programar (sic), vamos ilustrar algumas dificuldades e
sutilezas da implementao da coisa iterativa. Praticamente transcrevendo-se da recursiva

18

SUMRIO

4.2 Com strings

public static boolean isPalindrome(String s) {


if (s == null) return true;
return isPalindrome(s, 0, s.length () - 1);
}
private static boolean isPalindrome(String s, int f, int t) {
while (f <= t) {
// Tratamento dos casos de "no letra"
if (! isLetter(s.charAt(f))) {
f++;
continue ;
}
if (! isLetter(s.charAt(t))) {
t--;
continue ;
}
// Temos em f e em t duas letras
if (s.charAt(t) != s.charAt(f))
return false;
// Checam -se as prximas duas letras
f++; t--;
}
return true;
}

a qual tambm um bom exemplo do porqu da existncia de continues7 .


O algoritmo iterativo anterior peca um pouco pela eficincia: p.e. para s=",.;mariaairam. . .", !
isLetter(s.charAt(f)) inicialmente avaliada 3 vezes; em seguida, a condio similar sobre t verificada
4 vezes e tambm sobre f! Um polimento posterior no cdigo trata esse problema
boolean isPalindrome(String s, int f, int t) {
while (f <= t) {
// Tratamento dos casos de "no letra"
while (f <= t && !isLetter(s.charAt(f))) f++;
if (f > t) break;
while (f <= t && !isLetter(s.charAt(t))) t--;
if (f > t) break;
// Temos em f e em t duas letras
if (s.charAt(t) != s.charAt(f))
return false;
// Checam -se as prximas duas letras
f++; t--;
}
return true;
}

e agora a condio no while externo desnecessria pois repete-se no primeiro while mais interno. Mais
um passe, tambm substituindo-se o while externo por for e f++; t-- no corpo do for
7

Novamente, tal uso deve estar condenado no manual de boas prticas.

19

SUMRIO

4.2 Com strings

boolean isPalindrome(String s, int f, int t) {


for ( ; ; f++, t--) {
// Varredura em f at o final da String , pulando no letras
while (f <= t && !isLetter(s.charAt(f))) f++;
if (f > t) break;
// Varredura em t at o incio da String , pulando no letras
while (f <= t && !isLetter(s.charAt(t))) t--;
if (f > t) break;
// Temos em f e em t duas letras
if (s.charAt(t) != s.charAt(f))
return false;
}
return true;
}

Mais um tapa: remove-se o primeiro if, delegando-se essa verificao para o segundo (obviamente se f > t
aps o primeiro while, f <= t falso para o segundo while e o cdigo interrompido em seguida)
private static boolean isPalindrome(String s, int f, int t) {
for ( ; ; f++, t--) {
// Varredura em f at o final da String , pulando no letras
while (f <= t && !isLetter(s.charAt(f))) f++;
// Varredura em t at o incio da String , pulando no letras
while (f <= t && !isLetter(s.charAt(t))) t--;
if (f > t) break;
// Temos em f e em t duas letras
if (s.charAt(t) != s.charAt(f))
return false;
}
return true;
}

e paramos por aqui antes que sobre apenas uma linha de cdigo. Eventualmente o break pode ser substitudo por return true e o return true; do final pode ser removido, caso o compilador no reclame.

20

SUMRIO

5 Exercises (a lista)

5 Exercises (a lista)

5.2 Recurso em arrays

5.1 Recurso simples

Para os exercises abaixo, com a exceo do primeiro,


no usar loops.

Para os exercises abaixo, no usar loops.


E. 5.1 (CCPS 109, Recursion Exercises)[null]
creva o mtodo recorrente

E. 5.5 Escreva um mtodo que ordene o array de


Es- modo recorrente com base no algoritmo Insertion
Sort.

void listNumbers(int from, int to)

E. 5.6 (CCPS 109, Recursion Exercises)[null]


Escreva
o
mtodo
recorrente
que mostra na consola os nmeros de from a to. Escreva uma verso que escreve em ordem crescente e
int min(int[] a, int from, int to)
outra em ordem decrescente.

que retorna o menor valor do array a entre os ndices


E. 5.2 (CCPS 109, Recursion Exercises)[null]
Es- from e end.

creva o mtodo recorrente


int mul(int a, int b)

E. 5.7 [null] Escreva o mtodo recorrente

que calcula a multiplicao de dois inteiros positivos boolean linearSearch(int[] a, int from, int
to, int x)
a e b. As nicas operaes aritmticas que podem ser
utilizadas so a soma e a subtrao.
que faz a busca linear entre as posies from e to do
array a pelo valor x.

E. 5.3 (CCPS 109, Recursion Exercises)[null]


Escreva o mtodo recorrente
int sumOfDigits(int n)

5.3 Recurso em Strings

Para os exercises de String abaixo pode-se usar apeque calcula a soma dos dgitos do inteiro positivo n. nas os mtodos charAt(), length() e substring(),
Por exemplo, para o argumento n = 12345, o mtodo alm da concatenao. Sem loops.
retorna 15.

Sugesto genrica: Pense sempre que a string vazia pode ser um caso base. Obviamente se a prpria
E. 5.4 (UFOP) Considere um sistema numrico que String for um argumento de controle.
no tenha a operao de adio implementada e que
voc disponha somente dois operadores (mtodos): E. 5.8 [null] Escreva um mtodo recursivo
sucessor e predecessor. meio bvio dizer isso mas
void printString(s)
o sucessor e o predecessor seriam operaes que somam 1 e subtraem 1, respectivamente.
que imprime uma String recursivamente, caracter a
Ento, pede-se para escrever uma mtodo recur- caracter.

sivo que calcule a soma de dois nmeros x e y atravs


desses dois operadores: sucessor e predecessor. Su- E. 5.9 (CCPS 109, Recursion Exercises)[*] Escreva o
gesto meio bvia: Escreva int succ(int n) e int mtodo recorrente
pred(int n) e em seguida o mtodo pedido que
contm apenas chamadas a esses dois (alm das b- String mySubstring(String s, int from, int
to)
vias a ele mesmo).

21

5.3 Recurso em Strings

SUMRIO

que funciona de modo similar ao mtodo substring


da classe String. Apenas usar os mtodos charAt(),
a concatenao + e length().

E. 5.10 [null] Escreva um mtodo recorrente


voidprintStringReverse(String s)
que imprime uma String recursivamente e ao contrrio, caracter a caracter. Apenas usar os mtodos
charAt(), a concatenao + e length().

E. 5.11 (CCPS 109, Recursion Exercises)[null]


creva o mtodo

Es-

String repeat(String s, int n)


que retorna uma string montada a partir de n cpias
da String s passada pelo argumento. Por exemplo,
repeat("Hello", 3) retorna "HelloHelloHello".
Caso n 0, o mtodo retorna a string vazia "".

E. 5.12 (CCPS 109, Recursion Exercises)[null]


creva o mtodo recorrente

Es-

String disemvowel(String s)
que retorna uma string formada pela String s passada com as vogais removidas. Escreva, para auxiliar
o mtodo,
boolean isVowel(char ch)
que indica se ch uma vogal. Faa os algoritmos assumindo apenas vogais no acentuadas.

E. 5.13 [null] Escreva um mtodo recursivo


String rotateRight(String s, int n)
que roda a String s direita n vezes. Por exemplo,
caso rotateRight("Maria", 3) retorna "riaMa".
E. 5.14 (CCPS 109, Recursion Exercises)[null]
creva o mtodo recursivo

Es-

int binToDec(s)
que retorna a representao na base 10 de uma String
que contm com um nmero binrio, caracter a caracter. Por exemplo, binToDec("101011") retorna
43. Retorna 0 no caso de String vazia ou nula.

22