Você está na página 1de 28

Capítulo 2

ALGORITMOS

Sumário

Este capítulo é dedicado à análise algorítmica da solução de problemas. Começamos por


apresentar as características de um algoritmo, bem como a metodologia de programação
procedimental baseada na decomposição hierárquica. Para implementar esta metodologia,
usamos numa primeira fase a linguagem natural, e mostramos o refinamento progressivo da
solução, recorrendo para o efeito a uma linguagem pseudocódigo. Definimos as regras
desta linguagem, que deve ser semelhante à linguagem Pascal, de maneira a podermos
delinear algoritmos detalhados e rigorosos que possam ser facilmente traduzidos para o
Pascal. Apresentamos os três modelos básicos de decomposição algorítmica, através de
exemplos típicos da utilização de cada modelo. Finalmente, mostramos como o
computador pode ser usado para a simulação de problemas do mundo real.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 2

2.1 Especificação do problema


No primeiro capítulo, no ponto da especificação de um problema, foi referido que um
computador só resolve problemas que são completamente especificados ou problemas que
se podem decompor numa combinação de um número finito de problemas completamente
especificados. Um problema completamente especificado caracteriza-se pela existência de
duas entidades, uma estrutura de dados e um algoritmo.

A estrutura de dados, ou seja, as variáveis de entrada e de saída, infere-se normalmente a


partir do enunciado do problema. Veremos mais tarde que existem também variáveis
auxiliares, ou internas, que vão aparecendo durante a análise algorítmica do problema. O
algoritmo, ou seja, a solução do problema, tem que ser descoberta pelo programador, a não
ser quando a solução é previamente conhecida, por se tratar de problemas cujas soluções já
foram devidamente estudadas e são do conhecimento geral.

Comecemos com um problema simples. Pretende-se escrever um programa que dada uma
distância expressa em milhas, que é lida do teclado, converte-a para quilómetros e escreve-a
no monitor. A especificação completa é apresentada na Figura 2.1. A partir do enunciado,
deduzimos que temos apenas uma variável de entrada MILHAS, que é a distância em
milhas a ser convertida. Uma distância é obviamente uma quantidade física real não
negativa. Também temos apenas uma variável de saída QUILOMETROS, que é a distância
calculada em quilómetros através da fórmula de conversão.

Variável de entrada: MILHAS (distância expressa em milhas)


valor numérico real positivo ou nulo

Variável de saída: QUILOMETROS (distância expressa em quilómetros)


valor numérico real representado com 3 casas decimais

Solução: QUILOMETROS = 1.609 * MILHAS

Figura 2.1 - Especificação completa da conversão de distâncias.


Analisemos um exemplo de solução mais complexa, mas conhecida, como é o caso da
equação de 2º grau. Pretende-se escrever um programa que dados os parâmetros A, B e C,
que são lidos do teclado, determina as raízes da equação de 2º grau, Ax2+Bx+C=0, e
escreve-as no monitor. Este exemplo serve para mostrar uma das regras básicas
mencionadas anteriormente. Um algoritmo deve considerar sempre a resolução de classes
de problemas e não de problemas particulares. Ora, as raízes da equação de 2º grau
pertencem ao conjunto dos números complexos, pelo que, a solução geral deve calcular
duas raízes com parte real e parte imaginária. A especificação completa deste exemplo é
apresentada na Figura 2.2.

Neste caso temos três variáveis de entrada, que são os coeficientes A, B e C da equação de
2º grau, sendo que A tem de ser forçosamente diferente de zero para que a equação seja
realmente de 2º grau. As variáveis de saída são duas, X1 e X2, mas como as raízes da
equação de 2º grau, no caso geral, são números complexos, então temos quatro variáveis de
saída. X1R e X2R são as partes reais de X1 e X2 respectivamente, enquanto X1I e X2I são
as partes imaginárias de X1 e X2 respectivamente. A solução passa por aplicar a fórmula
resolvente, em que se começa por calcular o descriminante B2-4AC. Se o valor for positivo
ou nulo, então temos soluções reais, ou seja, soluções complexas com parte imaginária igual
a zero. Senão temos soluções complexas conjugadas.
3 CAPÍTULO 2 : ALGORITMOS

Variáveis de entrada: A, B, C (coeficientes da equação)


valores numéricos reais, mas em que A não pode ser nulo

Variáveis de saída: X1R, X1I, X2R, X2I (raízes da equação)


valores numéricos reais representados em notação científica, com 4
algarismos significativos na mantissa

Solução: Calcular o discriminante ∆ = B 2 − 4 × A × C

− B + ∆ − B − ∆
∆ ≥ 0 ⇒ X1R = e X1I = 0 ; X 2R = e X 2I = 0
2× A 2× A
− B − ∆ − B − ∆
∆ < 0 ⇒ X1R = e X1I = ; X 2R = e X 2I = −
2× A 2× A 2× A 2× A

Figura 2.2 - Especificação completa da resolução da equação de 2º grau.

2.2 Algoritmos e suas características


Um algoritmo é um método que permite obter, de forma unívoca, os valores das variáveis
de saída em função dos valores das variáveis de entrada. Mas, é preciso ter em
consideração, que em determinado tipo de aplicações, um programa pode não ter qualquer
variável de entrada. No entanto, um programa gera sempre, pelo menos uma variável de
saída. Se bem que, em determinado tipo de aplicações, as variáveis de saída podem ser
apresentadas no monitor sob a forma gráfica.

Um algoritmo deve contemplar alternativas para toda a gama de valores das variáveis de
entrada, o que implica uma descrição algébrica da solução do problema. Só tendo em
consideração todas as possibilidades dos valores que se apresentam à entrada do problema
é que se consegue construir uma solução eficaz para o problema. Entenda-se por solução
eficaz, uma solução que num intervalo de tempo finito e independentemente dos dados
colocados à entrada, atinge sempre um resultado. Quer seja, o cálculo dos dados de saída,
quer seja, a indicação da impossibilidade de efectuar tal cálculo.

Também foi referido que o algoritmo deve considerar sempre a resolução de classes de
problemas e não de problemas particulares. Deve também fazê-lo de forma eficiente, ou
seja, da maneira mais objectiva e com o menor custo possível. O custo pode ser medido
em número de instruções do processador. Finalmente, o algoritmo deve terminar, ou seja,
deve atingir a situação em que não existem mais instruções para executar.

Numa definição mais formal, podemos dizer que um algoritmo é uma descrição detalhada e
rigorosa da solução do problema, ou seja, uma sequência finita de instruções bem definidas
que num número finito de passos, resolve de forma eficaz e eficiente o problema.

Uma descrição detalhada e rigorosa implica que cada uma das operações tem que ser
enunciada sem ambiguidade. Costuma-se começar por usar uma linguagem natural, por
exemplo o português, e depois à medida que a descrição se torna mais detalhada, passa-se
para uma linguagem formalmente mais simples, mas mais rigorosa, baseada na notação
matemática. Esta linguagem designa-se por pseudocódigo.

Quando se diz que um algoritmo é uma sequência finita de instruções, estamos a supor que
o conjunto de operações descrito no algoritmo é realizado segundo uma ordem
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 4

preestabelecida. Só se inicia uma dada operação, quando a anterior estiver terminada, ou


seja, estamos perante uma execução sequencial das instruções.

A sequência de operações tem necessariamente que ser concluída num número finito de
passos, sob pena da solução, que ela representa, não ser realizável num computador. A
sequência de operações também tem que produzir sempre resultados, ou seja, calcular
valores, senão é inútil.

2.3 Decomposição hierárquica da solução


A melhor estratégia para lidar com a complexidade consiste em implementar a estratégia do
dividir para conquistar. A decomposição hierárquica, também designada decomposição do
topo para a base (Top-Down Decomposition), permite decompor um problema complexo, em
subproblemas menos complexos, de resolução mais fácil que o problema original, até que
seja atingido um nível de decomposição em que a complexidade dos subproblemas seja
tratável. Esta estratégia permite que o programador se possa concentrar na resolução de
cada um dos subproblemas de cada vez, obtendo um maior controlo do grau de abstracção
em cada nível hierárquico e limitando o número de operações existentes em cada
subproblema.

Mas, a maior vantagem da decomposição hierárquica é a de permitir uma maior


flexibilidade na exploração de soluções possíveis. Ou seja, numa solução devidamente
hierarquizada é sempre possível substituir a decomposição de um nível hierárquico por
outra decomposição alternativa, ou simplesmente substituir a solução de um subproblema
por outra solução alternativa.

Para implementar esta estratégia é necessário que a linguagem de programação permita a


decomposição do programa em subprogramas. Acontece que a implementação de um
programa em subprogramas, é a essência do paradigma da programação procedimental.
Pelo que, a implementação da decomposição hierárquica assenta na decomposição do
programa em procedimentos e na abstracção procedimental.

Uma decomposição hierárquica, começa sempre com uma solução inicial, onde são
tomadas as decisões mais importantes, e depois, continua-se a refinar a solução, explorando
eventualmente várias soluções alternativas, à medida que vamos quebrando a complexidade
do problema.

