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); }