Aula 3 - Resoluções


Exercício 1

Alínea a)

Existem muitas soluções para este problema. Serão apresentadas duas. A primeira não recorre a quaisquer variáveis auxiliares nem afecta os valores da variáveis, limitando-se a considerar todos os casos possíveis. A segunda recorre a uma variável auxiliar de modo a simplificar os testes e afecta os valores das variáveis.

A segunda solução pode considerar-se melhor que a primeira porque:

  1. Ambas exigem entre duas e três comparações.
  2. A segunda não requer qualquer variável auxiliar.
  3. Na segunda não se "perde tempo" em atribuições.
{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito qual o maior e qual o mais pequeno de tres valores
    lidos da entrada.
}
maior_menor_1
inicio
    inteiro a, b, c, temp;

    escrever linha;

    escrever "Introduza tres valores: ";
    ler para a, b, c;

    (* No final "a" guardara' o menor dos valores e "b" guardara' o maior 
       dos valores. *)
 
    se a > b:
        (* Troca "a" com "b" de modo a que "a < b": *)
        temp <- a;
        a <- b;
        b <- temp;
    fim se;
    (* Neste instante e' garantido que a <= b. *)

    (* Se "c > b", entao "c" e' o maior e "a" o mais pequeno, caso contrario
       temos "c <= b", logo "c" so' "ameaca" "a" (dai a comparacao subsequente
       com "a"). *) 
    se c > b:
        b <- c;
    senao:
        se c < a:
            a <- c;
        fim se;
    fim se;

    escrever "Menor: ", a, linha;
    escrever "Maior: ", b, linha;