O refinamento progressivo da solução é feito sob três componentes. Passa-se gradualmente


de uma descrição em linguagem natural para uma descrição mais detalhada, algébrica e com
pseudocódigo, à medida das necessidades, com a definição das variáveis internas, a
clarificação das expressões booleanas decisórias ou de terminação de ciclos repetitivos. É
feita a especificação formal de novas operações, ou seja, a criação de novos procedimentos.
E faz-se uma caracterização rigorosa das dependências de informação de forma a
estabelecer o fluxo dos dados através dos procedimentos.

Vamos começar por apresentar os algoritmos em linguagem natural dos problemas, cujas
especificações completas, foram apresentadas anteriormente.

A Figura 2.3 apresenta as três operações necessárias para resolver o problema da conversão
de distâncias de milhas para quilómetros, cuja especificação completa foi apresentada na
Figura 2.1.
5 CAPÍTULO 2 : ALGORITMOS

Primeiro é preciso ler do teclado um valor não negativo que representa a distância em
milhas que se pretende converter, daí a designação de leitura com validação. Depois é
preciso efectuar a conversão, usando para o efeito a fórmula de conversão. Finalmente o
valor calculado é escrito no monitor. Para separar as operações usamos o separador do
Pascal que é o ponto e vírgula (;).

nome: Conversão de distâncias (milhas para quilómetros)


begin
Leitura com validação da distância expressa em milhas;
Conversão da distância de milhas para quilómetros;
Impressão da distância expressa em quilómetros;
end
Figura 2.3 - Algoritmo em linguagem natural da conversão de distâncias.
A Figura 2.4 apresenta as três operações necessárias para resolver o problema da equação
de 2º grau, cuja especificação completa foi apresentada na Figura 2.2. Primeiro é preciso ler
do teclado os coeficientes da equação, tendo em conta que o coeficiente A não pode ser
nulo. Depois são determinadas as raízes da equação usando a fórmula resolvente, que são
por fim escritas no monitor.

nome: Resolução da equação de 2º grau


begin
Leitura com validação dos coeficientes da equação (A, B, C);
Determinação das raízes da equação (A, B, C, X1R, X1I, X2R, X2I);
Impressão das raízes (X1R, X1I, X2R, X2I);
end

Figura 2.4 - Algoritmo em linguagem natural da resolução da equação de 2º grau.


Mesmo num algoritmo delineado em linguagem natural, é fundamental explicitar-se
claramente as dependências de informação. Pelo que, ao especificar-se uma operação, deve
indicar-se entre parêntesis curvos, quais são as variáveis que transportam informação de e
para as operações, de forma a estabelecer o fluxo dos dados através das operações, que
mais tarde darão origem a procedimentos. Tal como foi feito neste último exemplo.

Qualquer um dos algoritmos apresentados ainda está longe do algoritmo desejável para
passar à fase de codificação em Pascal. Por exemplo, a primeira operação de leitura com
validação tem que ser melhor explicitada. O que é validar uma variável? E a parte de
conversão ou de cálculo deve ser explicitada através duma solução algébrica. No entanto,
esta primeira descrição algorítmica, ou descrição algorítmica de primeiro nível, dá-nos já
uma ideia das operações fundamentais do programa. Agora temos que pegar em cada uma
das operações deste primeiro nível e refiná-las, usando para o efeito uma linguagem
algébrica e pseudocódigo, obtendo assim algoritmos mais rigorosos de segundo nível.

2.4 Regras gramaticais do pseudocódigo


Porque é que devemos usar pseudocódigo em vez da linguagem de programação
propriamente dita? Porque, quando pensamos um algoritmo, devemos abstrair-nos da
linguagem de programação e concentrarmo-nos no método de resolução. Mas, para se
garantir que a passagem do algoritmo para o programa se faça de uma forma natural, as
regras gramaticais do pseudocódigo de descrição devem ser muito semelhantes às da
linguagem de programação que se vai utilizar. Vamos portanto, agora definir as regras do
pseudocódigo que vamos utilizar ao longo deste texto.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 6

Cada componente da descrição algorítmica deve ser composta por (ver Figura 2.5): um
título; o tipo de operação, função ou procedimento, quando se implementa o componente
como um subprograma, caso contrário é omitido; a descrição das variáveis de entrada e de
saída; e a sequência de operações entre begin e end. A descrição das variáveis de entrada e
de saída pode ser omitida se forem devidamente descritas na especificação completa.

nome: título (variáveis de entrada , variáveis de saída)

tipo de operação: função ou procedimento

variáveis de entrada: lista de variáveis de entrada e sua descrição

variáveis de saída: lista de variáveis de saída e sua descrição

begin
decomposição da operação em termos de operações mais simples
end

Figura 2.5 - Descrição da operação.


Vamos introduzir agora o conceito de operação composta, ou bloco. Uma operação
composta consiste num agrupamento de uma sequência de operações simples, separadas
por ponto e vírgula (;), entre as palavras begin e end (ver Figura 2.6).

begin
sequência de operações simples
end

Figura 2.6 - Definição da operação composta.


Existem dois tipos de operações de tomada de decisão: a operação decisória binária e a
operação decisória múltipla. A operação decisória binária pode ter as duas formas
apresentadas na Figura 2.7. Uma condição booleana é uma expressão lógica, cujo valor é
VERDADEIRO ou FALSO. No Pascal, a condição booleana não necessita de ser escrita
entre parêntesis curvos, quando é uma condição booleana simples. Mas existem linguagens
de programação, como o C, em que tal é obrigatório. Por isso, nas descrições algorítmicas
em pseudocódigo apresentadas ao longo deste texto, iremos usar sempre esta regra.

if ( condição booleana )
then operação simples ou composta;
─────────────────────────────────────────────────────────────────────
if ( condição booleana )
then operação simples ou composta
else operação simples ou composta;

Figura 2.7 - Definição da operação decisória binária.


Na primeira forma, se o valor da condição booleana for VERDADEIRO, a instrução
simples ou composta, colocada imediatamente a seguir ao separador then, é efectuada, caso
contrário, nada é feito. Na segunda forma, estabelece-se uma realização alternativa de
operações, sendo seleccionada a operação colocada a seguir ao separador then, quando o
valor da condição booleana for VERDADEIRO, ou a operação colocada a seguir ao
separador else, quando o valor da condição booleana for FALSO.

A operação decisória múltipla é apresentada na Figura 2.8. A expressão enumerada,


significa que é uma expressão que pode assumir um número de valores passíveis de
enumeração, ou seja, dado um valor particular, é sempre possível determinar-se o valor que
7 CAPÍTULO 2 : ALGORITMOS

surge imediatamente antes, e o que surge imediatamente depois. A realização da operação


supõe o cálculo prévio da expressão enumerada. Depois, o grupo de alternativas específicas
é pesquisado para se determinar se existe alguma, cuja lista de valores contenha o valor
actual da expressão. Em caso afirmativo, a operação simples ou composta que lhe está
associada, é efectuada. Caso contrário, e se existir alternativa por defeito, a operação
simples ou composta que lhe está associada, é efectuada. Caso nada disto se verifique, nada
é feito e a operação não executa qualquer acção. O identificador de alternativa por defeito
usado pela maioria dos compiladores de Pascal é o otherwise. No entanto, alguns
compiladores, como por exemplo, o Turbo Pascal, usam o else.

case expressão enumerada of


lista de valores: operação simples ou composta;
...
lista de valores: operação simples ou composta;
[otherwise] else operação simples ou composta;
end;

Figura 2.8 - Definição da operação decisória múltipla.


Existem dois tipos de operações de repetição. A operação cujo número de iterações é
previamente conhecido e a operação cujo número de iterações é previamente
desconhecido. A primeira é apresentada na Figura 2.9.

for variável contadora := valor inicial to valor final do


operação simples ou composta;
────────────────────────────────────────────────────────────────────
for variável contadora := valor inicial downto valor final do
operação simples ou composta;

Figura 2.9 - Definição da operação repetitiva cujo número de iterações é previamente conhecido.
A operação simples ou composta que constitui o núcleo repetitivo da operação for é
efectuada um número de vezes igual à distância positiva, mais um, entre o valor inicial e o
valor final. O sentido de variação é crescente para a operação for … to … do, e,
decrescente, para a operação for … downto … do. A variável contadora vai
sucessivamente assumindo, em cada iteração, um dos valores do intervalo especificado,
começando pelo valor inicial e terminando no valor final. As operações que formam o
núcleo repetitivo, podem referenciar esta variável, mas em nenhum caso podem modificar
o seu valor.

Existem duas variantes de operações repetitivas cujo número de iterações é previamente


desconhecido (ver Figura 2.10).

repeat
sequência de operações simples
until ( condição booleana );
─────────────────────────────────────────────────────────────────────
while ( condição booleana ) do
operação simples ou composta;

Figura 2.10 - Definição das operações repetitivas cujo número de iterações é previamente
desconhecido.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 8

A sequência de operações simples que constitui o núcleo repetitivo da operação repeat é


