Processing math: 100%

Algoritmos fundamentais; Recursão; Tipos básicos;

Algoritmos

O que é um algoritmo?

Testar números primos

Números primos

Um número inteiro positivo n é primo se for divisível apenas por 1 e por n:


2, 3, 5, 7, 11, 13 …


Vamos especificar um algoritmo para testar se um número é primo.

Algoritmo

Dado: n inteiro.

Se n1 então não é primo e terminamos imediatamente.

Se n>1 tentamos para d=2,3,,n1:

Se chegamos ao final sem encontrar um divisor: concluimos que n é primo.

É um algoritmo

Implementação

#define FALSE 0
#define TRUE  1

/* Testar se um número inteiro é primo */

int primo(int n) {
   int d;
   if(n <= 1) return FALSE;
   for (d = 2; d < n; d++) {
     if (n%d == 0)  // d divide n?
       return FALSE;
    }
   return TRUE;
}

Observações

Implementação (2)

/* Testar se um número é primo; 
   versão mais eficiente */

int primo(int n)
{
   int d;
   if(n <= 1) return FALSE;
   for (d = 2; d*d <= n; d++) {
     if (n%d == 0)   // d divide n
       return FALSE;
    }
   return TRUE;
}

Máximo divisor comum

Exemplo

O máximo divisor comum (mdc) de dois inteiros a,b é o maior número inteiro que divide a e b.


Exemplo:

252=21×12105=21×5

Algoritmo de Euclides

Dados: a,b dois números inteiros positivos.

  1. se a=b então terminamos; o mdc é a e b (são iguais).
  2. se a>b então:
    aab e repetimos o passo 1.
  3. se a<b então:
    bba e repetimos o passo 1.

Exemplo de execução

Calcular o mdc de 252 e 105.

iter a b
0 252 105
1 147 105
2 42 105
3 42 63
4 42 21
5 21 21

R: 21

Implementação

/* Calcular o mdc de dois inteiros positivos
   pelo Algoritmo de Euclides (1ª versão)
   */
int mdc(int a, int b)
{
   while (a != b) {
     if(a > b)
         a = a - b;
      else
         b = b - a;
   }
   return a; // a, b são iguais
}

Será um algoritmo?

OK:

Questões:

  1. Correção: porque obtemos o mdc no final?
  2. Terminação: será que o ciclo termina sempre?

Correção

Propriedade da divisão inteira:

Se d divide a e b, então d divide ab e d divide ba.

Terminação

Em cada iteração:

se a>b:(a,b)(ab,b)se a<b:(a,b)(a,ba)

Observações

Algoritmo de Euclides (2)

(Versão usando resto da divisão.)


Dados: a,b dois inteiros não-negativos.

  1. se b=0 então terminamos; o mdc é a.
  2. caso contrário:

Exemplo

Calcular o mdc de 252 e 105.

iter a b resto a ÷ b
0 252 105 42
1 105 42 21
2 42 21 0
3 21 0

R: 21

Implementação

/* Calcular o mdc de dois inteiros usando
   o algoritmo de Euclides (2ª versão)
   */
int mdc(int a, int b)
{
    int r;
    while(b != 0) {
        r = a%b;
        a = b;
        b = r;
    }
    return a;
}

Recursão

Definições recursivas

fact(0)=1fact(n)=n×fact(n1),se n>0

Algoritmos recursivos

A definição anterior define um algoritmo: permite calcular o factorial de qualquer inteiro não negativo.

Exemplo:

fact(4)=4×fact(3)=4×(3×fact(2))=4×(3×(2×fact(1)))=4×(3×(2×(1×fact(0)))=4×(3×(2×(1×1)))=24

Recursão em linguagem C

Podemos implementar este processo defindo a função recursiva em C:

int fact(int n)
{
   if (n == 0)
      return 1;              // caso base
   else
      return n * fact(n-1);  // caso recursivo
}

Caso base e recursivo

Há dois casos na definição anterior:

caso base (n=0)

o factorial de zero é 1 (sem mais chamadas recursivas)

caso recursivo (n>0)

calculamos o factorial do natural anterior e multiplicamos o resultado por n

Terminação de definições recursivas

Para que uma definição recursiva termine é suficiente que:

Exemplo: na função de fact

Logo: fact termina para qualquer n maior ou igual a 0.

Observações finais

  int fact(int n) {
     int r = 1;    // resultado
     for(int i = 1; i<=n; i++) 
         r = r*i;
     return r;
  }

Tipos básicos

Inteiros em C

        int i;          // com sinal
        unsigned int j; // sem sinal 
        unsigned j;      // unsigned int 

Inteiros com e sem sinal

Exemplo: 16 bits sem sinal

mínimo0(0000000000000000)21(0000000000000001)22(0000000000000010)2máximo2161(1111111111111111)2

Exemplo: 16 bits com sinal

mínimo215(1000000000000000)21(1111111111111111)20(0000000000000000)2+1(0000000000000001)2máximo+2151(0111111111111111)2

Tamanhos

   short int       unsigned short int
   int             unsigned int 
   long int        unsigned long int

Tamanhos

Imprimir os limites

#include <stdio.h>
#include <limits.h>

int main(void) {
  printf("SHRT_MIN = %d\n", SHRT_MIN);
  printf("SHRT_MAX = %d\n", SHRT_MAX);

  printf("INT_MIN = %d\n", INT_MIN);
  printf("INT_MAX = %d\n", INT_MAX);

  printf("LONG_MIN = %ld\n", LONG_MIN);
  printf("LONG_MAX = %ld\n", LONG_MAX);
     // %ld para formatar long int
}

Exemplo

Execução no meu portátil (GNU/Linux X86-64):

$ ./tamanhos 
SHRT_MIN = -32768
SHRT_MAX = 32767
INT_MIN = -2147483648
INT_MAX = 2147483647
LONG_MIN = -9223372036854775808
LONG_MAX = 9223372036854775807

Constantes

         17   // int
     -1000L   // long int 
     2500UL   // unsigned long int
   long int i = 17;     // 17 -> 17L

Ler e escrever inteiros

Para ler ou escrever inteiros short, long ou unsigned devemos usar formatos específicos em scanf e printf.

Casos mais comuns:

"%u"

inteiro decimal unsigned

"%ld"

inteiro decimal long

"%lu"

inteiro decimal unsigned long

Exemplo

Versão 1

#include <stdio.h>

int main(void) {
   unsigned l, w, h, v;

   printf("L=?"); scanf("%u", &l);
   printf("W=?"); scanf("%u", &w);
   printf("H=?"); scanf("%u", &h);
   v = l*w*h; // cálculo do volume

   printf("LxWxH: %u*%u*%u (cm)\n", l,w,h);
   printf("Volume: %u (cm^3)\n", v);
}

Execução 1

Esta versão calcula o volume correto para

l = w = h = 1500

mas obtemos de novo overflow para

l = w = h = 2000

Versão 2

#include <stdio.h>

int main(void) {
   unsigned long l, w, h, v;

   printf("L=?"); scanf("%lu", &l);
   printf("W=?"); scanf("%lu", &w);
   printf("H=?"); scanf("%lu", &h);
   v = l*w*h; // cálculo do volume

   printf("LxWxH: %lu*%lu*%lu (cm)\n", l,w,h);
   printf("Volume: %lu (cm^3)\n", v);
}

Execução 2

Vírgula flutuante

Limites IEEE 754

tipo menor positivo maior valor precisão
float 1.17×1038 3.40×1038 6 algarismos
double 2.22×10308 1.79×10308 15 algarismos

Constantes

5.7e1 5.7×101
5.7E-3 5.7×103

Ler e escrever vírgula flutuante

Exemplo

#include <stdio.h>

int main(void) {
  double l, w, h, v;

  printf("L=?"); scanf("%lf", &l);
  printf("W=?"); scanf("%lf", &w);
  printf("H=?"); scanf("%lf", &h);
  v = l*w*h; // cálculo do volume

  printf("LxWxH: %.3f*%.3f*%.3f (cm)\n", 
         l, w, h);
  printf("Volume: %.3g (cm^3)\n", v);
}

Conversões explícitas

Conversão explicita de tipos (“cast”):

(int) expr converter para inteiro
(float) expr converter para vírgula flutuante
(tipo) expr forma geral
int k = 2, n = 3; 
printf("%f\n", (float)k/(float)n); // 0.66666
printf("%f\n", (float)k/n);        // 0.66666
printf("%f\n", (float)(k/n));      // 0.00000

Conversões explícitas (cont.)

Também podemos converter entre tamanhos:

int i = 1500;
long j;
j = (long)i;

Devemos efetuar conversões antes de operações que possam causar overflow:

int i = 1500;
long j;
j = (long)(i*i*i);  // "overflow"(?)
j = (long)i*i*i;    // OK