1. Capítulo 4
DESENVOLVENDO ALGORITMOS
Algoritmo é formalmente uma seqüência finita de passos que levam a execução de uma
tarefa. Podemos pensar em algoritmo como uma receita, uma seqüência de instruções que dão
cabo de uma meta específica. Estas tarefas não podem ser redundantes nem subjetivas na sua
definição, devem ser claras e precisas. Algoritmo é a lógica necessária para o desenvolvimento de
um programa.
Programas de computadores nada mais são do que algoritmos escritos numa linguagem de
computador (Pascal, C, Cobol, Fortran, Visual Basic, entre outras) e que são interpretados e
executados por uma máquina, no caso um computador. Notem que dada esta interpretação
rigorosa, um programa é por natureza muito específico e rígido em relação aos algoritmos da vida
real.
Em um modo geral, um algoritmo segue um determinado padrão de comportamento, com
objetivo de alcançar a solução de um problema.
Padrão de comportamento: imagine a seqüência de números: 1, 6, 11, 16, 21, 26, ... Para
determinar qual será o sétimo elemento dessa série, precisamos descobrir qual é a sua regra de
formação, isto é, qual é o seu padrão de comportamento. Como a seqüência segue uma certa
constância, facilmente determinada, somos capazes de determinar qual seria o sétimo termo ou
outro termo qualquer.
Descrevemos então uma atividade bem cotidiana: trocar uma lâmpada. Apesar de parecer
óbvia demais, muitas vezes fazemos este tipo de atividade inconscientemente, sem percebermos
os pequenos detalhes.
Vejamos como seria descrevê-la passo a passo:
pegar uma escada;
posicionar a escada embaixo da lâmpada;
buscar uma lâmpada nova;
subir na escada;
retirar a lâmpada velha;
colocar a lâmpada nova.
Para se trocar a lâmpada, é seguida uma determinada seqüência de ações, representadas
através desse algoritmo. Como isso pode ser seguido por qualquer pessoa, estabelece-se aí um
padrão de comportamento. A sequencialização tem por objetivo reger o fluxo de execução,
determinando qual ação vem a seguir.
O algoritmo anterior tem um objetivo bem específico: trocar uma lâmpada. E se a lâmpada
não estiver queimada? O algoritmo faz com ela seja trocada do mesmo modo, não prevendo essa
situação. Para solucionar este problema, podemos efetuar um teste seletivo, verificando se a
lâmpada está ou não queimada:
2. pegar uma escada;
posicionar embaixo da lâmpada;
buscar uma lâmpada nova;
ligar o interruptor;
se a lâmpada não acender, então:
subir na escada;
retirar a lâmpada velha;
colocar a lâmpada nova.
Dessa forma, algumas ações estão ligadas à condição (lâmpada não acender). No caso da
lâmpada acender, as três linhas não serão executadas.
Em algumas situações, embora o algoritmo resolva o problema proposto, a solução pode
não ser a mais eficiente. Exemplo: três alunos devem resolver um determinado problema:
O aluno A conseguiu resolver o problema executando 35 linhas de programa.
O aluno B resolveu o problema executando 10 linhas de programa
O aluno C resolveu o problema executando 54 linhas de programa.
Obviamente, o algoritmo desenvolvido pelo aluno B é menor e mais eficiente que os
demais. Isso significa que há código desnecessário nos demais programas.
Dessa forma, podemos otimizar o algoritmo anterior, uma vez que buscamos a escada e a
lâmpada sem saber se serão necessárias:
ligar o interruptor;
se a lâmpada não acender, então:
pegar uma escada;
posicionar a escada embaixo da lâmpada;
buscar uma lâmpada nova;
subir na escada;
retirar a lâmpada velha;
colocar a lâmpada nova.
Podemos considerar ainda que a lâmpada nova pode não funcionar. Nesse caso devemos
trocá-la novamente, quantas vezes for necessário, até que a lâmpada acenda:
ligar o interruptor;
se a lâmpada não acender, então:
pegar uma escada;
posicionar a escada embaixo da lâmpada;
buscar uma lâmpada nova;
subir na escada;
retirar a lâmpada velha;
colocar a lâmpada nova;
se a lâmpada não acender, então:
retirar a lâmpada;
colocar outra lâmpada;
se a lâmpada não acender, então: ...
3. Observamos que o teste da lâmpada nova é efetuado por um conjunto de ações:
se a lâmpada não acender então:
retirar a lâmpada
colocar outra lâmpada
Em vez de escrevermos várias vezes este conjunto de ações, podemos alterar o fluxo
sequencial de execução do programa, de forma que, após executar a ação “coloque outra
lâmpada”, voltemos a executar a ação “se a lâmpada não acender”.
Precisa-se então determinar um limite para tal repetição, para garantir que ela cesse
quando a lâmpada finalmente acender:
enquanto a lâmpada não acender, faça:
retire a lâmpada
coloque outra lâmpada
Uma versão final do algoritmo, que repete ações até alcançar o seu objetivo: trocar a
lâmpada queimada por uma que funcione, é apresentada abaixo.
ligar o interruptor;
se a lâmpada não acender, então:
pegar uma escada;
posicionar a escada embaixo da lâmpada;
buscar uma lâmpada nova;
subir na escada;
retirar a lâmpada velha;
colocar a lâmpada nova;
enquanto a lâmpada não acender, faça:
retirar a lâmpada;
colocar outra lâmpada.
Até agora, estamos efetuando a troca de uma única lâmpada. Todo o procedimento
poderia ser repetido 10 vezes, por exemplo, no caso de querermos trocar 10 lâmpadas.
Inicialmente, tínhamos um pequeno conjunto de ações que deveriam ser executadas (estrutura
sequencial). Através de uma condição, incluímos posteriormente uma estrutura de seleção. Na
necessidade de repetir um determinado trecho do algoritmo, construiu-se no final uma estrutura
de repetição.
4. 1. FATORES A SEREM LEVADOS EM CONSIDERAÇÃO NA CONSTRUÇÃO DE UM
ALGORITMO
1.1. COMPLEXIDADE
Percebeu-se, na medida em que colocávamos situações novas no problema a ser resolvido,
que ia aumentando a complexidade do algoritmo. Esse certamente é o maior problema envolvido
na construção de algoritmos. A complexidade pode ser vista como um sinônimo de variedade
(quantidade de situações diferentes que um problema pode apresentar), as quais devem ser
previstas na sua solução.
Já que conviver com a complexidade é um mal necessário, é saudável fazer o possível para
diminuí-la ao máximo, a fim de controlar o problema e encontrar sua solução.
Deve-se diferenciar O que de Como. Muitos programadores aumentam a complexidade de
um devido problema desnecessariamente. A forma errada de interpretação de um problema pode
levar a respostas irrelevantes à solução almejada ou até mesmo a nenhuma solução, gerando
algoritmos mais complexos do que o necessário.
Exemplo: digamos que se pergunte a um leigo a respeito de um relógio:
- Como é um relógio?
= É um instrumento com três ponteiros concêntricos.
Como a descrição não é relevante, poderíamos indagar:
- Um relógio com 2 ponteiros é possível?
= É... pode ser!
Poderíamos ainda indagar:
- E um relógio com apenas 1 ponteiro não poderia ser uma possibilidade?
= Bem... Pode ser com 3, 2 ou 1 ponteiro.
- E sem ponteiro pode?
= Ah!, Sim! Pode ser digital
Já a pergunta: “O que é um relógio?”, poderia resultar na resposta:
- É um instrumento cuja finalidade é marcar o decorrer do tempo.
Ou seja, algumas variáveis podem aumentar ou diminuir a complexidade de um sistema
quando forem bem ou mal utilizadas.
1.2. LEGIBILIDADE
Mede a capacidade de compreensão de um algoritmo por qualquer observador (que não o
construiu); a clareza com que sua lógica está exposta. Quanto mais legível for um algoritmo,
menor será sua complexidade.
5. 1.3. PORTABILIDADE
Devido a quantidade enorme de linguagens de programação existentes, não será adotada
nenhuma linguagem específica para trabalhar os algoritmos (ex: C, pascal, Java, etc.). Isso porque
a solução do problema fica ligada a características e recursos da linguagem na qual ela foi
concebida.
Utilizaremos uma pseudo-linguagem (linguagem fictícia) que visa a permitir a
representação dos algoritmos através da língua portuguesa (português estruturado). Esses
algoritmos poderão ser convertidos facilmente para qualquer linguagem de programação usual
(Basic estruturado, C, pascal, Java).
1.4. TÉCNICA DE RESOLUÇÃO POR MÉTODO CARTESIANO
A famosa frase de Descartes “Dividir para conquistar” é muito importante dentro da
programação. É um método que ataca um problema grande, de difícil solução, dividindo-o em
problemas menores, de solução mais fácil. Se necessário, pode-se dividir novamente as partes não
compreendidas. Esse método pode ser esquematizado em passos:
1. Dividir o problema em partes
2. Analisar a divisão e garantir a coerência entre as partes.
3. Reaplicar o método, se necessário
1.5. PLANEJAMENTO REVERSO
Consiste em, a partir do resultado final, determinar quais são os componentes básicos. Ou
seja, a partir da saída desejada, devemos poder determinar, reversamente, quais são os
componentes da entrada de dados necessários.
2. MÉTODO PARA CONSTRUIR UM ALGORITMO
Para escrever um algoritmo precisamos descrever a seqüência de instruções, de maneira
simples e objetiva. Para isso utilizaremos algumas técnicas:
Usar somente um verbo por frase
Imaginar que você está desenvolvendo um algoritmo para pessoas que não trabalham com
informática
Usar frases curtas e simples
Ser objetivo
Procurar usar palavras que não tenham sentido dúbio
Utilizando os conceitos já desenvolvidos, esquematizaremos um método para construir um
algoritmo logicamente correto:
6. 1. Ler atentamente o enunciado
Deve-se reler o enunciado de um exercício quantas vezes for necessário, até compreendê-lo
completamente. A maior parte da resolução de um exercício consiste na compreensão completa
do enunciado.
2. Retirar a relação das entradas de dados do enunciado
Através do enunciado, descobrimos quais são os dados que devem ser fornecidos ao programa, via
teclado, a partir dos quais são desenvolvidos os cálculos. Obs. Pode haver algum algoritmo que
não necessite da entrada de dados (pouco comum).
3. Retirar do enunciado, a relação das saídas das informações
Através do enunciado podemos descobrir quais são as informações que devem ser mostradas para
compor o resultado final, objetivo do algoritmo.
4. Determinar o que deve ser feito para transformar as entradas nas saídas especificadas
Nessa fase é que teremos a construção do Algoritmo propriamente dito. Devemos determinar
qual sequência de passos ou ações é capaz de transformar um conjunto de dados nas informações
de resultado. Para isso, utilizamos os fatores descritos anteriormente, tais como legibilidade,
portabilidade, método cartesiano e planejamento reverso, e finalmente podemos construir o
algoritmo.
2.1. FASES
Vimos que ALGORITMO é uma seqüência lógica de instruções que podem ser executadas. É
importante ressaltar que qualquer tarefa que siga determinado padrão pode ser descrita por um
algoritmo, como por exemplo:
COMO FAZER ARROZ DOCE
ou então
CALCULAR O SALDO FINANCEIRO DE UM ESTOQUE
Entretanto ao montar um algoritmo, precisamos primeiro dividir o problema apresentado
em três fases fundamentais.
Onde temos:
ENTRADA: São os dados de entrada do algoritmo
PROCESSAMENTO: São os procedimentos utilizados para chegar ao resultado final
SAÍDA: São os dados já processados
7. Analogia com o homem
2.2. EXEMPLO DE ALGORITMO
Imagine o seguinte problema: Calcular a média final dos alunos da 3ª Série. Os alunos
realizarão quatro provas: P1, P2, P3 e P4. Onde:
Para montar o algoritmo proposto, faremos três perguntas:
a) Quais são os dados de entrada?
R: Os dados de entrada são P1, P2, P3 e P4
b) Qual será o processamento a ser utilizado?
R: O procedimento será somar todos os dados de entrada e dividi-los por 4 (quatro)
c) Quais serão os dados de saída?
R: O dado de saída será a média final
Algoritmo:
Receba a nota da prova1
Receba a nota de prova2
Receba a nota de prova3
Receba a nota da prova4
Some todas as notas e divida o resultado por 4
Mostre o resultado da divisão
8. 2.3. TESTE DE MESA
Após desenvolver um algoritmo ele deverá sempre ser testado. Este teste é chamado de
TESTE DE MESA, que significa, seguir as instruções do algoritmo de maneira precisa para verificar
se o procedimento utilizado está correto ou não.
Veja o exemplo utilizando a tabela abaixo:
P1 P2 P3 P4 Média
3. MÁXIMAS DE PROGRAMAÇÃO
“Máximas de Programação” são regras práticas para a elaboração de algoritmos com
qualidade. Alguns exemplos são apresentados a seguir.
1. Algoritmos devem ser feitos para serem lidos por seres humanos: tenha em mente que
seus algoritmos deverão ser lidos e entendidos por outras pessoas (e por você mesmo), de
tal forma que possam ser corrigidos, modificados, ou receber manutenção;
2. Escreva os comentários no momento em que estiver escrevendo o algoritmo: um algoritmo
não documentado é um dos piores erros que um programador pode cometer; é sinal de
amadorismo. Como o objetivo da escrita de comentários é facilitar o entendimento do
algoritmo, eles devem ser tão bem concebidos quanto o próprio algoritmo, e a melhor
maneira de conseguir isso é escrevê-los nos momentos de maior intimidade com os
detalhes, ou seja, durante a resolução do problema. Lembre-se: é melhor ter um algoritmo
sem comentários do que ter um algoritmo com comentários desatualizados;
3. Os comentários deverão acrescentar alguma coisa às pessoas que farão manutenção em
seu algoritmo: o conjunto de comandos nos diz o que está sendo feito; os comentários
deverão nos dizer o porquê;
4. Use comentários no cabeçalho do algoritmo: todo algoritmo ou procedimento deverá ter
comentários em seu início para explicar o que ele faz e fornecer instruções para seu uso.
Alguns destes comentários seriam:
a. Uma descrição do que faz o algoritmo;
b. Como utilizá-lo;
c. Explicação dos significados das variáveis mais importantes;
9. d. Estruturas de dados utilizadas;
e. Data da escrita;
f. Autor;
g. Nomes de quaisquer métodos especiais utilizados, como referências nas quais mais
informações possam ser encontradas.
5. Utilize espaços em branco para melhorar a legibilidade: espaços em branco, inclusive linhas
em branco, são muito valiosos, pois melhoram a aparência de um algoritmo. Veja:
a. Deixar uma linha em branco entre as declarações e o corpo do algoritmo;
b. Deixar uma linha em branco antes e outra depois de um comentário;
c. Separar grupos de comandos que executam funções lógicas distintas por uma ou mais
linhas em branco;
d. Utilizar brancos para indicar precedências de operadores. Em vez de “A + B * C” é
bem mais agradável a forma “A + B*C”.
6. Escolha nomes representativos para suas variáveis: os nomes das variáveis deverão
identificar o melhor possível as funcionalidades que elas representam. Por exemplo, X <-
Y + Z é muito menos claro que PREÇO <- CUSTO + LUCRO. Uma seleção adequada de
nomes de variáveis é o princípio mais importante da legibilidade de algoritmos;
7. Um comando por linha é suficiente: a utilização de vários comandos por linha é prejudicial,
por várias razões, entre as quais se destacam:
a. O algoritmo pode ficar ilegível;
b. O algoritmo fica mais difícil de ser depurado.
Veja um exemplo:
A <- 14, 2 ; I<-1; enquanto I<10 faça X <- X + 1 ; K<-I*K;I<-I+1
fim-enquanto;
Agora, com cada comando em uma linha:
A <- 14,2
I <- 1
enquanto I<10 faça
X <- X + 1
K <- I * K
I <- I + 1
fim-enquanto;
Caso desejássemos incluir um novo comando dentro do enquanto, seria necessário
reescrever toda a linha.
10. 8. Utilize parênteses para aumentar a legibilidade e prevenir-se contra erros.
Veja uns exemplos, primeiro, com pouco parênteses:
A * B * C / (D * E * F)
A * B / C * D / E * F
A / B / C / D
X > Y ou Q
A + B < C
Agora, com parênteses extras:
(A * B * C) / (D * E * F)
((((A * B) / C) * D) / E) * F
((A / B) / C) / D
(X > Y) ou Q
(A + B) < C
9. Utilize “identação” para montar a estrutura lógica do algoritmo: a identação não deve ser
feita de forma caótica, mas segundo certos padrões estabelecidos;
10. Sempre que for feita uma modificação no algoritmo, os comentários associados devem ser
alterados, e não apenas os comandos. Antes não comentar do que deixar um comentário
errado.
Para concluir, vamos ver um trecho de um algoritmo mal escrito. A análise das regras de
programação violadas será apresentada a seguir.
inicio
inteiro: XPT, I, II, III, IIIII
leia (XPT, IIIII)
I <- 1
enquanto I < XPT faça
se I < IIIII então se IIIII = 20 então II <- XPT + 2 senão III <- IIIII
^ XPT; senão III <- XPT
IIIII <- III + I; fim-se; fim-se; //sem comentários
I <- I + 1
fim-enquanto; imprima (I, II, IIIII, III, XPT); fim.
Esse código fere todas as regras pregadas pelas máximas de programação, sendo que as
mais graves são: falta de identação, nomes não significativos para as variáveis e ausência de
comentários.
11. EXERCÍCIOS PROPOSTOS
Questão 01:
Um homem ia pela estrada transportando para vender na cidade um LOBO, um CORDEIRO, cada
qual por uma coleira, e um SACO DE COUVES nas costas. Em determinada etapa do caminho
deparou-se com um rio com uma canoa muito pequena onde cabiam ele e apenas mais um dos
três itens que transportava de cada vez. Ora, ele tinha o problema de vigiar o cordeiro que estava
doido pra comer as couves e lógico um lobo louco pra comer o cordeiro. Tendo que atravessar um
de cada vez, não podia deixar juntos em qualquer margem do rio, o lobo com o cordeiro e nem o
cordeiro com as couves. Como o homem conseguirá atravessar o rio sem perder nenhum de seus
itens? Monte um algoritmo que descreva os passos desse homem para atravessar o rio.
Questão 02:
Elabore um algoritmo que mova 3 discos de uma torre de Hanói, que consiste em 3 hastes (a-b-c),
uma das quais serve de suporte para os três discos de tamanhos diferentes (1-2-3), os menores
sobre os maiores. Pode-se mover um disco de cada vez para qualquer haste, sendo que nunca
deve ser colocado um disco maior sobre um menor. O objetivo é transferir os três discos da haste
A para haste C.