efectuada até que a condição booleana de terminação se torne VERDADEIRA. A
operação simples ou composta que constitui o núcleo repetitivo da operação while, é
efectuada enquanto esta condição não for FALSA. No primeiro caso, o núcleo repetitivo é
efectuado, pelo menos, uma vez, enquanto que, no segundo, o núcleo repetitivo pode
nunca ser efectuado. A correcta utilização destas operações exige que se tenha alguns
cuidados. A condição booleana de terminação tem que ser inicializada antes da execução da
operação while. Mas, para a operação repeat, isso nem sempre é necessário. E pelo menos
uma das operações do núcleo repetitivo tem que afectar o valor da condição booleana de
terminação, caso contrário a operação repetitiva pode repetir-se indefinidamente.

Numa descrição algorítmica há quase sempre a necessidade de ler dados do teclado e de


escrever resultados no monitor. Para tal efeito vamos usar as operações apresentadas na
Figura 2.11. Para efectuar a leitura do valor de uma variável de entrada introduzida pelo
teclado temos as operações read e readln. A diferença será apresentada mais tarde e por
agora vamos usar sistematicamente a operação readln. Para escrever o valor de uma
variável de saída no monitor temos a operação write, ou em alternativa, a operação writeln
se quisermos mudar de linha após a escrita. Em alternativa a escrever variáveis ou em
conjunção com a escrita de variáveis, as operações de escrita podem ser usadas para
escrever mensagens, ou seja, sequências de caracteres, desde que inseridas entre pelicas.
Para separar a escrita de mensagens, da escrita de variáveis usa-se a vírgula (,). A operação
writeln sem mais nada significa mudar de linha no monitor.

read (lista de variáveis de entrada);

readln (lista de variáveis de entrada);


─────────────────────────────────────────────────────────────────────
write (lista de variáveis de saída e/ou mensagens);

writeln (lista de variáveis de saída e/ou mensagens);

Figura 2.11 - Definição das operações de leitura do teclado e escrita no monitor.


A Figura 2.12 apresenta os algoritmos em pseudocódigo, das operações de primeiro nível
do problema da conversão de distâncias, anteriormente apresentadas na Figura 2.3.

nome: Leitura com validação da distância expressa em milhas (MILHAS)


begin
repeat
write ('Distância em milhas? ');
readln (MILHAS);
until (MILHAS >= 0.0);
end

nome: Conversão da distância (MILHAS, QUILOMETROS)


begin
QUILOMETROS := 1.609 * MILHAS;
end

nome: Impressão da distância expressa em quilómetros (QUILOMETROS)


begin
writeln ('Distância em quilómetros é ? ' , QUILOMETROS);
end

Figura 2.12 - Algoritmos de segundo nível da conversão de distâncias.


9 CAPÍTULO 2 : ALGORITMOS

Para obter uma leitura com validação, é necessário repetir a leitura do valor introduzido
pelo teclado, ou seja da variável de entrada MILHAS, até que o valor lido seja maior ou
igual a zero. Para tal efeito, utiliza-se a instrução repeat ... until a abraçar as instruções de
escrita da mensagem e de leitura da variável. Para criar uma melhor interface entre o
programa e o ser humano que vai utilizar o programa, todos os valores de entrada a ser
inseridos pelo teclado, devem ser precedidos por uma mensagem a informar o utilizador.
Essa mensagem deve ser sucinta e elucidativa dos valores que o programa está à espera. Na
escrita dos valores de saída, para além da escolha de um formato de saída adequado para
cada valor, estes também devem ser acompanhados por uma mensagem, também ela
sucinta e suficientemente explicativa da informação que está a ser escrita no monitor. Por
agora, vamos omitir os formatos de escrita dos valores de saída. Na conversão da distância,
aquando do cálculo da variável de saída QUILOMETROS, usa-se o operador de atribuição
do Pascal := em vez do operador = usado na expressão matemática da especificação
completa. Atribuir um valor a uma variável, significa armazenar um valor na célula de
memória usada para guardar a variável.

A Figura 2.13 apresenta os algoritmos em pseudocódigo, das operações de primeiro nível


do problema da resolução da equação de 2º grau, anteriormente apresentadas na Figura 2.4.

nome: Leitura com validação dos coeficientes da equação (A, B, C)


begin
writeln ('Resolução da equação de 2º grau Ax^2+Bx+C = 0');
repeat
write ('A = ');
readln (A);
until (A <> 0.0);
write ('B = ');
readln (B);
write ('C = ');
readln (C);
end

nome: Determinação das raízes da equação (A, B, C, X1R, X1I ,X2R, X2I)
begin
DELTA := sqr (B) - 4 * A * C;
if (DELTA >= 0.0)
then begin
X1R := (-B + sqrt (DELTA)) / (2 * A);
X1I := 0.0;
X2R := (-B - sqrt (DELTA)) / (2 * A);
X2I := 0.0;
end
else begin
X1R := -B / (2 * A);
X1I := sqrt (-DELTA) / (2 * A);
X2R := X1R;
X2I := -X1I;
end
end

nome: Impressão das raízes (X1R, X1I, X2R, X2I)


begin
writeln ('X1 = ' , X1R , ' +i ' , abs(X1I));
writeln ('X2 = ' , X2R , ' -i ' , abs(X2I));
end

Figura 2.13 - Algoritmos de segundo nível da resolução da equação de 2º grau.


PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 10

O algoritmo de leitura dos coeficientes da equação de 2º grau, é muito semelhante ao do


problema anterior, mas apenas o coeficiente A precisa de ser validado. No algoritmo da
determinação das raízes da equação, a primeira instrução consiste no cálculo do
discriminante da equação. Para efectuar tal cálculo recorre-se à função sqr (square) que
calcula o quadrado do coeficiente B. Se o discriminante for positivo ou nulo calculam-se
duas raízes reais, daí que X1I e X2I são ambas zero. Se pelo contrário, o discriminante for
negativo então calculam-se duas raízes complexas conjugadas, daí que as partes reais são
iguais e as partes imaginárias são simétricas. Para calcular a raiz quadrada existe a função
sqrt (square root). No algoritmo de impressão dos resultados, estamos por agora a omitir os
formatos de escrita. Por uma questão de pormenor estético e para evitar situações do tipo
em que apareça –i ou +i seguido de uma parte imaginária negativa, apresentam-se as partes
imaginárias em valor absoluto, recorrendo para o efeito à função abs (absolute).

Como estes algoritmos de segundo nível já estão muito próximos do Pascal, já podemos
fazer o algoritmo completo do problema. Nesta fase inicial, em que ainda não foram
apresentados os subprogramas, o algoritmo final do problema consiste na fusão dos
algoritmos parcelares, eliminando obviamente as instruções begin e end que encapsulam
cada algoritmo parcelar. A Figura 2.14 apresenta o algoritmo final no caso do problema da
conversão de distâncias. Compare-o com os algoritmos parcelares da Figura 2.12. Repare
que se separa as partes importantes do programa com um comentário, que é nada mais,
nada menos, que os nomes da operações do algoritmo de primeiro nível.

nome: Conversão de distâncias (milhas para quilómetros)


begin

(* Leitura com validação da distância expressa em milhas *)


repeat
write ('Distância em milhas? ');
readln (MILHAS);
until (MILHAS >= 0.0);

(* Conversão da distância de milhas para quilómetros *)


QUILOMETROS := 1.609 * MILHAS;

(* Impressão da distância expressa em quilómetros *)


writeln ('Distância em quilómetros é ? ' , QUILOMETROS);

end

Figura 2.14 - Algoritmo final em pseudocódigo da conversão de distâncias.


Como exercício de treino, faça o mesmo para o problema da resolução da equação de 2º
grau, apresentado na Figura 2.13.

2.5 Modelos de algoritmos


Quando temos um problema para programar, somos confrontados com vários algoritmos
possíveis. Às vezes, o algoritmo é induzido pelo próprio enunciado. No entanto, o
algoritmo global de um programa, ou seja, a decomposição da solução do problema,
enquadra-se num de três cenários possíveis:
• Modelo básico.
• Modelo básico modificado à entrada.
• Modelo básico modificado à saída.
11 CAPÍTULO 2 : ALGORITMOS

2.5.1 Modelo básico


Os dois exemplos apresentados até agora enquadram-se no tipo de algoritmo que
corresponde directamente à descrição funcional do que é um computador, e de como ele
executa um programa. Ou seja, existem três etapas: leitura dos valores das variáveis de
entrada, caso elas existam; processamento, ou seja, o cálculo das variáveis de saída a partir
das variáveis de entrada; escrita dos valores das variáveis de saída. Este é o modelo básico
de decomposição da solução de um problema, e sempre que pudermos, devemos utilizá-lo,
pois é o modelo mais fácil de implementar em procedimentos.

2.5.2 Modelo básico modificado à entrada


Quando, a leitura de dados está enquadrada num processo repetitivo, em que há de alguma
maneira acumulação de valores, sendo lidos para algumas variáveis de entrada diferentes
valores ao longo do tempo, temos uma decomposição algorítmica baseada no modelo
básico, mas modificado à entrada. Este modelo tem as seguintes quatro etapas: leitura dos
valores de algumas variáveis de entrada (esta etapa pode não existir); leitura dos valores das
restantes variáveis de entrada e processamento de algumas variáveis internas e de saída;
processamento complementar de algumas variáveis de saída; escrita dos valores das
variáveis de saída.