fim.
{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito qual o maior e qual o mais pequeno de tres valores
    lidos da entrada.
}
maior_menor_2
inicio
    inteiro a, b, c;

    escrever linha;

    escrever "Introduza tres valores: ";
    ler para a, b, c;

    se a >= b:
        se c >= a:
            (* c e' o maior e b e' o menor. *)
            escrever "Maior: ", c, linha, "Menor: ", b, linha;
        senao:
            (* a e' o maior. *)
            escrever "Maior: ", a, linha;
            se c <= b:
                (* c e' o menor. *)
                escrever "Menor: ", c, linha;
            senao:
                (* b e' o menor. *)
                escrever "Menor: ", b, linha;
            fim se;
        fim se;
    senao:
        se c >= b:
            (* c e' o maior e a e' o menor. *)
            escrever "Maior: ", c, linha, "Menor: ", a, linha;
        senao:
            (* b e' o maior. *)
            escrever "Maior: ", b, linha;
            se c <= a:
                (* c e' o menor. *)
                escrever "Menor: ", c, linha;
            senao:
                (* a e' o menor. *)
                escrever "Menor: ", a, linha;
            fim se;
        fim se;
    fim se;
 
fim.

Alínea b)

Neste caso procura-se o menor e o maior de vinte valores. Claro que seria possível declarar vinte variáveis e testar todas as possíveis combinações de maior e menor (20 x 19 = 380 !), mas o algoritmo tornar-se-ia demasiado extenso, quase incompreensível e, pior, se se pretendesse alterar de 20 para 30 o número de valores a comparar todo o algoritmo deveria ser alterado. Assim, a melhor solução neste caso é semelhante à pior solução na alínea anterior.

A ideia é ir actualizando duas variáveis, minimo e maximo à medida que novos valores são lidos, um pouco como se fez quando se somaram n valores [Aula 2, exercício 4.a)]. Com que valor devem ser inicializados minimo e maximo? Existem "elementos neutros" das comparações a fazer? Existem conceptualmente apenas: +infinito para o menor e -infinito para o maior. A solução então pode ser inicializar com ... o primeiro valor lido, como aliás se poderia ter feito no caso da soma.

A única desvantagem deste método é que não é possível saber o menor e o maior valor duma sequência de zero valores, mas isso provavelmente também não faria muito sentido.

{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito qual o maior e qual o mais pequeno de vinte valores
    lidos da entrada.
}
maior_menor_de_vinte
inicio
    inteiro valor, minimo, maximo;

    escrever linha;

    escrever "Introduzir 20 valores:", linha;

    (* Ler o primeiro valor: *)
    ler para valor;

    (* Inicializar "minimo" e "maximo" com o primeiro valor: *)
    minimo <- valor;
    maximo <- valor;

    (* Repetir 19 vezes (20-1) porque um dos valores ja' foi lido: *)
    repetir 19 vezes:
        ler para valor;
        se valor > maximo:
            maximo <- valor;
        senao:
            se valor < minimo:
                minimo <- valor;
            fim se;
        fim se;
    fim repetir;

    escrever "Maior: ", maximo, linha, "Menor: ", minimo, linha;
fim.

Alínea c)

Apresentam-se três alternativas, sucessivamente refinadas. Quais as vantagens e desvantagens relativas? Cumprirão todas o enunciado? Não! Em rigor só a primeira alternativa cumpre o enunciado (que pede explicitamente para serem lidos 20 valores). As alternativas 2 e 3 são apresentadas para exemplificar soluçãos típicas para problemas em que é lícito "curto-circuitar" o ciclo terminando-o prematuramente.

{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito se ocorreu o valor 7 alguma vez em 20 valores
    lidos da entrada.
}
ocorre_7_1
inicio
    inteiro valor;
    logico ocorreu_sete;

    escrever linha;

    escrever "Introduzir 20 valores:", linha;

    (* Inicializar "ocorreu_sete" com "falso": *)
    ocorreu_sete <- falso;

    repetir 20 vezes:
        ler para valor;
        se valor = 7:
            ocorreu_sete <- verdadeiro;
        fim se;
    fim repetir;

    escrever "O numero 7 ";
    se nao ocorreu_sete:
        escrever "nao ";
    escrever "ocorreu.", linha;
fim.
{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito se ocorreu o valor 7 alguma vez em 20 valores
    lidos da entrada.
}
ocorre_7_2
inicio
    inteiro valor, contador;
    logico ocorreu_sete;

    escrever linha;

    escrever "Introduzir 20 valores:", linha;

    (* Inicializar "contador" com zero: *)
    contador <- 0;

    (* Inicializar "ocorreu_sete" com "falso": *)
    ocorreu_sete <- falso;

    enquanto contador < 20 e nao ocorreu_sete:
        ler para valor;
        se valor = 7:
            ocorreu_sete <- verdadeiro;
        fim se;
        contador <- contador + 1;
    fim enquanto;

    escrever "O numero 7 ";
    se nao ocorreu_sete:
        escrever "nao ";
    escrever "ocorreu.", linha;
fim.
{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito se ocorreu o valor 7 alguma vez em 20 valores
    lidos da entrada.
}
ocorre_7_3
inicio
    inteiro valor, contador;

    escrever linha;

    escrever "Introduzir 20 valores:", linha;

    (* Inicializar "contador" com zero: *)
    contador <- 0;

    fazer:
        ler para valor;
        contador <- contador + 1;
    enquanto contador < 20 e valor  7;

    escrever "O numero 7 ";
    se valor  7:
        escrever "nao ";
    escrever "ocorreu.", linha;
fim.

Alínea d)

{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito quantas vezes ocorreu o valor 7 em 20 valores
    lidos da entrada.
}
quantos_7
inicio
    inteiro valor, quantos;

    escrever linha;

    escrever "Introduzir 20 valores:", linha;

    (* Inicializar "quantos" com o elemento neutro da adicao: *)
    quantos <- 0;

    repetir 20 vezes:
        ler para valor;
        se valor = 7:
            quantos <- quantos + 1;
        fim se;
    fim repetir;

    escrever "O numero 7 ocorreu ", quantos, "vezes.", linha;
fim.

Alínea e)

A solução deste problema é um pouco mais subtil que a solução da alínea b). Neste caso é necessário manter um contador de ocorrências e verificar se o valor lido é igual ao maximo corrente (se o for o número de ocorrências é incrementado).

{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito qual o maior de vinte valores lidos da entrada 
    e quantas vezes ocorreu.
}
maior_de_vinte_e_ocorrencias
inicio
    inteiro valor, maximo, contador;

    escrever linha;

    escrever "Introduzir 20 valores:", linha;

    (* Ler o primeiro valor: *)
    ler para valor;

    (* Inicializar "maximo" com o primeiro valor: *)
    maximo <- valor;
    (* Inicializar "contador" com um (porque?): *)
    contador <- 1;

    (* Repetir 19 vezes (20-1) porque um dos valores ja' foi lido: *)
    repetir 19 vezes:
        ler para valor;
        se valor > maximo:
            maximo <- valor;
            contador <- 1;
        senao:
            se valor = maximo:
                contador <- contador + 1;
            fim se;
        fim se;
    fim repetir;

    escrever "Maior: ", maximo, linha, "Ocorrencias: ", contador, linha;
fim.

Alínea f)

Neste caso a solução passa por utilizar uma acção cíclica enquando (note-se que há uma solução alternativa com fazer enquanto). Mas existe um problema: se o primeiro valor lido for negativo que fazer?

{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito qual o maior dos valores lidos da entrada
    ate' ao primeiro negativo (exclusive') e quantas vezes ocorreu.
}
maior_e_ocorrencias_ate_negativo
inicio
    inteiro valor, maximo, contador;

    escrever linha;

    escrever "Introduzir valores (terminar com negativo):", linha;

    (* Ler primeiro valor: *)
    ler para valor;

    (* Se o primeiro valor lido e' negativo, a sequencia esta' vazia: *)
    se valor < 0:
        escrever "Nao se leu nenhum valor!", linha;
    senao:
        (* Porque^ esta atribuicao? *)
        maximo <- valor;

	(* Contador e' zero porque dentro do enquanto, e antes de ser lido
	   novo valor, o contador sera' incrementado 
           (ja' que maximo = valor): *)
	contador <- 0;

	(* Enquanto "valor" nao for negativo... *)
	enquanto valor >= 0:
	    (* O valor e' o novo maximo? *)
	    se valor > maximo:
		maximo <- valor;
		contador <- 1;
	    senao:
		(* O valor e' de novo o maximo? *)
		se valor = maximo:
		    contador <- contador + 1;
		fim se;
	    fim se;

	    (* Ler proximo valor: *)
	    ler para valor;
	fim enquanto;

	escrever "Maior: ", maximo, linha, "Ocorrencias: ", contador, linha;
    fim se;
fim.

Exercício 2

Alínea a)

Notem-se os seguintes problemas:

  1. Que fazer quando o primeiro valor é zero (sequência vazia)?
  2. Que fazer quando o segundo valor é zero (um só valor na sequência)?
{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito se uma sequencia de valores lidos da entrada,
    ate' ao primeiro zero (exclusive'), esta' por ordem crescente.
}
sequencia_crescente
inicio
    inteiro valor, valor_anterior;

    escrever linha;

    escrever "Introduzir valores (terminar com zero):", linha;

    (* Ler primeiro valor: *)
    ler para valor;

    (* Inicializar valor_anterior com valor, pois nao "estraga" o teste
       de ordem... *)
    valor_anterior <- valor;
 
    (* Enquanto "valor" nao for zero... *)
    enquanto valor <> 0 e valor >= valor_anterior:
        valor_anterior <- valor;
        (* Ler proximo valor: *)
        ler para valor;
    fim enquanto;
    (*
     * Aqui so' se chega se:
     *  1. valor = 0 (e todos os valores (se e' que se leu algum!) estavam por
     *     ordem crescente!);
     *  2. valor  0 e valor < valor_anterior (encontrou-se um valor fora de
     *     ordem).
     * Por outro lado, se valor_anterior = 0 entao nao ouve valores lidos,
     * pois de outro modo valor_anterior teria sido atribuido de valor, 
     * sendo forcosamente nao nulo.
     *)

    se valor_anterior = 0:
        escrever "Nao se leu nenhum valor!", linha;
    senao:
        escrever "A sequencia ";
        se valor <> 0:
            escrever "nao ";
        fim se;
        escrever "estava por ordem crescente.", linha;
    fim se;
fim.

Note-se que mais uma vez esta solução viola o enunciado, porque pára no primeiro valor que viole a condição de sequência crescente! Como se poderia corrigir o algoritmo?

Alínea b)

{   (* Pressupostos: *)
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito se uma sequencia de valores lidos da entrada,
    ate' ao primeiro zero (exclusive'), e' mono'tona.
}
sequencia_mono'tona
inicio
    inteiro valor, valor_anterior;
    logico constante, crescente;

    escrever linha;

    escrever "Introduzir valores (terminar com zero):", linha;

    (* Inicializar "constante" ("crescente" nao precisa de ser inicializado
       porque ainda nao tem significado!): *)
    constante <- verdadeiro;

    (* Ler primeiro valor: *)
    ler para valor;

    (* Inicializar valor_anterior com valor, pois nao "estraga" o teste
       de ordem... *)
    valor_anterior <- valor;
 
    (* Enquanto "valor" nao for zero e for coerente com o estado... *)
    enquanto valor <> 0 e 
             (constante ou
              (valor >= valor_anterior e crescente) ou
              (valor <= valor_anterior e nao crescente)):
        se constante:
            se valor > valor_anterior:
                constante <- falso;
                crescente <- verdadeiro;
            senao:
                se valor < valor_anterior:
                    constante <- falso;
                    crescente <- falso;
                fim se;
            fim se;
        fim se;
            
        valor_anterior <- valor;
        (* Ler proximo valor: *)
        ler para valor;
    fim enquanto;
    (*
     * Aqui so' se chega se:
     *  1. valor = 0 (e todos os valores (se e' que se leu algum!) estavam por
     *     ordem mono'tona!);
     *  2. valor  0 e nao constante e nao (valor >= valor_anterior e 
     *     crescente) e nao (valor <= valor_anterior e nao crescente), ou seja
     *     se se encontrou um valor fora de ordem.
     * Por outro lado, se valor_anterior = 0 entao nao ouve valores lidos,
     * pois de outro modo valor_anterior teria sido atribuido de valor, 
     * sendo forcosamente nao nulo.
     *)

    se valor_anterior = 0:
        escrever "Nao se leu nenhum valor!", linha;
    senao:
        escrever "A sequencia ";
        se valor <> 0:
            escrever "nao ";
        fim se;
        escrever "e' monotona.", linha;
        se valor = 0:
            se constante:
                escrever "E' constante.", linha;
            senao:
                se crescente:
                    escrever "E' crescente.", linha;
                senao:
                    escrever "E' decrescente.", linha;
                fim se;
            fim se;
        fim se;
    fim se;
fim.

Note-se que mais uma vez esta solução viola o enunciado, porque pára no primeiro valor que viole a condição de sequência monótona! Como se poderia corrigir o algoritmo?


Exercício 3

Alínea a)

O problema fundamental a resolver é saber se um dado valor n é ou não primo. O método mais simples - e menos eficiente! - para resolver este problema é tentar dividir n por todos os valores de 2 a n - 1. Se o resto da divisão for nulo alguma vez, o número não é primo. Se o resto nunca for nulo, o número é primo:

{   (* Pressupostos: *)
    n > 2
}
{   (* Resultado a atingir: *)
    Deve aparecer escrita a seque^ncia dos primos de 2 a n, sendo n lido da 
    entrada.
}
primeiros_primos
inicio
    inteiro n, valor, divisor;

    escrever linha;

    escrever "Primos ate': ";

    ler para n;

    (* E' escusado testar o 2, que sabemos ser primo... *)
    escrever 2, linha;

    para valor <- 3, n:
        divisor <- 2;
        enquanto divisor < valor e valor mod divisor  0:
            divisor <- divisor + 1;
        fim enquanto;
        se divisor = valor:
            escrever valor, linha;
        fim se;
    fim para;
fim.

Para além do método sugerido no texto, muitos outros há para acelerar a verificação de se um número é primo. Por exemplo, é claro que se n não é divisível por 2 também não o é por nenhum valor maior que n div 2. Aliás, em geral basta testar com divisores d tais que d^2 <= n!

Se existir algum mecanismo de guardar os números primos conhecidos, então ainda é possível tornar mais eficiente o algoritmo se se verificar que basta testar se n é divisível pelos primos d até d^2 <= n!

Alínea b)

A solução seguinte é a mais óbvia, e porventura a menos eficiente:

{   (* Pressupostos: *)
    a, b > 0
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito o mdc de a e b.
}
mdc_1
inicio
    inteiro a, b, divisor;

    escrever linha;

    escrever "Mdc de: ";

    ler para a, b;

    (* Como o mdc nao pode ser maior que o menor dos valores "a" e "b"... *)
    se a < b:
        divisor <- a;
    senao:
        divisor <- b;
    fim se;

    (* Testar divisores desde o menor de "a" e "b" ate' 1: *)
    enquanto divisor <> 1 e (a mod divisor <> 0 ou b mod divisor <> 0):
        divisor <- divisor - 1;
    fim enquanto;

    escrever divisor, linha;
fim.

Alínea c)

Demonstração

É fácil provar que se m é o mdc de a e b (ambos positivos), m também é o mdc de b e a mod b. Note-se que a demonstração abaixo é válida mesmo quando a mod b = 0, embora nesse caso o mdc seja obviamente b.

Seja m o mdc de a e b. Então, existem c e d tais que:

a = m c
b = m d

Seja r = a mod b e q = a div b. Então a = b q + r.

Ou seja:

m c = m d q + r.

Donde:

r = m (c - d q)

Assim, provou-se que r = a mod b é divisível por m.

Falta apenas provar que m é não só divisor de b e r = a mod b, mas também o maior dos divisores de b e r.

Suponha-se que existe n > m divisor de b e r. Então existem x e y tais que:

b = n x
r = n y

Mas:

q n x + n y = q b + r = a

Por outro lado:

q n x + n y = n (q x + y)

Logo:

a = n (q x + y)

Ou seja, n não só divide b como também divide a! Logo m não é o mdc de a e b, o que é absurdo. Assim, não existe nenhum divisor de b e r maior do que m, ou seja m é o mdc de b e r.

Algoritmo

Dados a e b, duas coisas podem acontecer:

  1. a mod b = 0 - neste caso o mdc é o próprio b.
  2. a mod b = d 0 - neste caso o mdc de a e b é o mesmo que o de b e d, tendo-se assim reduzido o problema, pois d é menor que a e b.

Note-se que se a < b então a mod b é a, mas b mod a é menor que a! Assim, há vantagem em calcular o resto da divisão inteira do valor maior pelo valor menor!

{   (* Pressupostos: *)
    a, b > 0
}
{   (* Resultado a atingir: *)
    Deve aparecer escrito o mdc de a e b.
}
mdc_2 
(* Algoritmo de Euclides. *)
inicio
    inteiro a, b, temp;

    escrever linha;

    escrever "Mdc de: ";

    ler para a, b;

    enquanto b <> 0;
        temp <- b;
        b <- a mod b;
        a <- temp;
        (* Ou seja, calculou-se "d = a mod b", e ficou "b" com o valor de "d"
           e "a" com o valor de "b". Desta forma, a proxima divisao faz-se pelo
           valor mais baixo ("d")! *)
    fim enquanto;

    (* Aqui "b" e' sempre zero, e "a" tem o valor antigo de "b", o tal que 
       conduziu ao resto zero, ou seja, o mdc! *) 
    escrever a, linha;
fim.

Faça o traçado com 123 (3 x 41) e 321 (3 x 107), cujo mdc é 3, e com 89 (89) e 144 (2^4 x 3^2), mdc 1, para se convencer de que o algoritmo funciona...


Página concebida e mantida por Eng. Manuel Menezes de Sequeira (última actualização 2006/07/07)
Copyright © 1996-2001 ISCTE