Algoritmos

O que é um algoritmo?

Nesta aula

Dois algoritmos clássicos sobre números inteiros:

Vamos ainda ver a recursão: uma forma alternativa aos ciclos para especificar repetição.

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 \(n\leq 1\) então não é primo e terminamos imediatamente.

Se \(n>1\) tentamos para \(d = 2, 3, \ldots, n-1\):

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:

\[ \begin{eqnarray} 252 &=& 21 \times 12 \\ 105 &=& 21 \times 5 \end{eqnarray} \]

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:
    \(a \leftarrow a-b\) e repetimos o passo 1.
  3. se \(a<b\) então:
    \(b \leftarrow b-a\) 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 \(a-b\) e \(d\) divide \(b-a\).

Terminação

Em cada iteração:

\[ \begin{aligned} \text{se}~a>b: \quad & (a,b) \longrightarrow (a-b, b) \\ \text{se}~a<b: \quad & (a,b) \longrightarrow (a, b-a) \end{aligned} \]

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

\[ \begin{eqnarray*} \text{fact}(0) &=& 1\\ \text{fact}(n) &=& n \times \text{fact}(n-1)\,, \quad \text{se}~n>0 \end{eqnarray*} \]

Algoritmos recursivos

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

Exemplo:

\[ \begin{eqnarray*} \text{fact}(4) &=& 4 \times \text{fact}(3) \\ &=& 4 \times (3 \times \text{fact}(2))\\ &=& 4 \times (3 \times (2\times \text{fact}(1)))\\ &=& 4 \times (3 \times (2\times (1\times \text{fact}(0)))\\ &=& 4 \times (3 \times (2\times (1\times 1))) \\ &=& 24 \end{eqnarray*}\]

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