Vamos agora apresentar um exemplo deste tipo de decomposição da solução de um


problema. Pretende-se escrever um programa que dado um número indeterminado de
números introduzidos pelo teclado, até que apareça o número zero como indicador de
paragem, calcule a média dos números lidos e escreve-a no monitor.

O enunciado diz-nos que vai existir um processo de leitura que terminará apenas quando
for introduzido um valor de paragem, neste caso o zero. Logo, não podemos saber à
partida quantos valores vão de facto ser lidos. Pelo que, não podemos declarar variáveis de
entrada para armazenar toda a informação lida. A solução passa então por ler um valor para
a variável de entrada e processá-lo de imediato, de modo a poder reutilizar a variável.
Como para calcularmos a média de um conjunto de números, temos que somá-los e contá-
los primeiro, então o que temos de fazer é ir somando e contando os números lidos, com
excepção do número de paragem, durante o processo repetitivo de leitura. Quando a leitura
terminar, e partindo do princípio que foram de facto lidos números para além do número
de paragem, poderemos então calcular a média e imprimi-la no monitor. As Figura 2.15 e
Figura 2.16 apresentam respectivamente a especificação completa e o algoritmo de
primeiro nível em linguagem natural.

Variável de entrada: NUMERO (número lido)


valor numérico real

Variável de saída: MEDIA (média calculada dos números lidos)


valor numérico real representado com 1 casa decimal

Variáveis internas: SOMA (soma acumulada dos números lidos)


valor numérico real
N (contador dos números lidos)
valor numérico inteiro ou nulo

Solução: MEDIA = SOMA / N (se N ≠ 0)

Figura 2.15 - Especificação completa do cálculo da média de um conjunto de números.


PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 12

nome: Cálculo da média de um conjunto de números


begin
Leitura de um conjunto de números, sua soma e contagem (SOMA, N);
se há números para calcular a média?
então 1º Cálculo da média (SOMA, N, MEDIA);
2º Impressão da média (MEDIA);
senão Impressão de uma mensagem de erro;
end

Figura 2.16 - Algoritmo em linguagem natural do cálculo da média de um conjunto de números.


Durante o processo de leitura, os números são contados e somados respectivamente nas
duas variáveis internas SOMA e N. São essas variáveis que trazem de facto informação útil
para o resto do programa, pelo que, a variável de leitura NUMERO não é passada para o
exterior da operação. Se efectivamente, tiverem sido lido números, ou seja, se N for maior
do que zero, então calculamos a média e escrevemos o seu valor no monitor. Senão,
escrevemos no monitor uma mensagem de erro a dizer que não foi lido qualquer número
válido, ou seja, qualquer número para além do zero.

A Figura 2.17 apresenta os algoritmos de segundo nível, em pseudocódigo, das operações


do problema do cálculo da média de um conjunto de números.

nome: Leitura de um conjunto de números, sua soma e contagem (SOMA, N)


begin
SOMA := 0.0;
N := 0;
repeat
write ('Introduza um número? ');
readln (NUMERO);
if (NUMERO <> 0.0)
then begin
SOMA := SOMA + NUMERO;
N := N + 1;
end
until (NUMERO = 0.0);
end

nome: Cálculo da média (SOMA, N, MEDIA)


begin
MEDIA := SOMA / N;
end

nome: Impressão da média (MEDIA)


begin
writeln ('Média dos números lidos = ' , MEDIA);
end

nome: Impressão de uma mensagem de erro


begin
writeln ('Não foi introduzido qualquer número');
end

Figura 2.17 - Algoritmos de segundo nível do cálculo da média de um conjunto de números.


Na operação de leitura, para fazermos a soma acumulada dos números lidos temos que
pegar no valor da soma actual armazenado na variável SOMA, somar-lhe o número lido
que está na variável NUMERO e armazenar o novo valor calculado na mesma variável
SOMA. Ou seja, atribuir à variável SOMA o valor obtido pela adição de SOMA com
NUMERO.
13 CAPÍTULO 2 : ALGORITMOS

Este tipo de variável designa-se por variável acumuladora, e para que funcione bem temos
que assegurar que o valor inicialmente armazenado na variável é zero. Pelo que, temos de a
inicializar a zero, e como ela é real usamos o valor 0.0, para aumentar a legibilidade do
programa. Para contarmos os números lidos temos que pegar na variável N e acumular
uma unidade por cada número lido, o que se designa por incrementar a variável.
Obviamente, esta variável também precisa de ser inicializada a zero. Quando o valor de
paragem é lido, temos que proteger a operação de contagem para não fazermos uma
contagem incorrecta. Já que, este valor destina-se apenas a parar a leitura e não deve
interferir no cálculo da média. A protecção da operação de soma acumulada é desnecessária
neste caso, porque o zero é o elemento neutro da adição, mas é feita apenas por uma
questão de eficiência, evitando uma adição inútil. Para proteger estas duas instruções usa-se
a operação decisória binária if. O operador desigualdade em Pascal é <>. A Figura 2.18
apresenta o algoritmo final em pseudocódigo.

nome: Cálculo da média de um conjunto de números


begin

(* Leitura de um conjunto de números, sua soma e contagem *)


SOMA := 0.0;
N := 0;
repeat
write ('Introduza um número? ');
readln (NUMERO);
if (NUMERO <> 0.0)
then begin
SOMA := SOMA + NUMERO;
N := N + 1;
end
until (NUMERO = 0.0);

if (N > 0) (* foram lidos números? *)


then begin
(* Cálculo da média *)
MEDIA := SOMA / N;

(* Impressão da média *)
writeln ('Média dos números lidos = ' , MEDIA);
end
else writeln ('Não foi introduzido qualquer número');

end

Figura 2.18 - Algoritmo final em pseudocódigo do cálculo da média de um conjunto de números.

2.5.3 Modelo básico modificado à saída


Quando, a escrita de resultados está enquadrada num processo repetitivo, em que há de
alguma maneira produção sucessiva de valores, sendo atribuídos a algumas variáveis de
saída diferentes valores ao longo do tempo, temos uma decomposição algorítmica baseada
no modelo básico, mas modificado à saída. Este modelo tem as seguintes quatro etapas:
leitura dos valores das variáveis de entrada; processamento preliminar de algumas variáveis
de saída; processamento e escrita dos valores de algumas variáveis de saída; escrita dos
valores das restantes variáveis de saída (esta etapa pode não existir).

Vamos agora apresentar um exemplo deste tipo de decomposição da solução de um


problema.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 14

Pretende-se escrever um programa que desenhe no monitor um triângulo a cheio, com as


dimensões largura e altura lidas do teclado, expressas em número de caracteres de
impressão, usando para o efeito o carácter * (ver a Figura 2.19). O triângulo deve ficar
centrado, assumindo para o efeito que o número máximo de linhas da janela de
visualização é representado pelo parâmetro NLMAX, e, o número máximo de colunas da
janela de visualização é representado pelo parâmetro NCMAX. Estes parâmetros de valor
constante designam-se por constantes. Também são armazenadas na memória, mas o seu
valor não pode ser alterado durante a execução do programa. Este tipo de problema, cujo
objectivo é desenhar figuras, ou gráficos, no monitor, normalmente tem variáveis de
entrada com valores limitados pelas dimensões da janela de visualização, e, não tem
propriamente variáveis de saída. Os valores de saída calculados durante a execução do
programa, são apresentados no monitor sob a forma gráfica, usando para o efeito um
carácter, neste caso o asterisco. Cada linha da figura exige o cálculo do número de
asteriscos, a sua escrita no monitor e muda-se de linha, de forma a colocar o cursor no
início da linha seguinte.

NLB

altura ( NL )
NCB

largura ( NC )

Figura 2.19 - Desenho de um triângulo centrado.


A Figura 2.20 e a Figura 2.21 apresentam respectivamente a especificação completa e o
algoritmo em linguagem natural. A leitura das dimensões do triângulo exige a sua validação
em função das constantes acima mencionadas. Para centrar o triângulo, temos que calcular
o número de linhas e de colunas em branco, que sobram para além das dimensões do
triângulo, e que serão distribuídas antes e depois da impressão dos asteriscos. Designamos
esta operação por caracterização espacial da figura. Depois da obtenção destes valores de
centragem, podemos passar à impressão da figura, que neste caso significa, desenhar toda a
janela, ou seja, o triângulo e o espaço envolvente.

As constantes não são mencionadas nas dependências de informação, já que normalmente


são usadas como informação global do programa.
15 CAPÍTULO 2 : ALGORITMOS

Variáveis de entrada: NL (altura do triângulo)


valor numérico positivo e inferior à altura máxima da janela (NLMAX)
NC (largura do triângulo)
valor numérico positivo e inferior à largura máxima da janela (NCMAX)

Variável de saída: NAST (número de asteriscos da linha)


valor numérico inteiro positivo

Variáveis internas: NLB (número de linhas em branco)


