Beruflich Dokumente
Kultur Dokumente
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
5
6
7
8
9
9
11
11
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
SUMRIO
(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
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.
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
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).
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
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,
ou
SUMRIO
SUMRIO
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));
}
}
SUMRIO
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
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
11
SUMRIO
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)
f ib(2)
f ib(1)
f ib(1)
f ib(0)
f ib(0)
f ib(0)
12
SUMRIO
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
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);
}
14
SUMRIO
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
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.
charAt(int index)
int
String
length()
substring(int beginIndex)
String
Como o argumento s no converge para null, pode-se remov-lo do processo recursivo. Fica, dando aquele
capricho,
16
SUMRIO
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
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
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
19
SUMRIO
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)
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.
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.
21
SUMRIO
Es-
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.
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