Este capítulo descreve a solução de diversos exercícios, que mostram como usar e decidir quando usar os diversos comandos e estruturas de dados abordados neste livro.
Os enunciados dos exercícios são obtidos da página Web http://br.spoj.pl/problems/. SPOJ (Sphere Online Judge) é um sistema disponível na Internet (na página http://br.spoj.pl/) que permite o registro de novos problemas e a submissão de soluções de problemas registrados. Há dezenas de milhares de usuários e milhares de problemas já registrados. A solução pode ser submetida em dezenas de linguagens de programação, incluindo, é claro, C.
Considere que uma representação alfanumérica de um número de
telefone é uma sequência de caracteres tal que cada caractere pode
ser: uma letra maiúscula (de A a Z), um hifen (-) ou
um dígito 1 ou 0, sendo que letras maiúsculas representam
dígitos de 2 a 9, de acordo com a tabela abaixo.
| Letras | Número |
| ABC | 2 |
| DEF | 3 |
| GHI | 4 |
| JKL | 5 |
| MNO | 6 |
| PQRS | 7 |
| TUV | 8 |
| WXYZ | 9 |
Escreva um programa que leia várias linhas, cada linha contendo uma tal representacão alfanumérica de número de telefone, e imprima uma sequência de representações para os números de telefone, novamente uma em cada linha, que substitua letras maiúsculas por dígitos de acordo com a tabela mostrada.
Considere que cada representacão alfanumérica possui entre 1 e 30 caracteres. A entrada é terminada por fim de arquivo (EOF).
Por exemplo, para a entrada:
| 1-HOME-SWEET-HOME |
| MY-MISERABLE-JOB |
A saída deve ser:
| 1-4663-79338-4663 |
| 69-647372253-562 |
A solução mostrada abaixo usa um arranjo que armazenada, para cada
letra maiúscula, seu código, segundo a tabela apresentada. De fato,
como em C o primeiro índice de um arranjo tem que ser 0,
para cada letra maiúscula corresponde um índice entre 0 e o
número de letras maiúsculas menos um.
|
Essa solução evita escrever um programa, relativamente ineficiente e mais longo, que testa, após a leitura de cada caractere, se o caractere é uma letra pertencente a um grupo específico de letras na tabela mostrada, para impressão do dígito correspondente a esse grupo de letras na tabela.
Um número primo é um número que possui somente dois divisores: ele
mesmo e o número 1. Exemplos de números primos são: 1, 2, 3, 5, 17,
101 e 10007.
Neste problema você deve ler um conjunto de palavras, onde cada palavra é composta somente por letras no intervalo a-z e A-Z. Cada letra possui um valor específico, a letra a vale 1, a letra b vale 2 e assim por diante, até a letra z, que vale 26. Do mesmo modo, a letra A vale 27, a letra B vale 28 e a letra Z vale 52.
Você deve escrever um programa para determinar se uma palavra é uma palavra prima ou não. Uma palavra é uma palavra prima se a soma de suas letras é um número primo.
Entrada: Conjunto de palavras; cada palavra em uma linha, com L letras, onde 1 ≤ L ≤ 20. Término com fim de arquivo (EOF).
Saída: Para cada palavra imprimir: It is a prime word., se a soma das letras da palavra é um número primo, senão imprimir It is not a prime word.
Exemplo: Para a entrada:
| UFRN |
| contest |
| AcM |
a saída dever ser:
| It is a prime word. |
| It is not a prime word. |
| It is not a prime word. |
Uma solução completa é mostrada na Figura 8.1. A
função main lê diversas palavras, até encontrar
fim-de-arquvo, e imprime mensagem indicando se cada palavra lida é uma
palavra prima ou não. O tamanho de cada palavra é restrito a um
tamanho máximo de 20 caracteres.
A função primo verifica se um dado número n é primo ou
não. Essa função pode ser implementada de diversos modos. A
Figura 8.1 usa o método simples de divisões sucessivas,
por todos os números menores que √n. Para valores
grandes de n, esse método gasta mais tempo do que outros
algoritmos existentes. Os programas mostrados a seguir usam o
algoritmo conhecido como crivo de Eratóstenes. O leitor
interessado pode consultar e.g.:
| http://en.wikipedia.org/wiki/Prime_number#Verifying_primality |
#include <stdio.h> // define scanf, printf #include <math.h> // define sqrt const int max = 21; // Um caractere a mais do que o tamanho máximo de uma palavra // porque um caractere ('\0') é usado para indicar final da cadeia de caracteres int prima (char* palavra); int ler (char* palavra); int main () { char palavra[max]; while (ler(palavra)) printf("It is%s a prime word.\n", prima(palavra) ? "" : " not"); } int minusc (char let) { return (let >= 'a' && let <= 'z'); } int valor (char let) { return (minusc(let) ? let - 'a' + 1 : let - 'A' + ('z'-'a'+1)+1); } int prima (char* palavra) { int i, somaLet=0; for (i=0; i < max && palavra[i] != '\0'; i++) somaLet += valor(palavra[i]); return primo(somaLet); } int primo (int n) { if (n%2 == 0) return 0; int k = 3, sqrtn = sqrt((double)n); // Se existir divisor próprio de n, tem que existir divisor próprio menor que raiz de n. while (k <= sqrtn) { if (n%k == 0) return 0; k += 2; } return 1; } int ler (char* palavra) { // Retorna verdadeiro sse leitura com sucesso. return (scanf("%s", &palavra) == 1);\\ }
Figura 8.1: Solução de PAPRIMAS com teste simplificado de primalidade
Uma solução que usa um teste de primalidade baseado no algoritmo conhecido como crivo de Eratóstenes é mostrada a seguir:
|
O algoritmo do Crivo de Eratóstenes, inventado por Eratóstenes
em 350 A.C., cria uma lista de todos os primos menores que um valor
máximo m. Para o problema PAPRIMAS, existe tal valor máximo,
que pode ser definido como vinte vezes o valor máximo possível para
uma eletra (uma vez que podem ocorrer no máximo 20 letras em uma
palavra). O algoritmo de Eratóstenes consiste no seguinte:
l com todos os inteiros de 2 a m.
p.
l a partir de p:
l todos os múltiplos de p;
p igual ao número seguinte a p, em l, ainda não removido.
O programa cria um arranjo contendo todos os primos de 3 até m
e, para verificar se um dado número n é primo, percorre esse
arranjo usando uma pesquisa sequencial (de componente a
componente), até encontrar um número primo igual ou maior a n ou
até chegar ao final do arranjo.
Essa pesquisa sequencial em um arranjo ordenado é ineficiente. Um algoritmo mais eficiente, chamado de pesquisa binária, é usado a seguir.
|
O algoritmo de pesquisa binária compara o elemento que está sendo procurado com o valor que está na metade do arranjo ordenado, permitindo assim que a busca prossiga ou na metade inferior ou na superior do arranjo, conforme o elemento a ser procurado seja menor ou maior, respectivamente, do que o valor que está na metade do arranjo. Se o elemento a ser procurado é igual ao elemento na metade, então, é claro, a busca termina com sucesso.
São mostradas apenas as funções modificadas, que são
listaDePrimos e primo. A função listaDePrimos é
modificada apenas para retornar, além do arranjo contendo os primos, o
número de primos de fato armazenado nesse arranjo. A função
primo percorre esse arranjo usando pesquisa binária.