valor numérico inteiro positivo ou nulo
NCB (número de colunas em branco)
valor numérico inteiro positivo ou nulo
LINHA (linha onde se está a escrever os *)
valor numérico inteiro positivo
NCAR (contador de caracteres a escrever)
valor numérico inteiro positivo ou nulo

Solução: Desenhar um triângulo

Figura 2.20 - Especificação completa do desenho de um triângulo centrado.

nome: Desenho de um triângulo centrado


begin
Leitura com validação da altura e da largura do triângulo (NL, NC);
Caracterização espacial da figura (NL, NC, NLB, NCB);
Impressão da figura (NL, NC, NLB, NCB);
end

Figura 2.21 - Algoritmo em linguagem natural do desenho de um triângulo centrado.


A Figura 2.22 apresenta os algoritmos de segundo nível, em pseudocódigo e linguagem
natural, das operações do problema do desenho de um triângulo centrado.

nome: Leitura com validação da altura e da largura do triângulo(NL,NC)


begin
repeat
write ('Altura do triângulo? ');
readln (NL);
until (NL > 0) and (NL <= NLMAX);

repeat
write ('Largura do triângulo? ');
readln (NC);
until (NC > 0) and (NC <= NCMAX);
end

nome: Caracterização espacial da figura (NL, NC, NLB, NCB)


begin
Cálculo da centragem vertical (NL, NLB);
Cálculo da centragem horizontal (NC, NCB);
end

nome: Impressão da figura (NL, NC, NLB, NCB)


begin
Impressão das linhas em branco (NLB);
Impressão do triângulo (NL, NC, NCB);
Impressão das linhas em branco (NLB);
end

Figura 2.22 - Algoritmos de segundo nível do desenho de um triângulo centrado.


PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 16

Na operação de leitura, a validação das dimensões do triângulo, implica que têm de ser
maiores do que zero, e (and) não podem exceder as dimensões máximas da janela de
visualização. É preciso ter em consideração que, quando pretendemos usar uma condição
booleana composta, cada uma das condições booleanas simples, tem que ser
obrigatoriamente inserida entre parêntesis curvos. A caracterização espacial da figura
implica o cálculo da centragem vertical NLB e da centragem horizontal NCB. A impressão
da figura, ou seja, do triângulo e do espaço envolvente, implica escrever NLB linhas em
branco, seguido do triângulo, seguido de NLB linhas em branco. A operação de impressão
das linhas em branco é a mesma, independentemente do sítio onde vai ser utilizada, antes
ou depois do triângulo.

As operações que se encontram definidas em linguagem natural, ainda não são operações
elementares e por isso serão objecto de uma análise mais detalhada num terceiro nível
hierárquico, que é apresentado na Figura 2.23.

nome: Cálculo da centragem vertical (NL, NLB)


begin
NLB := (NLMAX - NL) div 2;
end

nome: Cálculo da centragem horizontal (NC, NCB)


begin
NCB := (NCMAX - NC) div 2;
end

nome: Impressão das linhas em branco (NLB)


begin
for LINHA := 1 to NLB do writeln;
end

nome: Impressão do triângulo (NL, NC, NCB)


begin
for LINHA := 1 to NL do
begin
Cálculo do número de asteriscos da linha (LINHA, NL, NC, NAST);
Impressão dos espaços (NCB);
Impressão dos asteriscos (NAST);
Mudar de linha;
end
end

Figura 2.23 - Algoritmos de terceiro nível do desenho de um triângulo centrado.


A centragem vertical NLB depende de NL e NLMAX, e é igual a metade da diferença entre
o número máximo de linhas da janela e a altura do triângulo. Por sua vez, a centragem
horizontal NCB depende de NC e NCMAX, e é igual a metade da diferença entre o
número máximo de colunas da janela e a largura do triângulo. Para fazermos a divisão
inteira o Pascal providencia o operador div.

Para escrever NLB linhas em branco, temos que mudar de linha, ou seja, fazer writeln,
num ciclo repetitivo de tipo for. Para desenhar o triângulo temos que escrever as suas
linhas num processo repetitivo. Para cada linha, temos que calcular o número de asteriscos
NAST, escrever NCB espaços antes de começarmos a escrever os NAST asteriscos e
finalmente muda-se de linha. Repare que não é preciso escrever outra vez NCB espaços
depois dos asteriscos para centrar horizontalmente o triângulo. Vamos agora analisar na
Figura 2.24 estas operações do quarto nível hierárquico.
17 CAPÍTULO 2 : ALGORITMOS

nome: Cálculo do número de asteriscos da linha (LINHA, NL, NC, NAST)


begin
NAST := round ( (LINHA - 1) / (NL - 1) * (NC - 1) + 1 );
end

nome: Impressão dos espaços (NCB)


begin
for NCAR := 1 to NCB do write (' ');
end

nome: Impressão dos asteriscos (NAST)


begin
for NCAR := 1 to NAST do write ('*');
end

Figura 2.24 - Algoritmos de quarto nível do desenho de um triângulo centrado.


As operações de escrita dos espaços e dos asteriscos são formalmente idênticas. Estamos
perante um ciclo repetitivo, cujo número de repetições é previamente conhecido. A
diferença entre as duas operações consiste no carácter a escrever no monitor, e no número
de repetições a efectuar. Ao desenvolver estas operações deparamo-nos com uma nova
variável interna, designada por NCAR, que serve de contador do número de caracteres a
escrever nos ciclos for.

A parte difícil do algoritmo é o cálculo do número de asteriscos de cada linha. Para isso
temos que recorrer à equação da recta, definida pelos dois pontos (coluna 1, linha 1) e
(coluna NC, linha NL). O que dá o declive (NL - 1) / (NC - 1). De notar que nesta figura o
eixo dos x é ao longo das colunas da janela e o eixo dos y é ao longo das linhas da janela.
Aplicando a equação da recta (y = mx + b) e sendo que o y é o número da linha de
impressão, ou seja, LINHA, e o x é o número de asteriscos a calcular, ou seja, NAST, então
obtemos a expressão NAST = (LINHA - 1) / (NL - 1) * (NC - 1) + 1. Como o número de
caracteres a escrever no monitor tem que ser obrigatoriamente um número inteiro, este
valor é arredondado. Para isso usa-se a função round. Com estes algoritmos de quarto
nível, já podemos escrever o algoritmo completo em pseudocódigo do terceiro nível da
impressão do triângulo, que é apresentado na Figura 2.25.

nome: Impressão do triângulo (NL, NC, NCB)


begin
for LINHA := 1 to NL do
begin
(* Cálculo do número de asteriscos da linha *)
NAST := round ( (LINHA - 1) / (NL - 1) * (NC - 1) + 1 );

(* Impressão dos espaços *)


for NCAR := 1 to NCB do write (' ');

(* Impressão dos asteriscos *)


for NCAR := 1 to NAST do write ('*');

(* Mudar de linha *)
writeln;
end
end

Figura 2.25 - Algoritmo completo em pseudocódigo da impressão do triângulo.


PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 18

Na Figura 2.26, apresentamos os algoritmos completos em pseudocódigo, das operações


do segundo nível que faltavam, ou seja, a caracterização espacial da figura e a impressão da
figura.

nome: Caracterização espacial da figura (NL, NC, NLB, NCB)


begin

(* Cálculo da centragem vertical *)


NLB := (NLMAX - NL) div 2;

(* Cálculo da centragem horizontal *)


NCB := (NCMAX - NC) div 2;

end

nome: Impressão da figura (NL, NC, NLB, NCB)


begin

(* Impressão das linhas em branco – antes do triângulo *)


for LINHA := 1 to NLB do writeln;

(* Impressão do triângulo *)
for LINHA := 1 to NL do
begin

(* Cálculo do número de asteriscos da linha *)


NAST := round ( (LINHA - 1) / (NL - 1) * (NC - 1) + 1 );

(* Impressão dos espaços *)


for NCAR := 1 to NCB do write (' ');

(* Impressão dos asteriscos *)


for NCAR := 1 to NAST do write ('*');

(* Mudar de linha *)
writeln;

end

(* Impressão das linhas em branco – depois do triângulo *)


for LINHA := 1 to NLB do writeln;

end

Figura 2.26 - Algoritmos completos em pseudocódigo de segundo nível.


19 CAPÍTULO 2 : ALGORITMOS

Finalmente a Figura 2.27 apresenta o algoritmo completo em pseudocódigo do desenho de


um triângulo centrado.

nome: Desenho de um triângulo centrado


begin

(* Leitura com validação da altura e da largura do triângulo *)

repeat
write ('Altura do triângulo? ');
readln (NL);
until (NL > 0) and (NL <= NLMAX);

repeat
write ('Largura do triângulo? ');
readln (NC);
until (NC > 0) and (NC <= NCMAX);

(* Caracterização espacial da figura *)

(* Cálculo da centragem vertical *)


NLB := (NLMAX - NL) div 2;

(* Cálculo da centragem horizontal *)


NCB := (NCMAX - NC) div 2;

(* Impressão da figura *)

(* Impressão das linhas em branco – antes do triângulo *)


for LINHA := 1 to NLB do writeln;

(* Impressão do triângulo *)
for LINHA := 1 to NL do
begin

