Semana 3

Propaganda
IP, Resoluções comentadas, Semana 3
jrg, vs 002, Out-2012
a) Defina uma função que recebe como argumento um número natural n e devolve o número de
divisores desse número.
Para resolver o problema temos que verificar cada um dos números entre 1 e n; sempre que um destes
números for divisor de n, conta-se. Arranjamos uma variável, i, para percorrer os valores de 1 a n-1 e
uma variável, divs, para contar os duvisores encontrados.
public static int divisores ( int n ) {
int ndivs = 0, i = 1;
while ( i <= n ) {
if ( n % i == 0 )
ndivs = ndivs + 1;
i = i + 1;
}
return ndivs;
}
É importante conseguir discernir algumas coisas no meio do programa, Para começar o contador i, que
percorre os números de 1 a n-1. Na listagem seguinte salientam-se as instruções que têm a ver com
esta variável.
public static int divisores ( int n ) {
int ndivs = 0, i = 1;
while ( i <= n ) {
// neste ponto vemos passar i=1, 2, ... até n
if ( n % i == 0 )
ndivs = ndivs + 1;
i = i + 1;
}
return ndivs;
}
É o costume: inicialização (i=1); incremento (i =i+1), a última operação dentro do ciclo while; e o valor
final ( i <= n ). Quando ao ndivs:
public static int divisores ( int n ) {
int ndivs = 0, i = 1;
while ( i <= n ) {
// neste ponto vemos passar i=1, 2, ... até n
if ( n % i == 0 ) {
//só passamos neste ponto se i for par
ndivs = ndivs + 1;
}
i = i + 1;
}
return ndivs;
}
que começa em 0 e conta, c=c+1, no ponto onde se verifica a condição.
b) Defina uma função que recebe como argumento um número natural n e devolve o somatório dos
seus divisores próprios.
É basicamente o mesmo que o problema anterior, só que agora, em vez de contar, somamos os
divisores. A listagem salienta o acumulador que faz a soma:
public static int sumDivisores ( int n ) {
int soma = 0, i = 1;
while ( i < n ) {
if ( n % i == 0 )
soma= soma + i;
i = i + 1;
}
return soma;
}
c) Defina uma função que recebe como argumento um número natural n e que devolve o número
de números perfeitos até n. Um número perfeito é tal que a soma dos seus divisores próprios é
igual ao próprio número.
Também não é muito diferente dos programas anteriores. A variável i faz um percurso de 1 até n.
Quando passar por um i que seja perfeito conta.
public static int perfectNumbersUpTo( int n) {
int i=1, c = 0;
while ( i <= n ) {
if ( i == sumDivisores(i) )
c = c + 1;
i = i + 1;
}
return c;
}
A única novidade é ver se i é perfeito. Para isso usa-se a condição:
i == sumDividores (i )
ou seja se i for igual á soma dos seus divisores (valor este dado pela função sumDivisores feita na alínea
anterior).
d) Defina uma função que recebe como argumento um número natural n e devolve verdadeiro caso
n seja primo, ou falso caso contrário.
public static boolean isPrime ( int n ) {
int i = 2;
while ( i < n ) {
if ( n % i == 0 )
return false;
i = i + 1;
}
return true;
}
É praticamente igual ao anterior. A variável i percorre os números de 2 a n-1. Se um desses números
for divisor de n concluímos que o número não é primo (return false). Se passarmos o ciclo todo ( ie,
experimentarmos todos) sem aparecer nenhum divisor concluímos que é primo (return true).
A listagem seguinte salienta estes pontos:
public static boolean isPrime ( int n ) {
int i = 2;
while ( i < n ) {
if ( n % i == 0 ) {
// cá está um divisor => não é primo!
return false;
}
i = i + 1;
}
// já fizemos as divisões todas e não apareceu nenhum divisor
// (se tivesse aparecido nem tínhamos chegado aqui)
// logo => é primo
return true;
}
Esta outra versão é um bocado mais rebuscada:
public static boolean isPrime ( int n ) {
int i = 2;
boolean primo = true;
while ( i < n ) {
if ( n % i == 0 ) {
primo = false;
}
i = i + 1;
}
return primo;
}
A variável "primo" funciona como uma flag que assinala se passámos alguma vez (ou seja, uma vez ou
mais) pelo ponto primo=false.
Esta versão é talvez um bocadito menos eficiente. Mesmo que se detecte que o número não é primo a
função continua até ao fim do ciclo (a anterior termina logo, com o return). A versão seguinte retoma
estas preocupações de eficiência:
public static boolean isPrime ( int n ) {
int i = 2;
boolean primo = true;
while ( i < n/2 && primo ) {
if ( n % i == 0 ) {
primo = false;
}
i = i + 1;
}
return primo;
}
O while termina quando acontecer uma de duas coisas:
- i chegar ao fim do seu curso
- ou primo passar a false (aparecer um divisor).
Rebuscado mesmo seria uma versão recursiva, como esta:
public static boolean ePrimo2 ( int n ) {
return !temDivisores ( n, 2);
}
public static
if ( d
if ( n
return
}
boolean temDivisores ( int n, int d ) {
== n ) return false;
% d == 0 ) return true;
temDivisores ( n, d + 1);
e) Defina uma função que recebe como argumento um número natural n e devolve a soma dos
números primos menores que n).
public static int sumPrimesUpTo ( int n ) {
int soma = 0, i = 1;
while ( i <= n ) {
if ( isPrime(i) ) {
soma = soma + i;
}
i = i + 1;
}
return soma;
}
f) Defina uma função que recebe como argumento um número natural n e devolve o número de
primos até n (inclusive).
public static int countPrimesUpTo ( int n ) {
int c = 0, i = 1;
while ( i <= n ) {
if ( isPrime(i) ) {
c = c + 1;
}
i = i + 1;
}
return c;
}
g) Defina uma função que permite saber se existe algum número primo entre determinado
intervalo (aberto).
public static int existsPrimeBetween ( int a, int b ) {
int n = countPrimesUpTo(b -1) - countPrimesUpTo(a);
return n > 0;
}
h) Defina uma função recursiva para calcular o n-ésimo número de Fibonacci.
A implementação recursiva neste caso é praticamente transpor a definição
fibonacci(0) = 0
fibonacci(0) = 1
fibonacci(n) = fibonacci( n - 1) + fibonacci( n - 2)
public static
if ( n
if ( n
return
}
int fibonacci
== 0 ) return
== 1 ) return
fibonacci ( n
( int n ) {
0;
1;
- 1) + fibonacci ( n -2 );
Mas é importante perceber bem o esquema. Por exemplo, responder à pergunta: se for pedido o
fibonnaci de 4 quantas vezes será executada a função ? O programa seguinte ajuda a clarificar esta
questão:
class Programa {
public static void main ( String[] args ) {
fibonacci( 4 );
}
public static int fibonacci ( int n ) {
System.out.println ("Pedido fibonacci de " + n);
if ( n == 0 ) return 0;
if ( n == 1 ) return 1;
return fibonacci ( n - 1) + fibonacci ( n -2 );
}
}
i) Defina uma função recursiva para calcular o factorial de um número.
Este tipo de problemas é fácil e imediato quando estamos perante uma definição recursiva. È o caso do
factorial que se pode definir da seguinte forma:
factorial(0) = 1
factorial(n) = n * factorial ( n -1).
public static int factorial ( int n )
if ( n == 0 ) return 1;
return n * factorial ( n -1 );
}
{
Outros problemas poderiam ser tratados da mesma forma. Por exemplo o problema da alínea f) da
semana 2 pode ser definido da mesma forma.
somaN(1) = 1;
somaN(n) = n + S(n-1)
public static int somaN ( int n )
if ( n == 1 ) return 1;
return n + somaN ( n -1 );
}
{
Outro exemplo, o problema da alínea e) da semana 2 pode ser definido assim
potencia2(0) = 1
potencia2(n) = 2 * potencia2(n-1);
public static int potencia2 ( int n )
if ( n == 0 ) return 1;
return 2 * potenciaN ( n -1 );
}
{
Mas não é sempre assim tão simples. Muitos problemas não aparecem assim. Mas se quisermos muito,
podemos ir à procura da recursividade, mesmo que ela não seja evidente ou prática.
Por exemplo, um ciclo normal pode ser tratado de forma recursiva. Por exemplo o mesmo problema f)
da semana 2 que já falámos e para o qual adoptámos a seguinte solução iterativa:
public static void somaN( int n
int i=1;
int soma = 0;
while ( i <= n) {
soma = soma + i;
i = i + 1;
}
return soma;
}
) {
Visto assim, não há grande potencial para uma solução recursiva. Mas, podemos forçar:
public static void somaN( int n
) {
return somaN ( n, 1, 0);
}
public static void somaN( int n, int i, int s
) {
if ( i > n ) return s;
return somaN ( n, i + 1, s + i );
}
j) Defina uma função recursiva para calcular o máximo divisor comum entre dois números.
public static int gdc ( int x, int y ) {
if ( y == 0) return x;
return gdc( y, x % y);
}
k) Defina uma função que recebe como argumento um natural n e devolve a maior diferença entre
dois números primos consecutivos até n.
Primeiro uma versão iterativa:
public static int
largerDifferenceBetweenPrimes ( int a) {
int primoAnterior = 2;
int maiorDif = 0;
int i = 0;
while ( i <= a ) {
if ( isPrime(i) ) {
if ( i - primoAnterior > maiorDif)
maiorDif = i - primoAnterior;
primoAnterior = i;
}
i = i + 1;
}
return maiorDif;
}
Ficam duas coisas de memória: o "primo anterior" e a "maior diferença anterior". Cada vez que
passamos por um novo primo i, vemos a diferença e esse i passa a ser o "primo anterior". Quando à
diferença se for maior do que a "maior diferença anterior" passa a ser a nova " maior diferença
anterior".
Uma solução recursiva seria, neste caso, algo rebuscada. Por exemplo:
public static int
largerDifferenceBetweenPrimesR ( int a) {
return largerDifferenceBetweenPrimesR ( a, 2, 1, 0);
}
public static int
largerDifferenceBetweenPrimesR ( int a, int i, int pa, int md) {
if ( i > a) return md;
if ( isPrime(i) && i - pa > md ) md = i - pa;
if ( isPrime(i) ) pa = i;
return largerDifferenceBetweenPrimesR ( a, i+1 , pa, md);
}
Download