(* Cálculo do número de asteriscos da linha *)


NAST := round ( (LINHA - 1) / (NL - 1) * (NC - 1) + 1 );

(* Impressão de espaços *)
for NCAR := 1 to NCB do write (' ');

(* Impressão de asteriscos *)
for NCAR := 1 to NAST do write ('*');

(* Mudar de linha *)
writeln;

end

(* Impressão das linhas em branco – depois do triângulo *)


for LINHA := 1 to NLB do writeln;

end

Figura 2.27 - Algoritmo final em pseudocódigo do desenho de um triângulo centrado.


PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 20

2.6 O problema dos alumínios


Os exemplos apresentados até agora são do domínio da matemática ou envolvem conceitos
matemáticos. Mas, um computador para além de ser uma ferramenta para a programação
de aplicações de cálculo científico, também pode ser usado para a simulação e resolução de
problemas do mundo real. Vamos ver agora, como se pode criar um modelo abstracto para
a simulação de um problema concreto do dia a dia.

O senhor João tem uma empresa de montagem de caixilharias em alumínio. Quando um


cliente lhe encomenda por exemplo uma marquise, ele para fazer as diversas partes
constituintes da marquise, por exemplo uma janela, tem que pegar numa calha do tipo de
perfil de alumínio adequado, que tem um tamanho fixo, e cortar quatro secções, duas
alturas e duas larguras, com as medidas adequadas para depois montar a janela. Quando a
encomenda implica poucos cortes o senhor João consegue optimizar os cortes de maneira a
não desperdiçar muito as calhas de alumínio. Mas, quando o trabalho a realizar implica
muitas calhas de perfil, normalmente acabam por sobrar restos que não vão ser
aproveitados mais tarde, gerando assim desperdícios inúteis.

O senhor João, tem um filho a estudar na universidade e então decidiu comprar-lhe um


computador para ele usar durante o curso, pedindo-lhe em troca que ele fizesse um
programa com as seguintes especificações. O programa deve começar por pedir ao
utilizador, o comprimento das calhas do tipo de perfil de alumínio que vai ser usado, e
depois os comprimentos das secções que são necessárias cortar. O programa deve produzir
um relatório, em que para cada calha de perfil, indica as secções a serem cortadas e a sobra
não aproveitada. No fim, deve indicar o número total de calhas que são precisas e a
eficiência do plano de corte, ou seja, o comprimento total das secções cortadas sobre o
comprimento total das calhas necessárias, em percentagem. Ou em alternativa, 100%
menos o comprimento total das sobras sobre o comprimento total das calhas necessárias.
A eficiência dá-nos uma ideia do desperdício criado no corte das calhas. O programa deve
escolher uma sequência de cortes que aproveite as calhas ao máximo, de maneira a
conseguir uma eficiência o mais próximo possível dos 100%, ou seja, gerar poucas sobras.

Vamos analisar este problema sobre o ponto de vista dos valores de entrada. Primeiro
temos a leitura do comprimento das calhas de perfil, seguida da lista de secções que
pretendemos cortar. Como não sabemos à partida quantos são os cortes a efectuar,
portanto, não podemos declarar variáveis de entrada para armazenar toda a informação
lida. Vamos assumir por uma questão de simplificação que os tamanhos das secções a
cortar são dados em centímetros e são valores exactos, ou seja, valores inteiros. Como um
comprimento é um valor positivo não nulo, então podemos usar o valor zero, como
terminador da leitura. Estamos perante um problema que se enquadra no modelo de
decomposição algorítmica que classificámos de modelo básico modificado à entrada.

Vamos agora analisar este problema sobre o ponto de vista dos valores de saída. Para cada
calha de perfil, temos que indicar os cortes a efectuar, sendo obviamente indiferente a
ordem desses mesmos cortes, e a sobra que não vai ser usada. Como à partida também não
sabemos quantas calhas de perfil vamos usar, então também não podemos declarar
variáveis de saída para armazenar toda a informação calculada. Estamos perante um
problema que se enquadra no modelo de decomposição algorítmica que classificámos de
modelo básico modificado à saída. E, ainda existem dois valores de saída que são
calculados após todo o processamento, e que são o número de calhas que foram usadas e a
eficiência.
21 CAPÍTULO 2 : ALGORITMOS

A Figura 2.28 apresenta a especificação completa do problema. As variáveis de entrada


necessárias para a resolução do problema são óbvias. Temos a variável COMPCALHA
para armazenar o comprimento da calha, e a variável CORTE que vai ler os sucessivos
valores das secções a cortar. As variáveis de saída é que vão depender do algoritmo usado e
portanto das variáveis internas. Por exemplo, vamos precisar da variável interna CALHA
para simular o planeamento dos cortes na calha, armazenando em cada instante o que resta
da calha que está a ser cortada. Quando este resto da calha não puder ser aproveitada,
porque é mais pequena que qualquer secção que precisa ainda de ser cortada, então este
valor é a sobra da calha. Mas, para no fim podermos calcular a eficiência temos que, das
duas uma, ou saber o comprimento total das secções cortadas ou saber o comprimento
total das sobras. Como somar as sobras exige menos cálculos do que somar as secções
cortadas, vamos optar pela segunda possibilidade e para isso precisamos da variável interna
acumuladora SOBRAS. Como variáveis de saída, temos então a variável NCALHAS que
conta o número de calhas que vão ser precisas, e a variável EFICIENCIA para a eficiência.

Variáveis de entrada: COMPCALHA (comprimento da calha de perfil)


valor numérico inteiro positivo
CORTE (comprimento da secção a cortar)
valor numérico inteiro positivo ou nulo (zero indica fim de leitura)

Variáveis de saída: NCALHAS (contador do número de calhas)


valor numérico inteiro positivo ou nulo
EFICIENCIA (eficiência em %)
valor numérico real representado com 1 casa decimal

Variáveis internas: CALHA (calha que está a ser cortada)


valor numérico inteiro positivo ou nulo
SOBRAS (total acumulado das sobras)
valor numérico inteiro positivo ou nulo

Solução: Simular o corte das calhas, calcular o número


total de calhas necessárias e o comprimento total das sobras. A
eficiência é dada por (1 - SOBRAS / ( NCALHAS * COMPCALHA ) ) * 100%

Figura 2.28 - Especificação completa do problema dos alumínios.


A Figura 2.29 apresenta o algoritmo de primeiro nível com as operações básicas. A
primeira operação lê apenas o comprimento da calha de perfil. A operação de simulação
dos cortes, é responsável pela leitura das secções que vão ser cortadas, pela sequenciação
dos cortes e pela escrita da informação relativa a cada calha de perfil que vai ser precisa, ou
seja, os cortes efectuados e a sobra criada. Também tem que calcular o número de calhas
necessárias e acumular as sobras, para poder calcular a eficiência. A operação de impressão
dos resultados finais escreve o número total de calhas gastas e a eficiência no monitor. Esta
operação só é realizada se for usada pelo menos uma calha de perfil, caso contrário é
escrita uma mensagem de erro no monitor.

nome: Problema dos alumínios


begin
Leitura com validação do comprimento das calhas (COMPCALHA);
Simulação dos cortes (COMPCALHA, NCALHAS, SOBRAS);
if (NCALHAS <> 0)
then Impressão dos resultados finais (COMPCALHA, NCALHAS, SOBRAS)
else Impressão de uma mensagem de erro
end

Figura 2.29 - Algoritmo em linguagem natural do problema dos alumínios.


PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 22

A Figura 2.30 apresenta os algoritmos detalhados em pseudocódigo da primeira e da


terceira operações. Na operação de impressão dos resultados finais, primeiro é calculado a
eficiência e depois é escrita no monitor. No entanto, esta variável é supérflua, uma vez que,
como a eficiência é um valor estatístico que é calculado a partir de outras variáveis, pode
ser calculado na operação de escrita, poupando assim uma variável. No entanto, por uma
questão de legibilidade algorítmica, vamos manter o cálculo do valor separado da sua
escrita no monitor.

nome: Leitura com validação do comprimento das calhas (COMPCALHA)


begin
repeat
write ('Comprimento das calhas de perfil? ');
readln (COMPCALHA);
until (COMPCALHA > 0);
end

nome: Impressão dos resultados finais (COMPCALHA, NCALHAS, SOBRAS);


begin
writeln ('Número total de calhas = ' , NCALHAS);
EFICIENCIA = (1 - SOBRAS/(NCALHAS*COMPCALHA))*100;
writeln ('Eficiência = ' , EFICIENCIA , '%');
end

Figura 2.30 - Algoritmos em pseudocódigo das operações de leitura e impressão.


A Figura 2.31 apresenta o algoritmo da operação de processamento em linguagem natural.
O algoritmo segue basicamente o modo de trabalho do senhor João. Pega numa calha
completa e vai cortando as secções pretendidas pela ordem que aparecem na lista, até que
esta tem um comprimento menor do que o corte que se pretende efectuar a seguir. Nessa
altura, pega numa calha nova e continua a efectuar cortes, até esgotar a lista. Quando o
trabalho acabar, das duas uma. Se acabou de pegar numa calha e esta nem chegou a ser
usada, então é arrumada e nem sequer é contabilizada. Caso contrário, o resto da calha é
contabilizado como sobra, ou eventualmente arrumada para posterior utilização, isto
dependendo do seu comprimento. A passagem do algoritmo em linguagem natural da
Figura 2.31, para o algoritmo em pseudocódigo da Figura 2.32 é simples.

nome: Simulação dos cortes (COMPCALHA, NCALHAS, SOBRAS)


begin
Inicializar o acumulador de sobras;
Pegar na primeira calha;
repetir
Ler o comprimento da secção que se pretende cortar;
se o comprimento é válido?
então se a secção a cortar pode ser cortada na calha em uso?
então Cortar a secção pretendida
senão 1º Acumular o resto da calha como sobra;
2º Pegar numa nova calha;
3º Cortar a secção pretendida;
até não existirem mais secções para cortar;
se a calha em uso está intacta?
então Descontar uma calha porque esta não chegou a ser usada
senão Acumular o resto da calha como sobra;
end

Figura 2.31 - Algoritmo em linguagem natural da operação de simulação dos cortes.


23 CAPÍTULO 2 : ALGORITMOS

nome: Simulação dos cortes (COMPCALHA, NCALHAS, SOBRAS)


begin
(* Inicializar o acumulador de sobras *)
SOBRAS := 0;
(* Pegar na primeira calha *)
NCALHAS := 1;
CALHA := COMPCALHA;
write ('Calha nº ', NCALHAS, ' -> ');
repeat
(*Ler o comprimento da secção que se pretende cortar *)
write ('Comprimento da secção a ser cortada? ');
readln (CORTE);
if (CORTE > 0)
then if (CORTE <= CALHA)
then begin
(* Cortar a secção pretendida *)
CALHA := CALHA – CORTE;
write (CORTE , ' + ');
end
else begin
(* Acumular a sobra da calha *)
writeln ('Sobra ' , CALHA);
SOBRAS := SOBRAS + CALHA;
(* Pegar numa nova calha *)
NCALHAS := NCALHAS + 1;
CALHA := COMPCALHA;
write ('Calha Nº ', NCALHAS, ' -> ');
(* Cortar a secção pretendida *)
CALHA := CALHA - CORTE;
write (CORTE , ' + ');
end;
until (CORTE = 0);
if (CALHA = COMPCALHA)
then (* Descontar uma calha porque esta não chegou a ser usada *)
NCALHAS := NCALHAS – 1
else begin
(* Acumular a sobra da última calha *)
writeln ('Sobra ' , CALHA);
SOBRAS := SOBRAS + CALHA;
end
end

Figura 2.32 - Algoritmo em pseudocódigo da operação de processamento.


A operação pegar numa calha completa significa inicializar a variável CALHA com o
comprimento total da calha, escrever no monitor o número da calha que estamos a usar e
contabilizar mais uma calha, ou seja, incrementar o contador de calhas NCALHAS.
Excepção feita à primeira calha, em que a variável em vez de ser incrementada é
inicializada. A inicialização do contador de calhas a 1, e não a 0, implica que no fim temos
que testar se a última calha, chegou de facto a ser cortada, senão o contador tem de se
decrementado de uma unidade. Se isto acontecer teremos uma linha escrita no monitor
sem informação útil que terá de ser ignorada pelo utilizador do programa. A simulação dos
cortes é feita subtraindo ao comprimento da calha em uso, ou seja, à variável CALHA, a
variável CORTE que representa a secção a cortar e escrever esse valor no monitor.
Quando não se podem efectuar mais cortes na calha em uso, o valor da variável CALHA
representa a sobra que é escrita no monitor e é acumulada às sobras anteriores, ou seja, à
variável SOBRAS. Muda-se de linha para que a informação sobre a calha seguinte fique
numa linha nova. A Figura 2.33 apresenta o algoritmo final em pseudocódigo.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 24

nome: Problema dos alumínios


begin
(* Leitura com validação do comprimento das calhas *)
repeat
write ('Comprimento das calhas de perfil? ');
readln (COMPCALHA);
until (COMPCALHA > 0);
(* Simulação dos cortes *)
(* Inicializar o acumulador de sobras *)
SOBRAS := 0;
(* Pegar na primeira *)
NCALHAS := 1;
CALHA := COMPCALHA;
write ('Calha Nº ', NCALHAS, ' -> ');
repeat
(*Ler o comprimento da secção que se pretende cortar *)
write ('Comprimento da secção a ser cortada? ');
readln (CORTE);
if (CORTE > 0)
then begin
then if (CORTE <= CALHA)
then begin
(* Cortar a secção pretendida *)
CALHA := CALHA – CORTE;
write (CORTE , ' + ');
end
else begin
(* Acumular a sobra da calha *)
writeln ('Sobra ' , CALHA);
SOBRAS := SOBRAS + CALHA;
(* Pegar numa nova calha *)
NCALHAS := NCALHAS + 1;
CALHA := COMPCALHA;
write ('Calha Nº ', NCALHAS, ' -> ');
(* Cortar a secção pretendida *)
CALHA := CALHA - CORTE;
write (CORTE , ' + ');
end;
until (CORTE = 0);
if (CALHA = COMPCALHA)
then (* Descontar uma calha porque esta não chegou a ser usada *)
NCALHAS := NCALHAS – 1
else begin
(* Acumular a sobra da última calha *)
writeln (CALHA);
SOBRAS := SOBRAS + CALHA;
end
if (NCALHAS <> 0)
then begin (* Impressão dos resultados finais *)
writeln ('Número total de calhas = ' , NCALHAS);
EFICIENCIA = (1 - SOBRAS/(NCALHAS*COMPCALHA))*100;
writeln ('Eficiência = ' , EFICIENCIA , '%');
end
else (* Impressão de uma mensagem de erro *)
writeln ('Não foi introduzida qualquer secção para cortar');
end

Figura 2.33 - Algoritmo final em pseudocódigo do problema dos alumínios.


25 CAPÍTULO 2 : ALGORITMOS

Este algoritmo tem no entanto um problema. É que não aproveita ao máximo as calhas de
perfil, já que se o corte seguinte não puder ser efectuado na calha em uso, então o resto da
calha é desperdiçado. Mas na prática, quando isto acontece, o senhor João vai procurar na
lista se existe mais à frente um corte que ainda dê para aproveitar o resto da calha. Só que a
nossa solução para computador não contempla tal facto. Porquê? Porque o programa não
sabe em avanço os valores seguintes da lista e portanto, não pode tentar aproveitar a sobra
da calha. Isto deve-se ao facto de até agora considerarmos que um programa só pode ler e
armazenar um valor de cada vez numa variável. Ou seja, estamos a considerar que uma
variável representa apenas uma célula de memória e portanto não pode armazenar mais do
que um valor.

Precisamos então de poder ler do teclado uma lista de valores e armazená-los numa única
variável, para serem posteriormente utilizados num ciclo repetitivo. A esse tipo de variáveis
dá-se o nome de array. Um array é um agregado de células de memória contíguas, que
apesar de ser identificada por um único nome, pode armazenar um número, definido pelo
programador, de valores diferentes simultaneamente. Mas, permitindo o acesso individual a
cada célula de memória, ou seja, ao valor armazenado em cada elemento da variável array.
Para aceder a cada elemento individual, usa-se o nome da variável seguido do índice do
elemento entre parêntesis rectos.

Com este tipo de variáveis, então já podemos ler do teclado os valores das secções que se
pretendem cortar e por conseguinte separar a leitura do processamento. Podemos assim
passar a ter uma decomposição algorítmica do tipo básico, sob o ponto de vista da entrada
de dados. Substituindo na especificação completa do problema, a variável de entrada
CORTE pela variável LISTACORTES de tipo array, podemos então reescrever o algoritmo
principal, ou de primeiro nível hierárquico, tal como é apresentado na Figura 2.34. A
operação de leitura passa a trazer para o programa, para posterior utilização nas operações
seguintes, o comprimento da calha COMPCALHA, as secções que pretendemos cortar
LISTACORTES, bem como o número de secções a cortar N.

nome: Problema dos alumínios


begin
Leitura dos dados de entrada (COMPCALHA, LISTACORTES, N);
Simulação dos cortes (COMPCALHA, LISTACORTES, N, NCALHAS, SOBRAS);
if (NCALHAS <> 0)
then Impressão dos resultados finais (COMPCALHA, NCALHAS, SOBRAS)
else Impressão de uma mensagem de erro
end

Figura 2.34 - Nova versão do algoritmo em linguagem natural do problema dos alumínios.
Agora a leitura de todos os dados de entrada é feita numa única operação, como é
apresentada na Figura 2.35. Uma variável de tipo array, tem sempre associado um número
máximo de elementos que pode armazenar e que foi definido pelo programador. Neste
caso, este valor é representado pela constante LMAX. A leitura dos valores é feita um a
um, indicando para o efeito o índice do elemento que está a ser lido. Vamos para já
considerar que o índice do primeiro elemento é 1. A leitura termina com a introdução do
valor zero, significando que não se pretende introduzir mais dados, tal como na versão
anterior. Ou (or), quando se esgota a capacidade de armazenamento da variável. Daí a
existência das duas condições do ciclo repetitivo. Quando a leitura terminar, é preciso
calcular o número de elementos da variável que contêm informação útil, ou seja, o número
de valores lidos, menos o último, caso ele seja o terminador.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 26

Este contador do número de elementos efectivos do array é um valor numérico inteiro que
é obviamente, maior ou igual a zero e menor ou igual a LMAX. Posteriormente, esta
variável N permite-nos controlar o acesso aos elementos úteis do array.

nome: Leitura dos dados de entrada (COMPCALHA, LISTACORTES, N)


begin
repeat
write ('Comprimento das calhas de perfil? ');
readln (COMPCALHA);
until (COMPCALHA > 0);

N := 0;
repeat
N := N + 1;
write ('Comprimento da secção a ser cortada? ');
readln (LISTACORTES[N]);
until (LISTACORTES[N] = 0) or (N = LMAX);

if (LISTACORTES[N] = 0) then N := N – 1;
end

Figura 2.35 - Nova versão da operação de leitura do problema dos alumínios.


A Figura 2.36 apresenta agora a nova versão do algoritmo de processamento, mais de
acordo com o método de trabalho do senhor João. A diferença consiste em procurar na
lista das secções a cortar, uma ou mais secções que ainda possam aproveitar a sobra da
calha. Caso seja possível, aproveita-se a sobra, senão ela é mesmo descartada. Surgem assim
duas novas operações, que são, aproveitar a sobra e ver se ainda é possível aproveitar esta
sobra, ou seja, procurar na lista de cortes, à frente da secção que se quer cortar, candidatas
a usarem a sobra.

nome:Simulação dos cortes(COMPCALHA, LISTACORTES, N, NCALHAS, SOBRAS)


begin
Inicializar o acumulador de sobras;
Pegar na primeira calha;
repetir
Ler o comprimento da secção que se pretende cortar;
se o comprimento é válido?
então se a secção a cortar pode ser cortada na calha em uso?
então Cortar a secção pretendida
senão se ainda é possível aproveitar esta sobra?
então Aproveitar a sobra
senão 1º Acumular o resto desta calha como sobra;
2º Pegar numa nova calha;
3º Cortar a secção pretendida;
até não existirem mais secções para cortar;
se a calha em uso está intacta?
então Descontar uma calha porque esta não chegou a ser usada
senão Acumular o resto desta calha como sobra;
end

Figura 2.36 - Nova versão da operação de processamento do problema dos alumínios.


A questão que se coloca agora é, qual o algoritmo de pesquisa que devemos usar.
Basicamente temos três estratégias. A primeira e mais simples consiste em procurar no
resto da lista a primeira secção que pode ser cortada a partir da sobra. Este algoritmo tem o
nome de a primeira que serve (first fit). A segunda, consiste em aproveitar ao máximo a
sobra e portanto, procurar no resto da lista a maior secção que ainda pode ser cortada a
partir da sobra, com o objectivo de minimizar o desperdício. Este algoritmo tem o nome
27 CAPÍTULO 2 : ALGORITMOS

de a melhor que serve (best fit). A terceira, consiste em aproveitar ao mínimo a sobra e
portanto, procurar no resto da lista a menor secção que ainda pode ser cortada a partir da
sobra, com o objectivo de maximizar o desperdício, para permitir ainda aproveitar a sobra
restante. Este algoritmo tem o nome de a pior que serve (worst fit). É preciso ter em
consideração, que o algoritmo first fit é dependente da ordem dos valores da lista, enquanto
que os outros dois não o são, pelo que, devem produzir melhores resultados. Os algoritmos
best fit e worst fit classificam-se como algoritmos heurísticos.

Do lado da saída do programa, não podemos fazer praticamente nada para separar o
processamento da escrita dos resultados. Agora, o que podemos fazer é criar de facto um
relatório da simulação do programa. Na actual versão, os resultados são escritos no
monitor, o que pode ser um problema. Como a janela de visualização é limitada e o
relatório pode exceder o número máximo de linhas de visualização, então parte do relatório
pode ficar irremediavelmente perdido e obrigar a nova simulação. A solução passa por
enviar o relatório para a memória de massa através da utilização de um ficheiro de texto.
Depois este ficheiro pode ser lido, analisado e eventualmente enviado para uma impressora
usando um dos editores de texto disponibilizados pelo sistema operativo.

Esta solução, com saída para ficheiro e a análise dos três algoritmos de pesquisa sequencial
mencionados, será objecto de estudo mais tarde, quando se estudar as variáveis
estruturadas de tipo array e os ficheiros de texto na linguagem Pascal.

2.7 Exercícios
Para os exercícios descritos a seguir, aplique a análise algorítmica que foi empregue neste
capítulo. Comece por fazer a especificação completa do problema e o algoritmo de
primeiro nível, com as operações principais em linguagem natural. Depois, pegue em cada
uma dessas operações e detalhe os seus algoritmos em pseudocódigo. Finalmente, junte os
algoritmos das operações e faça o algoritmo final do problema em pseudocódigo.

1. Pretende-se escrever um programa que dada uma temperatura em graus Celsius, que é
lida do teclado, converte-a para graus Fahrenheit e escreve-a no monitor. A fórmula de
conversão é F = 1.8 * C + 32.

2. Pretende-se escrever um programa que dado o raio de um círculo, que é lido do


teclado, calcula e escreve o seu perímetro e a sua área, no monitor. Utilize para PI o valor
3.14159.

3. Pretende-se escrever um programa que dadas as dimensões de um rectângulo, que são


lidas do teclado, calcula e escreve o seu perímetro e a sua área, no monitor.

4. Pretende-se escrever um programa que lê um número real, lê um carácter que


representa uma operação aritmética ( + - * / ), lê outro número real, faz a operação
aritmética pretendida e escreve o resultado no monitor com o seguinte formato.
operando 1 operação operando 2 = resultado

5. Pretende-se escrever um programa que dado um número indeterminado de números


inteiros positivos introduzidos pelo teclado, até que apareça o número zero como indicador
de paragem, determina e escreve o maior dos números lidos no monitor.
PROGRAMAÇÃO ESTRUTURAS DE DADOS E ALGORITMOS EM PASCAL 28

6. Pretende-se escrever um programa que dado um número indeterminado de números


inteiros positivos introduzidos pelo teclado, até que apareça o número zero como indicador
de paragem, determina e escreve o menor dos números lidos no monitor.

7. Pretende-se escrever um programa que dado um número indeterminado de números


inteiros positivos introduzidos pelo teclado, até que apareça o número zero como indicador
de paragem, verifique se os números lidos constituem uma sequência contínua de números,
com um espaçamento de 5 unidades, ou seja, se cada número é igual ao anterior mais 5. Se
a sequência for contínua, o programa escreve no monitor a mensagem “A sequência de
números é contínua”, senão escreve a mensagem “A sequência de números não é
contínua”.

8. Pretende-se escrever um programa que dado um número indeterminado de números


inteiros positivos introduzidos pelo teclado, até que apareça o número zero como indicador
de paragem, verifique se os números lidos constituem uma sequência exclusivamente
constituída por números ímpares. Se a sequência for exclusivamente constituída por
números ímpares, o programa escreve no monitor a mensagem “A sequência de números é
exclusivamente constituída por números ímpares”, senão escreve a mensagem “A
sequência de números não é exclusivamente constituída por números ímpares”. Para
verificar se um número é ímpar, existe a função odd.

9. Pretende-se escrever um programa que dado um número indeterminado de números


inteiros positivos introduzidos pelo teclado, até que apareça o número zero como indicador
de paragem, verifique se os números lidos constituem uma sequência alternada de números
pares e de números ímpares. Se a sequência for alternada, o programa escreve no monitor a
mensagem “A sequência de números é constituída por números pares e números ímpares
alternadamente”, senão escreve a mensagem “A sequência de números não é constituída
por números pares e números ímpares alternadamente”.

10. Pretende-se escrever um programa que dado um número indeterminado de números


inteiros positivos introduzidos pelo teclado, até que apareça o número zero como indicador
de paragem, calcula a soma das diferenças entre números sucessivos e escreve-a no
monitor.

11. Pretende-se escrever um programa que desenhe no monitor um rectângulo a cheio,


com as dimensões largura e altura lidas do teclado, expressas em número de caracteres de
impressão, usando para o efeito o carácter *. O rectângulo deve ficar centrado, assumindo
para o efeito que o número máximo de linhas da janela de visualização é representado pelo
parâmetro NLMAX, e, o número máximo de colunas da janela de visualização é
representado pelo parâmetro NCMAX. Como sugestão, em vez de analisar o problema de
raiz, reescreva o algoritmo final do problema do triângulo centrado, apresentado na Figura
2.27, ajustando as operações de leitura e de impressão da figura, para o caso de um
rectângulo.

2.8 Leituras recomendadas


• 1º capítulo do livro “Introdução à Programação usando o Pascal”, 5ª edição, de J. Pavão
Martins, da editora McGraw-Hill de Portugal, 1994.

Você também pode gostar