Escolar Documentos
Profissional Documentos
Cultura Documentos
Livro - Estruturas de Dados e Algoritmos em C PDF
Livro - Estruturas de Dados e Algoritmos em C PDF
ESTRUTURAS
DE DADOS
E ALGORITMOS
EM C
Professor Doutor Antnio Manuel Adrego da Rocha
Professor Doutor Antnio Rui Oliveira e Silva Borges
Departamento de Electrnica e Telecomunicaes
Universidade de Aveiro
Prefcio
Este texto serve de suporte disciplina de Programao II, cujo objectivo o de fornecer uma
familiarizao com o ambiente de programao fornecido pelo Unix, na sua variante mais popularizada
Linux e o domnio da linguagem C, na sua norma ANSI, para o desenvolvimento de programas de
mdia e elevada complexidade.
Pretende-se ainda, que os alunos se familiarizem com a terminologia informtica apresentada nos textos
de referncia da rea das Cincias da Computao, pelo que, se tenha optado pela apresentao
sistemtica, em itlico e entre parntesis, dos nomes dos algoritmos e das estruturas de dados em ingls.
Captulo 1
INTRODUO AO C
Sumrio
Este captulo dedicado introduo das primeiras noes sobre a gramtica da linguagem
C. Comeamos por apresentar a estrutura de um programa e os seus elementos bsicos.
Explicamos os tipos de dados bsicos existentes, a definio de constantes e de variveis.
Apresentamos os vrios tipos de expresses e operadores existentes e a instruo de
atribuio, que a instruo bsica de uma linguagem imperativa. Apresentamos de seguida
as estruturas de controlo, que permitem alterar o fluxo da sequncia das instrues.
Apresentamos ainda as instrues de leitura de dados do teclado scanf e de escrita de
dados no monitor printf. Finalmente, apresentamos as bibliotecas que contm as funes
mais usuais e que estendem a operacionalidade da linguagem.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 2
1.1 Introduo
Em 1972, Dennis M. Ritchie desenvolveu a linguagem C, nos Laboratrios Bell da
companhia AT & T, que a principal empresa de telecomunicaes dos Estados Unidos da
Amrica, como uma linguagem de programao concebida para a escrita de sistemas
operativos, aquilo que se designa por Programao de Sistemas. Como a linguagem C era
to flexvel e permitia que os compiladores produzissem cdigo em linguagem mquina
muito eficiente, em 1973, Dennis M. Ritchie e Ken Thompson reescreveram quase
totalmente o sistema operativo Unix em C. Devido a esta ligao ntima, medida que o
Unix se tornou popular no meio acadmico, tambm a linguagem C se tornou a linguagem
preferida para o desenvolvimento de aplicaes cientficas. Pelo que, apesar de ter sido
concebida para a escrita de sistemas operativos, a linguagem C hoje encarada como uma
linguagem de uso geral.
Ora, como esta uma rea que no ser explorada no mbito desta disciplina, em muitos
programas, sobretudo naqueles que sero desenvolvidos nesta disciplina, no se coloca a
questo de devolver um valor. Pelo que, para evitar a mensagem de aviso do compilador,
recomenda-se terminar o main com a instruo return 0. Um programa em C tem a
estrutura apresentada na Figura 1.1.
aluso a funes
sequncia de instrues
return 0;
}
A incluso de ficheiros de interface num dado ficheiro fonte feita usando a directiva do
pr-processador #include numa das suas duas variantes:
x No caso do ficheiro de interface pertencer linguagem C, ento ele est armazenado no
directrio por defeito e usa-se a directiva #include <nome do ficheiro de interface>.
x No caso do ficheiro de interface ter sido criado pelo utilizador e no estar armazenado
no directrio por defeito, usa-se a directiva #include "nome do ficheiro de interface".
No mnimo, todos os ficheiros que contenham cdigo que faa acesso aos dispositivos
convencionais de entrada e de sada tm que incluir o ficheiro de interface stdio.h, que
descreve as funes e que contm as definies associadas com o acesso aos dispositivos
de entrada e de sada e aos ficheiros. Normalmente, o dispositivo de entrada o teclado e o
dispositivo de sada o monitor. Portanto, qualquer programa interactivo tem pelo menos
a aluso a este ficheiro de interface, tal como se apresenta na Figura 1.2.
A seguir s definies de objectos externos segue-se a aluso s funes locais que vo ser
usadas na funo main, bem como a definio de estruturas de dados e constantes locais.
Locais para a aplicao, mas que para o ficheiro, se comportam como definies globais.
Repare que a estruturao do programa, muito diferente do Pascal, sendo que as funes
so primeiramente aludidas ou referidas, para se tornarem visveis em todo o ficheiro e s
depois da funo main que so definidas. Neste exemplo, define-se apenas, atravs da
directiva #define, um identificador constante MIL_QUI, que representa o factor de
converso de milhas para quilmetros. Ele visvel para todo o cdigo do ficheiro, ou seja,
uma constante global. A funo main implementada com instrues simples, e com
recurso apenas s funes de entrada e de sada de dados da biblioteca stdio.
cabealho
int main ( void )
corpo
{
declarao de variveis locais
double MILHAS,
QUILOMETROS;
sequncia de instrues
do
{
printf ("Distncia em milhas? ");
scanf ("%lf", &MILHAS);
} while (MILHAS < 0.0);
QUILOMETROS = MIL_QUI * MILHAS;
printf ("Distncia em quilmetros %8.3f\n", QUILOMETROS);
return 0;
}
As palavras reservadas da linguagem C so: auto; break; case; char; const; continue;
default; do; double; else; enum; extern; float; for; goto; if; int; long; register; return;
short; signed; sizeof; static; struct; switch; typedef; union; unsigned; void; volatile; e
while. As palavras reservadas aparecem a cheio ao longo do texto e no cdigo apresentado.
A Figura 1.6 apresenta os tipos de dados simples existentes na linguagem C. Estes tipos de
dados tambm se designam por escalares, uma vez que, todos os seus valores esto
distribudos ao longo de uma escala linear. Dentro dos tipos de dados simples, temos o tipo
ponteiro (pointer), o tipo enumerado (enum) e os tipos aritmticos, que se dividem em
tipos inteiros e tipos reais. Os tipos aritmticos e o tipo enumerado designam-se por tipos
bsicos. Os tipos aritmticos inteiros podem armazenar valores negativos e positivos, que
o estado por defeito ou usando o qualificativo signed, ou em alternativa, podem
armazenar apenas valores positivos, usando para o efeito o qualificativo unsigned que lhe
duplica a gama dinmica positiva. O tipo aritmtico int pode ainda ser qualificado como
short, reduzindo-lhe a capacidade de armazenamento. O qualificativo long pode ser usado
para aumentar a capacidade de armazenamento do tipo inteiro int e do tipo real double.
Qualificativos
unsigned
int char float double Enumerado
enum
Tipos Bsicos
O tamanho dos tipos inteiros int e long e a sua gama dinmica dependem do compilador e
do hardware utilizados. Esta informao indicada no ficheiro limits.h localizado no
directrio include do ambiente de desenvolvimento. No caso do computador utilizado nesta
disciplina, cujo processador de 32 bits, os tipos int e unsigned int so representados em
4 bytes, pelo que, permitem armazenar respectivamente valores entre -2147483648 e
2147483647 e valores entre 0 e 4294967295. Os tipos long e unsigned long so tambm
representados em 4 bytes.
Na linguagem C no existe tal distino. O tipo char que utiliza um byte permite armazenar
quer um carcter quer um valor inteiro. A Figura 1.7 apresenta dois exemplos que
exemplificam esta polivalncia. No primeiro caso a atribuio do valor 65, que o cdigo
ASCII do carcter A, equivalente atribuio do prprio carcter 'A' varivel CAR. No
segundo caso a atribuio do valor 3 varivel NUM diferente da atribuio varivel
CAR do carcter '3', cujo cdigo ASCII 51. Devido a esta polivalncia de interpretao do
valor inteiro armazenado na varivel, o valor escrito no monitor depende do especificador
de formato que seja empregue na instruo de sada de dados. O formato %d representa o
valor decimal, enquanto que o formato %c representa o carcter.
A atribuio do tipo pode ser forada pelos sufixos U para unsigned e L para long. Uma
constante seguida do sufixo U do tipo unsigned int ou do tipo unsigned long. Uma
constante seguida do sufixo L do tipo long ou do tipo unsigned long. Uma constante
seguida do sufixo UL do tipo unsigned long.
Todas as variveis usadas num programa tm que ser previamente definidas ou declaradas.
O objectivo da declarao simultaneamente a reserva de espao em memria para o
armazenamento dos valores que as variveis vo sucessivamente tomar, e a associao de
cada identificador com a rea de memria correspondente.
A
char A, B = 'G'; B 'G'
4 bytes
unsigned int C = 1324;
double D = -2.5, E;
rea reservada
mas no inicializada
rea reservada
e inicializada D -2.5 8 bytes
E 8 bytes
1.6 Sequenciao
Tal como foi referido anteriormente, o corpo de uma funo delimitado pelos
separadores { e }, correspondentes, respectivamente, aos separadores begin e end do
Pascal, e contm a declarao das variveis locais, a aluso a funes usadas na sequncia
de instrues e a sequncia de instrues propriamente dita. A Figura 1.12 apresenta a
definio formal de uma sequncia de instrues. Ao contrrio do que se passa em Pascal,
na linguagem C cada instruo simples obrigatoriamente terminada com o separador ;, a
menos que o ltimo smbolo da instruo seja o separador }.
Tal como no Pascal, na linguagem C tambm existe o conceito de instruo composta, cuja
definio formal se apresenta na Figura 1.13, e que composta por uma sequncia de
instrues simples encapsuladas entre os separadores { e }. Uma instruo composta um
bloco de instrues simples que se comporta como uma instruo nica e usada em
instrues decisrias e repetitivas.
1.6.1 Expresses
A Figura 1.14 apresenta a definio formal de uma expresso. Uma expresso uma
frmula que produz um valor. Pode assumir as seguintes formas: ser uma constante; ser
uma varivel; ser o resultado da invocao de uma funo; ser uma expresso composta
por operandos e operadores, sendo que existem operadores unrios e binrios; e ser uma
expresso entre parnteses curvos.
13 CAPTULO 1 : INTRODUO AO C
No caso de aritmtica inteira, sempre que numa expresso surgem constantes, variveis, ou
se invocam funes de tipo char ou short, os seus valores so automaticamente
convertidos pelo compilador em quantidades de tipo int. Do mesmo modo, constantes,
variveis, ou funes de tipo unsigned char ou unsigned short, so automaticamente
convertidas pelo compilador em quantidades de tipo int, ou de tipo unsigned int, se o
primeiro tipo no tiver capacidade de armazenamento suficiente. No caso de aritmtica
real, sempre que numa expresso surgem constantes, variveis, ou se invocam funes de
tipo float, os seus valores so automaticamente convertidos pelo compilador em
quantidades de tipo double.
Contudo, sempre que o clculo de uma expresso envolva operadores com efeitos
colaterais, ou seja, uma expresso em que o valor de uma ou mais variveis afectado pelo
processo de clculo, normalmente devido utilizao dos operadores unrios incremento e
decremento, o cdigo resultante pode deixar de ser portvel. Assim, de extrema
importncia organizar cuidadosamente a formao das expresses para se evitar que tais
situaes ocorram.
Figura 1.20 - Exemplo de uma expresso onde existe o risco de ocorrncia de overflow.
A linguagem C permite ainda a construo de um tipo especial de expresso, cuja finalidade
fornecer o tamanho em bytes do formato de um tipo de dados particular. Esse tipo pode
ser representado, explicitamente, pelo seu identificador, ou, implicitamente, por qualquer
expresso desse tipo.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 16
1.6.2 Operadores
A Figura 1.21 apresenta os operadores aritmticos disponveis na linguagem C.
Operadores Unrios
Operador Smbolo Sintaxe Observaes
+ +x
Simtrico - -x
Incremento de 1 ++ ++x x tem que ser
x++ uma varivel
Decremento de 1 -- --x x tem que ser
x-- uma varivel
Operadores Binrios
Operador Smbolo Sintaxe Observaes
Adio + x + y
Subtraco - x - y
Multiplicao * x * y
Diviso / x / y y 0
Resto da diviso inteira % x % y y 0
e x e y tm de ser expresses inteiras
int X = 2, Y;
...
Y = 5 * X++; /* aps o clculo da expresso X = 3 e Y = 10 */
Y = 5 * ++X; /* aps o clculo da expresso X = 3 e Y = 15 */
int X, K = 3;
...
X = K * K++;
/* se a ordem de clculo for da esquerda para a direita, X = 9 */
/* se a ordem de clculo for da direita para a esquerda, X = 12 */
Figura 1.23 - Exemplo de uma expresso com utilizao incorrecta do operador ps-incremento.
O operador diviso usado simultaneamente para representar o quociente da diviso
inteira e da diviso real. Tal como se mostra na Figura 1.15, tratar-se- de uma diviso
inteira, sempre que os operandos forem inteiros e tratar-se- de uma diviso real, quando
pelo menos um dos operandos for real.
A Figura 1.25 apresenta os operadores lgicos que se aplicam tanto a expresses numricas
inteiras como a reais. Os operandos so interpretados como representando o valor falso, se
forem iguais a zero, e como representando o valor verdadeiro, em todos os restantes casos.
A expresso resultante de tipo int e assume o valor zero, se o resultado da comparao
for falso, e o valor um, se o resultado for verdadeiro. Ao contrrio do Pascal, uma
expresso formada por operadores lgicos nem sempre calculada at ao fim. O clculo
procede da esquerda para a direita e o processo interrompido logo que o resultado esteja
definido.
Operador Unrio
Operador Smbolo Sintaxe
Negao (not) ! !x
Operadores Binrios
Operador Smbolo Sintaxe
Conjuno (and) && x && y
Disjuno inclusiva (or) || x || y
Operador Unrio
Operador Smbolo Sintaxe
Complemento (not) ~ ~x
Operadores Binrios
Operador Smbolo Sintaxe
Conjuno (and) & x & y
Disjuno inclusiva (or) | x | y
Disjuno exclusiva (xor) ^ x ^ y
Deslocamento direita >> x >> y
Deslocamento esquerda << x << y
Os operadores lgicos actuam isoladamente sobre cada um dos bits dos operandos. Para os
operadores lgicos binrios, as operaes so efectuadas em paralelo sobre os bits
localizados em posies correspondentes de cada um dos operandos.
Regra geral, os tipos da varivel e da expresso podem ser quaisquer, desde que sejam tipos
escalares. Aps o clculo da expresso e antes que a atribuio tenha lugar, o valor da
expresso automaticamente convertido para o tipo da varivel.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 20
int X, Y, Z;
...
Y = X + 2 * Z;
X = X + 1;
Z = pow (Y, 2); /* pow a funo potncia, donde Z = Y2 */
Figura 1.29 - Exemplos da primeira variante da instruo de atribuio.
int X, Y, Z;
...
Y += 5; /* equivalente a Y = Y + 5 */
Z *= 5 + X; /* equivalente a Z = Z * (5 + X) */
A importncia desta notao tem a ver de novo com a compactao resultante e com a
possibilidade fornecida ao compilador de proceder mais facilmente a uma optimizao do
cdigo gerado. A Figura 1.31 apresenta dois exemplos.
int X, Y;
...
Y++; /* equivalente a Y = Y + 1 */
--X; /* equivalente a X = X - 1 */
int X, ABSX;
...
ABSX = X < 0 ? X : X;
/* equivalente a if (X < 0) then ABSX = -X; else ABSX = X; */
int X, Y, Z;
...
X = Y = Z; /* equivalente a X = (Y = Z); */
X += Y -= Z; /* equivalente a X = (X + (Y = (Y-Z)); */
A instruo decisria binria if (se), cuja definio formal se apresenta na Figura 1.35, tem
duas variantes que so fundamentalmente semelhantes s encontradas em Pascal. A nica
diferena reside no facto do separador then no surgir aqui. Em consequncia, a expresso
decisria obrigatoriamente colocada entre parnteses curvos para estabelecer a separao
da instruo a executar. A expresso decisria deve ser de um tipo escalar bsico. A
expresso falsa se for igual a zero e verdadeira se assumir qualquer outro valor. Nestas
condies, o compilador aceita qualquer expresso numrica como expresso decisria
vlida. Porm, por questes de clareza, isto deve ser evitado. de bom estilo que a
expresso decisria represente sempre uma expresso booleana.
if (V1 > 0)
if (V2 > 0) V2++;
else V1++; /* situao do else desligado */
if (V1 > 0)
{
if (V2 > 0) V2++;
}
else V1++;
if (V1 > 0)
if (V2 > 0) V2++;
else ;
else V1++;
Figura 1.40 - Erro devido troca do operador identidade pelo operador de atribuio.
A Figura 1.42 apresenta como se deve alinhar a instruo. Para aumentar a legibilidade, o
bloco de execuo selectivo e o bloco de execuo terminal devem ser alinhados mais
direita. As sequncias de instrues simples devem ser todas alinhadas, permitindo uma
melhor anlise das instrues que vo ser executadas.
switch ( expresso )
{
case V1 : instruo simples;
case V2 : instruo simples;
break;
case V3 : instruo simples;
instruo simples;
break;
case V4 : instruo simples;
break;
default : instruo simples;
}
No entanto, existe uma diferena semntica importante entre a instruo repeat until do
Pascal e a instruo do while da linguagem C. Em Pascal, o ciclo repetitivo executado at
a expresso decisria de terminao ser verdadeira. Mas, na linguagem C, o ciclo repetitivo
executado enquanto a expresso decisria de terminao for verdadeira. Ou seja, em
situaes equivalentes, uma necessariamente a negao da outra. Tal como em Pascal, a
utilizao correcta de qualquer das instrues supe que a expresso decisria de
terminao possa ser modificada, durante a execuo do ciclo repetitivo, para que o seu
valor passe eventualmente de verdadeiro a falso. Caso contrrio, o ciclo repetitivo seria
infinito. preciso igualmente garantir que todas as variveis que constituem a expresso
decisria so previamente inicializadas.
A Figura 1.45 apresenta como se deve alinhar a instruo while. No caso da variante mais
simples e se existir apenas uma instruo simples curta, ento a instruo while pode ser
toda escrita na mesma linha, mas se a instruo simples for longa deve ser escrita na linha
seguinte mais alinhada para a direita. No caso da variante, em que, o corpo do ciclo
repetitivo constitudo por uma instruo composta, os separadores { e } que definem a
instruo composta devem ser alinhadas pela palavra reservada while, e a sequncia de
instrues simples so escritas, uma por linha, todas alinhadas mais direita de maneira a
que seja legvel onde comea e acaba o ciclo repetitivo.
A Figura 1.46 apresenta como se deve alinhar a instruo do while. As palavras reservadas
do e while devem ser alinhadas e a condio booleana de terminao deve ser escrita
frente do terminador while. As instrues que constituem o corpo do ciclo repetitivo so
escritas uma por linha e todas alinhadas mais direita de maneira a que seja legvel onde
comea e acaba o ciclo repetitivo.
do
{
instruo simples;
...
instruo simples;
} while ( expresso );
Figura 1.47 - Exemplo comparativo da utilizao dos ciclos repetitivos do while e while.
A parte da inicializao executada em primeiro lugar e uma s vez. Em geral, a sua funo
atribuir valores iniciais a uma ou mais variveis usadas no ciclo repetitivo. A parte da
terminao uma expresso que calculada antes do incio de cada nova iterao e que
determina a continuao, se for verdadeira, ou no, se for falsa, do processo repetitivo.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 28
A expresso deve ser de um tipo escalar bsico. A expresso falsa se for igual a zero e
verdadeira se assumir qualquer outro valor. Nestas condies, o compilador aceita qualquer
expresso numrica como expresso decisria vlida. Porm, por questes de clareza, isto
deve ser evitado. de bom estilo que a expresso decisria represente sempre uma
expresso booleana. Finalmente, a parte de actualizao executada no fim de cada
iterao. Em geral, a sua funo actualizar os valores de uma ou mais variveis usadas no
ciclo repetitivo, entre as quais, sempre a varivel contadora. Normalmente, as expresses de
actualizao das variveis recorrem aos operadores unrios incremento e decremento ou ao
operador de atribuio precedido por um qualquer operador binrio.
Figura 1.49 - Exemplo do clculo das potncias de 2 com um ciclo repetitivo for.
Qualquer uma das trs partes componentes da instruo for, a inicializao, a terminao
ou a actualizao pode ser omitida. Situaes especiais correspondem aos seguintes casos:
x Quando todos os elementos so omitidos, ou seja, for ( ; ; ), temos um ciclo repetitivo
infinito.
x Quando a inicializao e a actualizao so omitidas, ou seja, for ( ; terminao ; ),
temos um ciclo repetitivo funcionalmente equivalente ao ciclo repetitivo while.
Ao contrrio do que se passa em Pascal, a varivel contadora tem um valor bem definido
aps o esgotamento do ciclo repetitivo, que ser aquele que conduziu a um valor falso da
expresso de terminao.
29 CAPTULO 1 : INTRODUO AO C
Devido sua grande versatilidade, a instruo for deve ser utilizada com extremo cuidado.
de bom estilo us-la apenas como uma generalizao da instruo for de Pascal. O que
significa, nomeadamente, que o valor da varivel contadora nunca deve ser modificado
dentro do ciclo repetitivo.
Um dos erros mais frequentemente cometido por programadores que se esto a iniciar na
utilizao da linguagem C, consiste em esquecer que o ciclo repetitivo for actua enquanto a
expresso de terminao for verdadeira. Se no exemplo anterior fosse utilizada a condio
I == N, o ciclo repetitivo no seria executado uma nica vez, a no ser que N fosse 1,
quando se pretende que o ciclo repetitivo seja executado N vezes.
Os ciclos repetitivos podem ser encadeados, tal como a instruo condicional binria,
dando origem a estruturas repetitivas em que, uma instruo do corpo do ciclo repetitivo
ela mesmo um ciclo repetitivo. A este tipo de construo d-se o nome de ciclos
imbricados (nested loops).
A instruo nula, expressa pela colocao do separador ;, surge muitas vezes associada com
as instrues while e for quando se pretende que o corpo do ciclo repetitivo no tenha
qualquer instruo. A Figura 1.50 apresenta um exemplo que determina o comprimento de
uma cadeia de caracteres. Uma vez que uma cadeia de caracteres um agregado de
caracteres terminado obrigatoriamente com o carcter '\0', ento preciso detectar o ndice
do agregado onde ele est armazenado. O que possvel fazer atravs de uma instruo
repetitiva em que a expresso de teste, recorrendo ao operador unrio incremento, provoca
o deslocamento dentro do agregado e, portanto, o ciclo repetitivo no carece de qualquer
instruo.
COMP = -1;
while (s[++comp] != '\0') ;
/* ou em alternativa com o ciclo for */
for (comp = 0; s[comp] != '\0'; comp++) ;
N := 0; /* na linguagem Pascal */
SOMA := 0.0;
readln (NUMERO);
while (N < 10) and (NUMERO >= 0.0) do
begin
N := N + 1;
SOMA := SOMA + NUMERO;
readln (NUMERO)
end;
N = 0; /* na linguagem C */
SOMA = 0.0;
scanf ("%lf", &NUMERO);
while (N < 10)
{
if (NUMERO < 0.0) break;
N++;
SOMA += NUMERO;
scanf ("%lf", &NUMERO);
}
N := 0; /* na linguagem Pascal */
for I := 1 to length (LINHA) do
if LINHA[I] in ['A'..'Z','a'..'z']
then begin
N := N + 1;
if LINHA[I] in ['A','E','I','O','U','a','e','i','o','u']
then if LINHA[I] in ['A','E','I','O','U']
then LINHA[I] := chr(ord(LINHA[I])-ord('A')+ord('a'))
else LINHA[I] := chr(ord(LINHA[I])-ord('a')+ord('A'))
end;
for (I = 0, N = 0; I < strlen (LINHA); I++) /* na linguagem C */
{
if (!isalpha (LINHA[I])) continue;
N++;
if ((toupper(LINHA[I]) != 'A') && (toupper(LINHA[I]) != 'E') &&
(toupper(LINHA[I]) != 'I') && (toupper(LINHA[I]) != 'O') &&
(toupper(LINHA[I]) != 'U')) continue;
if (isupper (LINHA[I])) LINHA[I] = LINHA[I]-'A'+'a';
else LINHA[I] = LINHA[I]-'a'+'A';
}
Por outro lado, o fluxo de texto de sada implementado numa regio da memria
principal, directamente acessvel pelo sistema operativo, que se designa por
armazenamento tampo de sada (output buffer). Aps a escrita de uma sequncia de
caracteres pela instruo de sada, o sistema operativo alertado para esse facto e,
eventualmente, vai transferir essa sequncia, carcter a carcter, para um registo do
controlador do monitor. Em resultado disso, a mensagem associada reproduzida no
monitor a partir da posio actual do cursor e da esquerda para a direita. Alm de
caracteres com representao grfica, as sequncias escritas podem conter caracteres de
controlo diversos que possibilitam, entre outras aces mais ou menos especficas, o
deslocamento do cursor para uma outra posio do monitor.
Salvo em duas situaes especiais adiante referidas, deve existir uma relao de um para um
entre cada especificador de converso e cada varivel da lista de ponteiros de variveis. Se o
nmero de variveis da lista de ponteiros de variveis for insuficiente, o resultado da
operao no est definido. Se, ao contrrio, o nmero de variveis for demasiado grande,
as variveis em excesso no so afectadas. O tipo da varivel e o especificador de
converso devem ser compatveis, j que a finalidade deste ltimo indicar, em cada caso,
que tipos de sequncias de caracteres so admissveis e como devem ser tratadas. Quando o
especificador de converso no vlido, o resultado da operao no est definido.
#include <stdio.h>
int main (void)
{
char CAR, /* escolha da opo do menu */
CARX; /* varivel auxiliar para teste de fim de linha */
...
do
{
printf ("1 - Opo_1\n");
printf ("2 - Opo_2\n");
printf ("3 - Opo_3\n");
printf ("Qual a sua escolha? ");
scanf ("%c", &CAR); /* ler o carcter que representa a opo */
if (CAR != '\n')
do /* descartar todos os eventuais restantes caracteres */
{ /* da linha, incluindo o carcter fim de linha */
scanf ("%c", &CARX);
} while (CARX != '\n');
} while ((CAR < '1') || (CAR > '3'));
...
}
Depois da leitura da varivel de tipo carcter, caso tenha sido lido um carcter diferente de
fim de linha ento vo ser lidos todos os caracteres que eventualmente existam no
armazenamento tampo de entrada at leitura do carcter fim de linha inclusive, usando
para o efeito uma varivel auxiliar de tipo carcter.
O ponteiro da varivel correspondente tem que referenciar uma regio de memria com
capacidade para o armazenamento dos caracteres lidos e do carcter '\0', que
automaticamente colocado no fim.
char NOME[8];
...
scanf ("%7s", NOME); /* ou em alternativa scanf ("%7s", &NOME[0]); */
Figura 1.57 - Exemplo da leitura de uma cadeia de caracteres com o especificador de converso %s.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 36
char NUMTEL[10];
...
scanf ("%9[0123456789]", NUMTEL);
/* ou em alternativa scanf ("%9[0123456789]", &NUMTEL[0]); */
char FRASE[80];
...
scanf ("%79[^\n]", FRASE);
/* ou em alternativa scanf ("%79[^\n]", &FRASE[0]); */
A Figura 1.59 apresenta um excerto de cdigo em que se utiliza o scanf para ler uma
varivel de tipo cadeia de caracteres, mas, de forma equivalente ao readln do Pascal. A
funo invocada para ler uma sequncia de caracteres quaisquer, constituda no mximo
por 79 caracteres. Depois descartam-se todos os caracteres que eventualmente existam no
armazenamento tampo de entrada at leitura do carcter fim de linha. De seguida l-se e
descarta-se o carcter fim de linha. Estas duas aces no podem ser combinadas numa s,
porque caso no existam caracteres extras, ento a instruo de leitura terminaria
abruptamente sem efectuar a leitura do carcter fim de linha, que ficaria no armazenamento
tampo de entrada disponvel para posteriores invocaes da funo. Caso no tenha sido
lido qualquer carcter para a varivel FRASE, o que pode ser indagado aferindo o resultado
devolvido pelo scanf e armazenado na varivel T, que zero nesse caso, ento constri-se
37 CAPTULO 1 : INTRODUO AO C
#include <stdio.h>
int main (void)
{
char FRASE[80]; /* frase a ser lida */
int T; /* sinalizao do estado de leitura */
...
printf ("Escreva a frase: ");
T = scanf ("%79[^\n]", FRASE); /* leitura da frase */
scanf ("%*[^\n]"); /* descartar todos os outros caracteres */
scanf ("%*c"); /* descartar o carcter fim de linha */
/* construir uma cadeia de caracteres nula */
if (T == 0) FRASE[0] = '\0';
...
}
Figura 1.59 - Leitura de uma cadeia de caracteres equivalente instruo readln do Pascal.
O processo de leitura termina quando for lido um nmero de caracteres vlidos igual
largura mxima de campo, caso ela tenha sido especificada, surgir entretanto um carcter
separador, ou qualquer outro carcter no vlido, que funciona neste caso tambm como
carcter separador, ou ocorrer um conflito. Nesta ltima situao, a varivel
correspondente no afectada. A Figura 1.60 apresenta as sequncias de caracteres
admissveis para cada carcter de converso.
int I;
short S;
double D;
...
scanf ("%d%hi%lf", &I, &S, &D);
int A, B, C;
...
printf ("Valores 1 e 2? ");
scanf ("%d%d\n", &A, &B);
printf ("Valor 3? ");
scanf ("%d", &C);
A Figura 1.63 apresenta um excerto de cdigo em que se utiliza o scanf para ler uma
varivel de tipo numrica, mas, de forma equivalente ao readln do Pascal. A funo
invocada para ler uma varivel numrica, que neste caso inteira de tipo int. Depois
descartam-se todos os caracteres que eventualmente existam no armazenamento tampo de
entrada at leitura do carcter fim de linha. De seguida l-se e descarta-se o carcter fim de
linha. Estas duas aces no podem ser combinadas numa s, porque caso no existam
caracteres extras, ento a instruo de leitura terminaria abruptamente sem efectuar a leitura
do carcter fim de linha, que ficaria no armazenamento tampo de entrada disponvel para
posteriores invocaes da funo. Caso no tenha sido lido qualquer valor numrico para a
varivel VAL, o que pode ser indagado aferindo o resultado devolvido pelo scanf e
armazenado na varivel T, que zero nesse caso, ento repete-se o processo at que seja
efectivamente lido um valor numrico.
39 CAPTULO 1 : INTRODUO AO C
#include <stdio.h>
int main (void)
{
int VAL; /* valor a ser lido */
int T; /* sinalizao do estado de leitura */
...
printf ("Valor? ");
do
{
T = scanf ("%d", &VAL); /* ler o valor */
scanf ("%*[^\n]"); /* descartar todos os outros caracteres */
scanf ("%*c"); /* descartar o carcter fim de linha */
} while (T == 0);
/* repetir enquanto um valor numrico no tiver sido lido */
...
}
Figura 1.63 - Leitura de uma varivel de tipo numrica equivalente instruo readln do Pascal.
A Figura 1.64 apresenta o cdigo necessrio para ler uma informao horria vlida, de tipo
HH:MM:SS. O processo de leitura tem que ler trs quantidades numricas positivas da o
tipo de dados utilizado ser unsigned. Como os valores no excedem 23 para as horas e 60
para os minutos e segundos, ento podem ser de tipo short. A leitura repetida enquanto
houver um erro de formato e enquanto houver valores fora dos limites.
#include <stdio.h>
int main (void)
{
unsigned short H, M, S; /* hora, minuto e segundo a serem lidos */
int T; /* sinalizao do estado de leitura */
...
do
{
do
{ /* leitura da informao horria */
printf ("Informao horria HH:MM:SS? ");
T = scanf ("%2hu:%2hu:%2hu", &H, &M, &S);
scanf ("%*[^\n]"); /* descartar todos os outros caracteres */
scanf ("%*c"); /* descartar o carcter fim de linha */
} while (T != 3); /* repetir enquanto houver um erro de formato */
} while ((H > 23) || (M > 59) || (S > 59));
/* repetir enquanto houver valores fora dos limites */
...
}
Como o formato de escrita pode conter literais que so directamente copiados sem
modificao para o fluxo de texto de sada, a instruo printf muito geral, podendo ser
tornada semanticamente equivalente instruo write de Pascal, quando o formato de
escrita no contm o carcter '\n', ou instruo writeln, quando este precisamente o
ltimo carcter presente. De um modo geral, a incluso de literais no formato de escrita
possibilita melhorar a compreenso dos dados impressos e organizar a sua apresentao de
um modo mais regular. A impresso do carcter %, que no um literal, obtida atravs
do especificador de converso %%.
O papel do especificador de preciso est associado de alguma forma com a preciso com
que os valores convertidos so expressos. Assim, tem-se que:
x Para valores inteiros, indica o nmero mnimo de algarismos usados na representao
do valor. Se surgir apenas o carcter ., significa um nmero zero de algarismos.
x Para valores reais:
x Com os caracteres de converso e ou f indica o nmero de algarismos usados na
representao da parte fraccionria. Se surgir apenas o carcter ., significa um
nmero zero de algarismos. Quando no est presente, esse nmero de 6.
x Com o carcter de converso g indica o nmero mximo de algarismos
significativos. Se surgir apenas o carcter ., significa um nmero zero de algarismos.
x Quando o valor no puder ser expresso no nmero de algarismos indicado pelo
especificador de preciso, ento o valor ser arredondado.
x Para valores de tipo cadeia de caracteres, indica o nmero mximo de caracteres a
imprimir.
preciso ter em ateno que no existe uma total simetria nos formatos de converso das
funes scanf e printf. Assim, a funo scanf exige o formato de converso %lf para ler
um valor double, enquanto que a funo printf usa o formato de converso %f para
escrever uma expresso de tipo double. Esta assimetria deve-se ao facto de a linguagem C
converter automaticamente expresses reais para double e portanto, no existir de facto a
impresso de expresses de tipo float. Esta assimetria um dos erros mais frequentemente
cometido por programadores que se esto a iniciar na utilizao da linguagem C.
A norma IEEE 754, usada na caracterizao dos tipos inteiros no computador utilizado na
disciplina, permite a deteco destas situaes por inspeco do valor devolvido. Se o erro
for EDOM e o valor devolvido for nan, ento isso significa que no um nmero. Se o
erro for ERANGE podemos ter os seguintes valores devolvidos: inf que significa que um
nmero positivo muito grande; -inf que significa que um nmero negativo muito grande;
e 0 que significa que um nmero muito pequeno.
A sua aplicao prtica vem muitas vezes associada com o envio da mensagem de erro
correspondente para o dispositivo convencional de sada de erro, que normalmente o
mesmo que o dispositivo convencional de sada, ou seja, o monitor.
Isto feito usando a funo perror, descrita no ficheiro de interface stdio.h, que imprime no
dispositivo convencional de sada de erro uma combinao de duas mensagens, separadas
pelo carcter :.
mensagem definida pelo programador : mensagem associada ao valor de errno
A Figura 1.76 apresenta um exemplo da utilizao da funo perror.
#include <stdio.h>
#include <errno.h>
...
errno = 0;
Z = log (X);
if (errno != 0) /* ocorreu uma situao de erro */
{
perror ("erro no clculo de um logaritmo na rotina ...");
...
}
...
A definio do tipo ldiv_t formalmente semelhante, substitui-se apenas int por long em
todas as ocorrncias.
Esta biblioteca contm ainda as funes para gesto de memria, que permitem adjudicar e
libertar memria dinmica, que sero apresentadas mais tarde.
Sumrio
Este captulo dedicado aos aspectos mais avanados da linguagem C. Comeamos por
apresentar o conceito de funo generalizada, que o nico tipo de subprograma existente
e como atravs dela se implementam funes e procedimentos. Explicamos a passagem de
parmetros s funes e introduzimos os primeiros conceitos sobre ponteiros.
2.1 Funes
No estabelecimento de solues de problemas complexos essencial controlar o grau de
complexidade da descrio. A descrio deve ser organizada de uma forma hierarquizada,
tambm designada de decomposio do topo para a base (Top-Down Decomposition), de
modo a que corresponda, a cada nvel de abstraco considerado, uma decomposio num
nmero limitado de operaes, com recurso a um nmero tambm limitado de variveis.
S assim possvel:
x Minimizar a interaco entre as diferentes operaes, impondo regras estritas na
formulao das dependncias de informao.
x Desenvolver metodologias que, de uma forma simples, conduzam demonstrao da
correco dos algoritmos estabelecidos.
x Conceber um desenho da soluo que:
x Promova a compactao atravs da definio de operaes reutilizveis em
diferentes contextos.
x Enquadre a mudana ao longo do tempo, possibilitando a alterao de
especificaes com um mnimo de esforo.
Parmetros Parmetros
de de
Entrada Procedimento Sada
(0) (0)
Parmetros
de Resultado
Entrada Funo de
Sada
(0)
Parmetros
Parmetros de
de Funo Sada
Entrada
Generalizada (0)
(0) Resultado
de Sada
Figura 2.3 - Esquema de uma funo generalizada da linguagem C.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 4
Aps a sua definio, a nova operao identificada por um nome, por uma lista opcional
de parmetros de comunicao, ou seja, os parmetros de entrada, os parmetros de sada,
e pela indicao do tipo do valor devolvido.
cabealho
double CONVERTE_DISTANCIA (double ML)
{
corpo
return ML * MIL_QUI;
}
Mas, por vezes necessrio usar parmetros de entrada-sada numa funo. Ou seja, o
valor da varivel necessita de ser alterada durante a execuo da funo. Nesses casos
estamos perante a passagem de parmetros por referncia, tambm designada por
endereo. Nesta situao passado o endereo, ou seja, a localizao da varivel na
memria. Isso feito usando um ponteiro para a varivel na invocao da funo.
Figura 2.6 - Definio formal dos operadores para manipulao de variveis atravs de ponteiros.
O operador endereo, cujo smbolo o &, d a localizao em memria da varivel.
localizao de uma varivel chama-se habitualmente ponteiro para a varivel, j que o
valor que lhe est associado aponta para, ou referencia, a regio de memria onde essa
varivel est armazenada. Mais especificamente, o valor que est associado a um ponteiro
representa o endereo do primeiro byte da regio de armazenamento da varivel. Assim, um
ponteiro, embora assuma o valor de um endereo, no propriamente um endereo. O que
est subjacente ao conceito no a localizao de um byte, que define a diviso fsica da
memria, mas de uma varivel, ou seja, da diviso lgica da memria. Portanto, no faz
sentido falar-se em ponteiros em abstracto, porque um ponteiro est sempre associado a
um tipo de dados bem definido. Como nos processadores actuais, o barramento de
endereos tem uma dimenso de 32 bits, o tamanho do formato do tipo ponteiro igual a 4
bytes. Por isso, tem-se que sizeof (ponteiro para um tipo de dados genrico) = 4.
uma varivel. Nessa situao indica que a varivel de tipo ponteiro. E quando utilizado
na lista de parmetros, precedendo um parmetro, indica que o parmetro um parmetro
de entrada-sada, ou seja, que uma passagem por referncia.
int A, *PA;
A 23
...
PA = &A;
PA
*PA = 23;
tipo base
Agregado bidimensional com
15 elementos do tipo base,
com o elemento da 4 coluna
da 3 linha marcado.
Assim, a constante numrica positiva, includa no descritor de dimenso, ter que ser
maior, ou quando muito igual, ao nmero de constantes da lista. Se for igual, como o
caso do agregado A, todos os elementos do agregado sero inicializados com os valores
indicados. Se for maior, como o caso do agregado B, s os K primeiros elementos, em
que K igual ao nmero de constantes da lista, sero inicializados com os valores
indicados, sendo que os restantes elementos so inicializados a zero. Alternativamente,
como o caso do agregado C, o descritor de dimenso pode ser omitido e ento ser
reservado espao, e sero inicializados, tantos elementos quanto o nmero de constantes da
lista de inicializao.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10
Por conseguinte, uma varivel de tipo ponteiro para um tipo base pode ser encarada como
uma varivel de tipo agregado unidimensional do mesmo tipo base na referncia a uma
regio de memria formada por um conjunto contguo de variveis do tipo base. Pelo que,
a seguinte declarao do agregado A e do ponteiro para o agregado PA inicializado com o
endereo inicial do agregado, resulta no mapa de reserva de espao em memria que se
apresenta na Figura 2.14 e permite a utilizao do ponteiro PA para aceder aos elementos
do agregado.
TIPO_BASE A[N], *PA = A;
PA+i { &PA[i] = &A[i] *(PA+i) { PA[i] = A[i], com 0 d i < N
A
TIPO_BASE A[8], *PA = A;
A[0]
A[1]
A[2]
A[3]
A[4]
A[5]
A[6]
A[7]
PA
Figura 2.16 - Utilizao do operador sizeof para calcular o nmero de elementos de um agregado.
Um dos erros mais frequentemente cometido, por programadores que se esto a iniciar na
utilizao da linguagem C, ultrapassar a dimenso de um agregado, nomeadamente em
ciclos repetitivos for. No exemplo apresentado na Figura 2.17, o for vai processar o
agregado desde o elemento de ndice 0 at ao elemento de ndice 10, que no existe no
agregado. Acontece que nesta posio de memria que est armazenada a varivel I que
controla o for, pelo que, o seu valor vai ser reinicializado a zero e portanto o for de novo
repetido. Estamos perante um ciclo repetitivo infinito criado por engano.
int I, AR[10];
...
for (I = 0; I <= 10; I++) AR[I] = 0; /* situao de erro */
Figura 2.17 - Situao de erro na utilizao do ciclo for para processar um agregado.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
PTS 4 bytes
Figura 2.20 - Mapa de reserva de espao em memria de um ponteiro para uma cadeia de caracteres
inicializado com uma cadeia de caracteres constante.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
Mas j possvel atribuir a uma varivel de tipo ponteiro para char uma constante de tipo
cadeia de caracteres ou outra varivel de tipo cadeia de caracteres. A Figura 2.21 apresenta
um exemplo que mostra esta diferena.
a + ( z - a + 3 ) % 26 = c
z a
y b
x c
w d
alfabeto minsculo
...
A Figura 2.25 apresenta a funo que codifica um carcter minsculo. A funo no testa
se o carcter ou no minsculo, pelo que, s deve ser invocada para caracteres
minsculos. Se o valor do deslocamento circular, que representado pelo parmetro de
entrada K, for positivo ento o carcter codificado K posies para a frente, se for
negativo ento o carcter codificado K posies para a trs, e se K for zero o carcter sai
inalterado. Apresentam-se as solues em Pascal e na linguagem C.
(* no Pascal *)
function CODIFICAR_MINUSCULO (CAR: char; K: integer): char;
var CAR_S : char;
begin
CAR_S := chr(ord('a') + (ord(CAR) - ord('a') + 26 + K) mod 26);
CODIFICAR_MINUSCULO := CAR_S
end;
char CODIFICAR_MINUSCULO (char CAR, int K) /* na linguagem C */
{
return 'a' + (CAR - 'a' + 26 + K ) % 26;
}
As funes comparam byte a byte o contedo das regies de memria referenciadas por z1 e
z2. O processo de comparao termina logo que uma deciso possa ser univocamente
tomada.
O valor devolvido ser positivo, quando o contedo da regio z1 for maior do que o
contedo da regio z2, zero, quando o contedo da regio z1 for igual ao contedo da
regio z2, e, negativo, quando o contedo da regio z1 for menor do que o contedo da
regio z2.
No caso da funo memcmp, comparam-se sempre n bytes sem ser atribuda qualquer
interpretao ao seu contedo, enquanto que, para strcmp e strncmp, se supe que z1 e
z2 referenciam cadeias de caracteres, ou seja, agregados de caracteres terminados
obrigatoriamente pelo carcter nulo, e o processo de comparao decorre at tomada
unvoca de uma deciso, no caso da funo strcmp, ou at um mximo de n caracteres
terem sido comparados no caso da funo strncmp.
#define NMAX 10
...
char FRASE[80], FIM[] = "FIM"; int N = 0;
...
do
{
N++;
printf ("Escreva a frase -> ");
scanf ("%79[^\n]", FRASE); /* leitura da frase */
scanf ("%*[^\n]"); /* descartar todos os outros caracteres */
scanf ("%*c"); /* descartar o carcter de fim de linha */
if ( !strcmp (FRASE, FIM) ) break;
... /* processar a frase lida */
} while ( N < NMAX );
ponteiro nulo. Se existir, esse carcter substitudo pelo carcter nulo e constitui o fim da
palavra actual, cuja localizao devolvida pela funo. Antes de terminar, porm, a funo
strtok armazena internamente a localizao do carcter seguinte ao carcter nulo. Esta
referncia vai constituir o ponto de partida da prxima pesquisa que, para ser feita, exige a
invocao da funo com um ponteiro nulo em substituio de z.
FRASE
INICIAL '#' 'e' 'r' 'a' '$' '#' 'u' 'm' 'a' '$' 'v' 'e' 'z' '#' 'u' 'm' '$' '\0'
FRASE '#' 'e' 'r' 'a' '\0' '#' 'u' 'm' 'a' '\0' 'v' 'e' 'z' '\0' 'u' 'm' '\0' '\0'
FINAL
PALAVRA[0]
PALAVRA[1]
PALAVRA[2]
PALAVRA[3]
A funo memset copia o valor c, previamente convertido para unsigned char, para os n
primeiros bytes da regio de memria referenciada por z. A funo strerror devolve a
mensagem associada com a varivel global errno. A funo strlen devolve o comprimento
da cadeia de caracteres, ou seja, o nmero de caracteres armazenados at ao carcter nulo.
A Figura 2.35 apresenta um exemplo da utilizao da funo strerror. Esta funo devolve
uma cadeia de caracteres que pode ser usada para escrever mensagens de erro associadas
varivel global de erro. Neste exemplo, logo aps a invocao da funo matemtica raiz
quadrada, a varivel global de erro testada para em caso de erro escrever a mensagem
respectiva no monitor atravs do printf. No clculo de uma raiz quadrada, h erro se o
valor de X for negativo, pelo que, nesse caso a mensagem indicar que o argumento X est
fora da gama permitida para o clculo da raiz quadrada.
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <string.h>
...
errno = 0;
Z = sqrt (X);
if (errno != 0) /* se ocorreu uma situao de erro */
{
printf("ERRO -> %s\n", strerror(errno));
... -> Numerical argument out of domain */
}
A Figura 2.37 apresenta um exemplo da utilizao da funo sscanf para decompor uma
cadeia de caracteres em subcadeias de caracteres. A cadeia de caracteres FRASE, que
armazena uma data decomposta nas suas componentes, CIDADE, DIA, MES e ANO,
para posterior impresso no monitor com outro formato.
A funo sprintf cria uma cadeia de caracteres referenciada por z, constituda por texto e
pelos valores das expresses que formam a lista de expresses, segundo as regras impostas
pelo formato de escrita, indicado por formato. As definies do formato e da lista de
expresses so as mesmas que para as funes printf e fprintf.
#define MAX_CAR 40
...
char FRASE[MAX_CAR+1], FORMATO[20];
...
sprintf (FORMATO, "%%%ds", MAX_CAR); /* FORMATO igual a "%40s" */
...
scanf (FORMATO, FRASE); /* equivalente a scanf ("%40s", FRASE); */
Para alm dos agregados bidimensionais, por vezes existe a necessidade de utilizar
agregados tridimensionais. Por exemplo, para a simulao de um campo electromagntico
no espao. A Figura 2.42 apresenta a declarao e inicializao de dois agregados
tridimensionais, bem como da sua colocao na memria. Tal como na declarao de
agregados unidimensionais possvel omitir o descritor de dimenso, ou seja, fazer uma
definio incompleta, mas apenas da primeira dimenso, tal como feito na declarao do
agregado E. A primeira dimenso pode ser inferida pelo compilador a partir da expresso
de inicializao, e neste exemplo 2.
25 CAPTULO 2 : COMPLEMENTOS SOBRE C
Mas, apesar de normalmente no serem necessrios agregados com mais de duas ou trs
dimenses, a linguagem C tal como o Pascal no limita o nmero de dimenses de um
agregado. Segundo a norma ANSI, os compiladores devem suportar pelo menos seis
dimenses.
2.5 Ponteiros
Como j foi referido anteriormente, um ponteiro embora assuma o valor de um endereo,
no propriamente um endereo, porque ele est sempre associado a um tipo de dados
bem definido. Da que duas variveis de tipo ponteiro podem conter o mesmo valor, ou
seja, o mesmo endereo, e no entanto constiturem de facto entidades distintas.
27 CAPTULO 2 : COMPLEMENTOS SOBRE C
int *PI; PI
double *PD, VD;
... PD
PI = (int *) &VD;
PD = &VD;
VD
int A;
void V; /* declarao ilegal porque a varivel V no tem sentido */
void *PV; /* a varivel PV representa um ponteiro genrico */
...
PV = &A;
*PV = 5; /* instruo ilegal porque a varivel *PV no tem sentido */
*((int *) PV) = 5; /* agora A = 5 */
PPA
int A, *PA;
...
*PA = 23; /* incorrecto porque PA no aponta para lado nenhum */
int A, *PA = &A;
...
*PA = 23; /* correcto A = 23 */
PA (*PA)[0]
TIPO_BASE (*PA)[4];
(*PA)[1]
(*PA)[2]
(*PA)[3]
B
TIPO_BASE B[2][4], (*PB)[4] = B;
B[0][0] B[0]
B[0][1]
B[0][2]
B[0][3]
B[1][0] B[1]
B[1][1]
B[1][2]
B[1][3]
PB
Existe, porm, uma diferena subtil entre as variveis de tipo agregado bidimensional de
um tipo base e as variveis de tipo ponteiro para um agregado unidimensional, de tamanho
igual segunda dimenso do agregado bidimensional do mesmo tipo base. Como se
verifica do mapa de reserva de espao em memria apresentado na Figura 2.56, no existe
espao directamente associado com a varivel B e, portanto, B, B[0] e B[1] so ponteiros
constantes, cujos valores no podem ser modificados. Assim, instrues do tipo
B = expresso; ou B[i] = expresso; so ilegais e tem-se que *(B+i) = B[i].
representao grfica
de ponteiro nulo
Uma estrutura distingue-se assim de um agregado pelo facto de permitir que os seus
elementos, que se designam por campos, possam ser de tipos diferentes e porque o acesso
a cada um dos campos no feito atravs da sua localizao na estrutura, mas sim atravs
do nome do campo a que se pretende aceder. Estamos perante um acesso por nomeao.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 34
struct tdados_pessoa
{
char NOME[60];
char SEXO;
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
};
struct tdados_pessoa
{
char NOME[60];
char SEXO;
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
} PESSOA, GRUPO PESSOAS[10], *PPESSOA;
Figura 2.62 - Exemplo da declarao de uma estrutura e da declarao de variveis desse tipo.
Existe uma outra forma de criar um novo tipo de dados em linguagem C usando para esse
efeito a instruo typedef, que equivalente instruo type do Pascal. A Figura 2.63
apresenta esta forma alternativa.
typedef struct
{
char NOME[60];
char SEXO;
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
} TDADOS_PESSOA;
Na linguagem C possvel atribuir uma estrutura a outra estrutura, tal como no Pascal. A
Figura 2.66 apresenta alguns exemplos de instrues de atribuio envolvendo estruturas.
A Figura 2.67 mostra a atribuio do valor 30 ao campo DIA, directamente atravs de uma
varivel, usando o operador acesso a campo, e por referncia indirecta atravs de um
ponteiro, usando o operador referncia a campo.
typedef struct
{
unsigned int DIA;
unsigned int MES;
unsigned int ANO;
} TDATA;
typedef struct
{
char NOME[60];
char SEXO;
TDATA DATA_NASCIMENTO;
} TDADOS_PESSOA;
Figura 2.70 - Acesso aos campos de uma estrutura interna a outra estrutura.
Figura 2.73 - Definio e invocao de uma funo com uma estrutura passada por referncia.
A Figura 2.74 apresenta a funo de escrita da estrutura TDADOS_PESSOA. Como a
estrutura um parmetro de entrada da funo, pode ser passada por valor.
39 CAPTULO 2 : COMPLEMENTOS SOBRE C
Figura 2.74 - Definio e invocao de uma funo com uma estrutura passada por valor.
Figura 2.76 - Exemplo de uma funo que processa um agregado de estruturas (1 verso).
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 40
A Figura 2.77 apresenta uma segunda verso, em que o acesso aos elementos do agregado
feito atravs de um ponteiro que aponta para o elemento pretendido.
Figura 2.77 - Exemplo de uma funo que processas um agregado de estruturas (2 verso).
A Figura 2.78 apresenta o programa que invoca a funo NUMERO_HOMENS. O
nmero de elementos do agregado determinado recorrendo ao operador sizeof. O tipo
de dados TDADOS_PESSOA o da Figura 2.68.
Tal como no caso da definio de estruturas, a definio de enumerados tambm pode ser
feita usando a instruo typedef.
41 CAPTULO 2 : COMPLEMENTOS SOBRE C
T_DIA_UTIL DIA;
...
do
{
printf ("Dia da semana (SEGUNDA=2 ... SEXTA=6)? ");
scanf ("%1d", &DIA);
} while (DIA<2 || DIA>6);
/* admitindo que foi introduzido o valor 2, ento DIA = SEGUNDA */
DIA += 4; /* agora DIA = SEXTA */
DIA--; /* agora DIA = QUINTA */
...
if (DIA > SEXTA) /* a expresso falsa */
printf ("*** Aleluia Fim de Semana ***\n");
Uma varivel de durao permanente s pode ser inicializada com uma expresso que
contenha apenas literais. Se a varivel no for inicializada dentro de um funo, ento fica
automaticamente inicializada a zero.
A linguagem C tambm permite declarar variveis, cujo valor no pode ser alterado depois
da sua inicializao. Nesse caso estamos perante uma constante que ao contrrio de uma
constante definida com a directiva de define, que designada por constante simblica, ocupa
espao em memria. Para declarar uma constante usa-se o qualificativo const. A Figura
2.84 apresenta uma nova verso da funo CONVERTE_DISTANCIA em que o factor
de converso uma constante real local.
O nvel de visibilidade de uma varivel depende do stio onde esta declarada. Existem
quatro nveis de visibilidade. As variveis declaradas antes da funo main so variveis
globais que tm um alcance para todos os ficheiros fonte da aplicao. Vamos design-las
por variveis globalmente globais ( Program Scope ). As variveis declaradas antes da
funo main com o qualificativo static so variveis globais que tm um alcance apenas no
ficheiro fonte onde esto declaradas. Vamos design-las por variveis localmente globais
( File Scope ). As variveis declaradas dentro das funes so variveis locais que tm o
alcance limitado funo onde so declaradas. Vamos design-las por variveis locais
( Function Scope ). As variveis declaradas dentro de um bloco, ou seja, dentro de um
conjunto de intrues inseridas entre chavetas so variveis que tm o alcance limitado ao
bloco onde esto declaradas. Vamos design-las por variveis de bloco ( Block Scope ). Por
uma questo de bom estilo de programao, estas variveis devem ser evitadas.
#include <stdio.h>
Numa funo no possvel declarar uma varivel local com o mesmo nome de um
parmetro da funo. Mas, como as variveis locais so invisveis externamente, pode-se
declarar variveis locais com o mesmo nome em funes diferentes. A Figura 2.86
apresenta exemplos de declarao e utilizao de variveis com diferente visibilidade.
45 CAPTULO 2 : COMPLEMENTOS SOBRE C
Sumrio
A norma ANSI define no ficheiro de interface stdio.h uma estrutura de dados, de nome
FILE, que mantm toda a informao necessria ao controlo de um fluxo de comunicao
e que consiste, entre outros elementos:
x Num indicador de posio de leitura ou de escrita.
x Num ponteiro para a localizao do armazenamento tampo (buffer) associado ao fluxo
de comunicao.
x Num sinalizador de erro, que indica se ocorreu um erro de leitura ou de escrita.
x Num sinalizador de fim de ficheiro, que indica que o fim da informao armazenada no
dispositivo foi atingido.
Assim, sempre que um programa pretender aceder a um dispositivo tem que declarar uma
varivel de tipo ponteiro para FILE para armazenamento do identificador do fluxo de
comunicao que devolvido pela operao de estabelecimento de comunicao. A partir
da, esse identificador usado em todas as operaes de leitura e/ou de escrita no
dispositivo, bem como no controlo e encerramento da comunicao.
x O fluxo de texto stdin est associado com o dispositivo convencional de entrada, que
o teclado.
x O fluxo de texto stdout est associado com o dispositivo convencional de sada, que o
monitor.
x O fluxo de texto stderr est associado com o dispositivo convencional de sada de erro,
que tambm o monitor.
Para garantir a portabilidade, o ficheiro de interface stdio.h, define ainda duas constantes.
A constante EOF (end of file), que sinaliza o fim de ficheiro e a constante NULL, que o
ponteiro nulo, ou seja o valor de um ponteiro que no localiza qualquer regio de memria.
FILE *fopen ( const char *nome do dispositivo , const char *modo de acesso ) ;
nome do dispositivo ::= nome vlido para um dispositivo genrico
modo de acesso ::= "r" | "rb" | "w" | "wb" | "a" | "ab"
"r+" | "rb+" | "w+" | "wb+" | "a+" | "ab+"
Modo de acesso
r w a r+ w+ a+
Existncia prvia do dispositivo * *
Dispositivo aberto ou criado sem contedo * *
Leitura do fluxo permitida * * * *
Escrita no fluxo permitida * * * * *
Escrita permitida s no fim do fluxo * *
...
FILE *FPENT; char NOMEFICH[81]; int ST;
...
do
{
printf ("Nome do ficheiro de entrada ");
ST = scanf ("%80s", NOMEFICH); /* leitura do nome do ficheiro */
scanf ("%*[^\n]"); /* descartar todos os outros caracteres */
scanf ("%*c"); /* descartar o carcter de fim de linha */
} while (ST == 0);
/* abertura do ficheiro */
if ( (FPENT = fopen (NOMEFICH, "r") ) == NULL )
{
fprintf (stderr, "O Ficheiro %s no existe\n", NOMEFICH);
exit (EXIT_FAILURE);
}
... /* leitura do contedo do ficheiro e seu processamento */
fclose (FPENT); /* fecho do ficheiro */
...
int fscanf ( identificador do fluxo de entrada , formato de leitura , lista de ponteiros de variveis ) ;
identificador do fluxo de entrada ::= varivel de tipo ponteiro para FILE, inicializada pela
invocao prvia de fopen, nos modos de acesso r, r+, w+ ou a+, ou stdin
Salvo em duas situaes especiais, deve existir uma relao de um para um entre cada
especificador de converso e cada varivel da lista de ponteiros de variveis. Se o nmero
de variveis da lista de ponteiros de variveis for insuficiente, o resultado da operao no
est definido. Se, pelo contrrio, o nmero de variveis for demasiado grande, as variveis
em excesso no so afectadas. O tipo da varivel e o especificador de converso devem ser
compatveis, j que a finalidade deste ltimo indicar, em cada caso, que tipos de
sequncias de caracteres so admissveis e como devem ser tratadas. Quando o
especificador de converso no vlido, o resultado da operao no est definido. O
processo de leitura s termina quando o formato de leitura se esgota, quando lido o
carcter de fim de ficheiro, ou quando existe um conflito de tipo entre o que est indicado no
formato de leitura e a correspondente quantidade a ser lida. Neste ltimo caso, o carcter
que causou o conflito mantido no fluxo de texto. A funo devolve o nmero de valores
lidos e armazenados, ou o valor EOF (end of file), se o carcter de fim de ficheiro lido antes
que qualquer converso tenha lugar. Se, entretanto, ocorreu um conflito, o valor devolvido
corresponde ao nmero de valores lidos e armazenados at ocorrncia do conflito.
A Figura 3.7 apresenta a soluo mais simples, que consiste em terminar a leitura assim que
se detecte uma linha desformatada, ou seja, uma linha que no contenha os NITEMS que
era esperado ler. Nesse caso assinalada a situao de erro e fecha-se o ficheiro.
#define NITEMS 4
...
FILE *FPENT; int NLIDOS;
...
/* leitura da linha completa de acordo com o formato esperado */
while ((NLIDOS = fscanf (FPENT, "formato\n", ponteiros)) == NITEMS)
{
... ; /* processamento da informao lida */
}
/* verificao da causa de paragem da leitura */
if ( NLIDOS != EOF ) /* EOF ou linha desformatada? */
fprintf (stderr, "Leitura parada devido a linha desformatada\n");
...
fclose (FPENT); /* fecho do ficheiro */
...
#define NITEMS 4
...
FILE *FPENT; int NLIDOS;
...
/* leitura da linha completa de acordo com o formato esperado */
while ( (NLIDOS = fscanf (FPENT, "formato\n", ponteiros)) != EOF )
{
/* no ocorreu EOF mas preciso aferir a consistncia da linha */
if ( NLIDOS != NITEMS ) /* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
else ... ; /* processamento da informao lida */
...
}
...
fclose (FPENT); /* fecho do ficheiro */
...
#define NITEMS 4
...
FILE *FPENT; int NLIDOS;
...
while ( !feof(FPENT) )
{
/* leitura da linha completa de acordo com o formato esperado */
NLIDOS = fscanf (FPENT, "formato\n", ponteiros)
/* no ocorreu EOF mas preciso aferir a consistncia da linha */
if ( NLIDOS != NITEMS ) /* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
else ... ; /* processamento da informao lida */
}
...
fclose (FPENT); /* fecho do ficheiro */
...
A Figura 3.11 apresenta um exemplo em que vamos ler um ficheiro de texto formatado e
escrever a informao lida noutro ficheiro de texto formatado, eventualmente com um
formato diferente. Vamos considerar que o ficheiro de entrada foi aberto com sucesso,
sendo FPENT o ponteiro para FILE identificador do fluxo de texto de entrada, e que o
ficheiro para escrita foi aberto com sucesso, sendo FPSAI o ponteiro para FILE
identificador do fluxo de texto de sada. S as linhas que esto de acordo com o formato de
leitura esperado sero escritas no ficheiro de sada com o formato pretendido.
#define NITEMS 4
...
FILE *FPENT, *FPSAI; int NLIDOS;
...
/* leitura da linha completa de acordo com o formato esperado */
while ( (NLIDOS = fscanf (FPENT, "formato\n", ponteiros)) != EOF )
{
/* no ocorreu EOF mas preciso aferir a consistncia da linha */
if ( NLIDOS != NITEMS ) /* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
else
{
... ; /* processamento da informao lida */
/* escrita da linha completa de acordo com o formato pretendido */
fprintf (FPSAI, "formato\n", expresses);
}
...
}
...
fclose (FPENT); /* fecho do ficheiro de entrada */
fclose (FPSAI); /* fecho do ficheiro de sada */
...
Este mecanismo permite funo main comunicar directamente com o exterior e receber
os argumentos que foram escritos na linha de comando. Para tal, acrescenta-se ao
cabealho da funo, dois parmetros de entrada com a seguinte sintaxe.
int main ( int argc, char *argv[ ] )
Vamos considerar que queremos escrever um programa que utiliza dois ficheiros. Neste
caso o programa tem de receber pelo menos trs argumentos, que so, o nome do
programa e os nomes dos dois ficheiros. A Figura 3.13 apresenta o excerto de cdigo que
valida os argumentos de entrada. Se o nmero de argumentos for inferior a trs, o
programa escreve no dispositivo convencional de sada de erro, identificado por stderr,
como que o programa deve ser invocado e termina a execuo do programa com
indicao de finalizao sem sucesso.
...
int main (int argc, char *argv[])
{
if ( argc < 3 ) /* nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s ficheiro ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
... /* processamento dos ficheiros */
return EXIT_SUCESS;
}
A Figura 3.14 apresenta o programa. Temos dois ficheiros de texto, logo necessitamos de
dois ponteiros para FILE. Vamos designar o ponteiro para o ficheiro de entrada por
FPENT e o ponteiro para o ficheiro de sada por FPSAI. Os nomes dos ficheiros vo ser
passados na linha de comando. Vamos considerar que o primeiro argumento argv[1], o
nome do ficheiro de entrada e o segundo argumento argv[2], o nome do ficheiro de
sada. O nmero de argumentos argc, bem como, a abertura dos ficheiros validada e em
qualquer situao de erro o programa termina com indicao de finalizao sem sucesso.
No caso da abertura sem sucesso do ficheiro de sada, o programa deve fechar o ficheiro de
entrada, que entretanto foi aberto, antes de terminar. A leitura do ficheiro de entrada feita
carcter a carcter at ser atingido o fim do ficheiro. Cada carcter escrito no ficheiro de
sada, transformado para minsculo atravs da funo tolower. Como a funo s converte
o carcter se ele for um carcter alfabtico maisculo, ento no h a necessidade de testar
previamente o carcter. Se o carcter for o carcter de fim de linha, o \n, ele escrito no
ficheiro de sada provocando uma mudana de linha. Assim transporta-se o formato do
ficheiro de entrada para o ficheiro de sada, com excepo dos caracteres alfabticos
maisculos que passam para caracteres alfabticos minsculos. O carcter \ o carcter
continuador de linha, que se utiliza para decompor linhas compridas.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main (int argc, char *argv[])
{
FILE *FPENT, *FPSAI; char CAR;
if ( argc < 3 ) /* o nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s ficheiro ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
/* abertura do ficheiro de entrada cujo nome argv[1] */
if ( (FPENT = fopen (argv[1], "r") ) == NULL )
{
fprintf (stderr, "No foi possvel abrir o ficheiro %s\n"\
, argv[1]);
exit (EXIT_FAILURE);
}
/* criao do ficheiro de sada cujo nome argv[2] */
if ( (FPSAI = fopen (argv[2], "w") ) == NULL )
{
fprintf (stderr, "No foi possvel criar o ficheiro %s\n"\
, argv[2]);
fclose (FPENT); /* fecho do ficheiro de entrada */
exit (EXIT_FAILURE);
}
/* leitura do carcter do ficheiro de entrada e escrita do */
/* carcter convertido para minsculo no ficheiro de sada */
while ( fscanf (FPENT, "%c", &CAR) != EOF )
fprintf (FPSAI, "%c", tolower(CAR));
fclose (FPENT); /* fecho do ficheiro de entrada */
fclose (FPSAI); /* fecho do ficheiro de sada */
return EXIT_SUCCESS;
}
A Figura 3.15 apresenta o programa. Uma vez que o nome de uma pessoa constitudo
por nomes separados pelo espao, no se pode usar o formato de leitura %s. Tem que se
utilizar o formato alternativo para a leitura de cadeias de caracteres que o %[]. Como o
nmero de registo da pessoa e o ano de nascimento no so precisos, a leitura pode
descartar esses itens, no havendo a necessidade de declarar variveis para esses itens. A
linha deve ser lida de forma a colocar o indicador de posio do ficheiro no incio da linha
seguinte, pelo que, o carcter de fim de linha tambm deve ser lido. Portanto, o formato
apropriado deve ser %*d:%40[^:]:%d:%d:%*d:%9s\n. O nome do ficheiro vai ser
passado na linha de comando. O nmero de argumentos e a abertura do ficheiro so
13 CAPTULO 3 : FICHEIROS EM C
#include <stdio.h>
#include <stdlib.h>
#define NITEMS 4
int main (int argc, char *argv[])
{
FILE *FPENT; char NOME[41], TEL[10]; int NLIDOS, DIA, MES;
if ( argc < 2 ) /* o nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s nome do ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
/* abertura do ficheiro de entrada cujo nome argv[1] */
if ( (FPENT = fopen (argv[1], "r") ) == NULL )
{
fprintf (stderr, "No foi possvel abrir o ficheiro %s\n"\
, argv[1]);
exit (EXIT_FAILURE);
}
printf ("Aniversariantes a 29 de Fevereiro\n");
/* leitura da linha do ficheiro de entrada */
while ( (NLIDOS = fscanf (FPENT, "%*d:%40[^:]:%d:%d:%*d:%9s\n"\
, NOME, &DIA, &MES, TEL)) != EOF )
if ( NLIDOS != NITEMS ) /* lido o nmero esperado de items? */
fprintf (stderr, "Linha desformatada\n");
/* escrita no monitor dos aniversariantes a 29/Fev */
else if ( DIA == 29 && MES == 2)
printf ("NOME -> %-40.40s TELEFONE -> %s\n", NOME, TEL);
fclose (FPENT); /* fecho do ficheiro */
return EXIT_SUCCESS;
}
Figura 3.15 - Processamento de um ficheiro constitudo por linhas com um formato especfico.
Como exerccio de treino, altere o programa para escrever a informao de sada num
ficheiro de texto, cujo nome passado na linha de comando.
A funo fwrite escreve no fluxo binrio, cujo nome indicado pelo identificador do fluxo,
o nmero de elementos indicado, todos com o mesmo tamanho em bytes, que esto
armazenados na regio de memria referenciada pela localizao indicada. O processo de
escrita termina quando escrito o nmero total de bytes pretendido, ou quando ocorre um
erro. A funo devolve o nmero de elementos que foram efectivamente escritos no fluxo
de sada, que pode ser menor do que o nmero de elementos pretendidos, se ocorreu um
erro. Se o nmero de elementos escritos for zero, o valor devolvido 0 (zero) e o fluxo
binrio no afectado.
A Figura 3.17 exemplifica a utilizao das funes fread e fwrite para ler e escrever uma
estrutura de tipo TDADOS_PESSOA em ficheiros binrios.
TDADOS_PESSOA PESSOA;
... /* leitura de uma estrutura */
fread (&PESSOA, sizeof (TDADOS_PESSOA), 1, FPENT);
... /* escrita de uma estrutura */
fwrite (&PESSOA, sizeof (TDADOS_PESSOA), 1, FPOUT);
Embora a sua aplicao possa ser feita com qualquer tipo de fluxo de comunicao, o seu
uso com fluxos de texto muito restrito. A norma ANSI impe que, neste caso, o ponto
de referncia obrigatoriamente SEEK_SET e o deslocamento ou 0 (zero), ou o valor
devolvido por uma invocao prvia da funo ftell.
A funo ftell devolve o valor do indicador de posio do ficheiro indicado. Para os fluxos
binrios, o valor devolvido representa a distncia em bytes a partir do princpio da
informao armazenada. Para os fluxos de texto, o valor devolvido contm um valor, que
depende da implementao e que pode ser usado posteriormente por uma invocao de
fseek para posicionamento do indicador de posio do ficheiro na mesma posio. A
funo devolve o valor do indicador de posio do ficheiro, em caso de sucesso, e o valor
1L, em caso de erro. No caso de insucesso, a causa sinalizada na varivel global de erro
errno.
A funo rename altera o nome do ficheiro cujo nome indicado por nome antigo para o
nome novo. A funo devolve 0 (zero), se tiver sucesso, e um valor no nulo, em caso
contrrio.
A funo tmpnam gera um nome vlido de ficheiro que distinto de qualquer nome j
existente. At TMP_MAX vezes, no mnimo 25, garantido que os nomes sucessivamente
gerados so diferentes. Se o nome do ficheiro for um ponteiro nulo, a funo devolve a
localizao de uma regio de memria interna onde est armazenado o nome, caso
contrrio, dever apontar para uma regio de memria com capacidade de armazenamento
de L_tmpnam caracteres, e ser esse o valor devolvido. O valor de L_tmpnam est
definido no ficheiro de interface limits.h.
17 CAPTULO 3 : FICHEIROS EM C
nome
registo base nmero de telefone
data de aniversrio
corpo
A Figura 3.23 apresenta o programa da agenda telefnica, ou seja, a funo main. Para a sua
implementao o programa tem como variveis de entrada, o nome do ficheiro e a opo
pretendida pelo utilizador. Como o ficheiro binrio vai ser utilizado como estrutura de
dados de suporte da agenda telefnica, ento o ficheiro, ou melhor o fluxo binrio
associado com o ficheiro, uma varivel interna do programa.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Figura 3.24 - Funo para escrever o menu de operaes e ler a opo pretendida.
A funo LER_NOME_FICHEIRO, que se apresenta na Figura 3.25, um procedimento
que tem como parmetro de sada o nome do ficheiro que lido do teclado.
return;
}
/* eliminao do registo */
if (PREG < NREG-1) DESLOCAR_PARA_CIMA (FP, NREG, PREG);
/* actualizao do nmero de registos armazenados */
ESCREVER_NUMREG (FP, NREG-1);
}
unsigned int POS_INSERCAO (FILE *FP, unsigned int NR, TREG REG)
{
TREG REGAUX; /* registo auxiliar */
unsigned int R = 0; /* posio de insero determinada */
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
do
{
LER_REGISTO_FICHEIRO (FP, R, ®AUX);
if ( strcmp (REG.NOME, REGAUX.NOME) < 0 ) break;
R++;
} while (R < NR);
return R;
}
unsigned int POS_ELIMINACAO (FILE *FP, unsigned int NR, char NTEL[])
{
TREG REGAUX; /* registo auxiliar */
unsigned int R = 0; /* posio de eliminao determinada */
void LER_REGISTO_FICHEIRO (FILE *, unsigned int, TREG *);
do
{
LER_REGISTO_FICHEIRO (FP, R, ®AUX);
if ( !strcmp (NTEL, REGAUX.NTELEF) ) break;
R++;
} while (R < NR);
return R;
}
A insero do registo no ficheiro exige deslocar para baixo os registos do ficheiro a partir
do ndice que foi detectado como local de insero do novo registo. A Figura 3.39
apresenta a funo DESLOCAR_PARA_BAIXO. Tem como parmetros de entrada o
ficheiro, o nmero de registos armazenados e o ndice de insero do registo. Cada registo
lido do ficheiro e escrito de novo no ficheiro mas uma posio mais abaixo. O
deslocamento dos registos feito do fim do ficheiro at ao ndice de insero, como se
exemplifica na parte esquerda da Figura 3.41.
P ler P escrever
escrever 4 ler
1
ler escrever
escrever
3 ler
2
ler escrever
escrever
2 ler
3
NR ler
NR-1 escrever
escrever
1 ler
4
NR+1 NR
Figura 3.42 - Funo para ler o nmero de registos armazenado no cabealho do ficheiro.
void ESCREVER_NUMREG (FILE *FP, unsigned int NR)
{
if ( fseek (FP, 0, SEEK_SET) != 0 )
{
perror ("erro no posicionamento do ficheiro - cabealho");
exit (EXIT_FAILURE);
}
if ( fwrite (&NR, sizeof (unsigned int), 1, FP) != 1 )
{
fprintf (stderr, "erro na escrita do cabealho do ficheiro\n");
exit (EXIT_FAILURE);
}
if ( fflush (FP) != 0 )
{
perror ("erro na escrita efectiva no ficheiro");
exit (EXIT_FAILURE);
}
}
Sumrio
O que no significa que os algoritmos recursivos sejam mais eficientes que os algoritmos
iterativos equivalentes. Pelo contrrio, alguns algoritmos recursivos desencadeiam um
nmero arbitrariamente grande de invocaes sucessivas da funo, pelo que, nestes casos
temos uma ineficincia acrescida associada invocao de funes. Por outro lado, alguns
algoritmos recursivos so computacionalmente ineficientes, devido ao facto de as mltiplas
invocaes recursivas repetirem o clculo de valores.
A Figura 4.4 representa a anlise grfica da invocao da funo factorial recursiva para o
clculo de 3!. Cada caixa representa o espao de execuo de uma nova invocao da
funo. Se considerarmos cada caixa como um n estamos perante o que se designa por
rvore de recorrncia da funo. Temos que 3! = 3 2!, por sua vez 2! = 2 1!, por sua
vez 1! = 1 0!, e finalmente a invocao recursiva termina com a condio de paragem,
sendo 0! = 1. O valor de 3! calculado no retorno das sucessivas invocaes da funo
recursiva, pelo que, 3! = 1 1 2 3 = 6.
FACTORIAL(3) = 3 * FACTORIAL(2)
3 * 2
FACTORIAL(2) = 2 * FACTORIAL(1)
2 * 1
FACTORIAL(1) = 1 * FACTORIAL(0)
1 * 1
FACTORIAL(0) = 1
Como bem sabido, a funo factorial explode rapidamente, assumindo valores de tal
maneira elevados que s podem ser armazenados em variveis de tipo real, com a
consequente perda de preciso. Pelo que, uma forma de resolver o problema, passa por
eliminar a necessidade do clculo do factorial recorrendo a um processo recorrente. Uma
vez que se trata de uma srie geomtrica, poderamos calcular cada elemento da srie a
partir do elemento anterior e adicionar os elementos calculados. Mas, se aplicarmos de um
modo sistemtico a propriedade distributiva da multiplicao relativamente adio,
obtemos uma expresso, em que o clculo do factorial desaparece.
x2 x2 x2 x2
seno( x, N ) x u 1 u 1 u 1 u 1 u ...
2u3 4 u 5 6 u 7 8 u 9
Esta expresso pode ser calculada de duas maneiras. Podemos calcul-la de baixo para cima
(bottom-up), ou seja, do termo P4 para o termo P1, atravs de um processo iterativo, ou em
alternativa podemos calcular a expresso de cima para baixo (top-down), ou seja, do termo P1
para o termo P4, atravs de um processo recursivo.
Vamos comear por analisar a implementao iterativa. Neste caso vamos calcular de
forma repetitiva a expresso entre parntesis da direita para a esquerda, ou seja, do termo
P4 at ao termo P1, utilizando o passo iterativo dado pela seguinte expresso.
5 CAPTULO 4 : RECURSIVIDADE
x2
Pi 1 1 u Pi
2 u i u 2 u i 1
Como estamos perante o clculo de um produto acumulado, Pi tem de ser inicializado a
1.0, que o elemento neutro do produto. Depois do clculo repetitivo, temos que o valor
final do seno seno( x, N ) x u P1 . A Figura 4.5 apresenta a funo iterativa.
Figura 4.5 - Funo iterativa que calcula a expanso em srie de Taylor da funo seno.
Vamos agora analisar a implementao recursiva. Neste caso vamos calcular a expresso
entre parntesis da esquerda para a direita, ou seja, do termo P1 at ao termo P4, sendo que,
cada termo calculado em funo do termo seguinte ainda por calcular. Da a necessidade
do processo ser recursivo. O passo recursivo dado pela seguinte expresso.
x2
Seno( x, N , i ) 1 u Seno( x, N , i 1)
2 u i u 2 u i 1
O processo recursivo terminado quando o termo a calcular o ltimo termo desejado, ou
seja, quando i igual a N, cujo valor dado pela seguinte expresso.
x2
Seno( x, N , N ) 1
2 u N u 2 u N 1
Caso o nmero de termos pretendidos na expanso seja apenas um, o valor final do seno
x e no h a necessidade de invocar o clculo recursivo. Caso, o nmero de termos seja
maior do que um, ento o valor final do seno igual a x vezes o valor obtido pelo clculo
recursivo do primeiro elemento, ou seja, seno( x, N ) x u Seno( x, N ,1) .
Figura 4.6 - Funo recursiva que calcula a expanso em srie de Taylor da funo seno.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6
Neste exemplo, a verso recursiva para alm do desperdcio de tempo gasto com a
invocao sucessiva da funo, tem uma implementao mais extensa em termos de
cdigo, uma vez que a soluo tem de ser decomposta em duas funes. Pelo que, a verso
iterativa mais eficiente e simples de programar.
FIBONACCI(5)
FIB(5) = FIB(4)+FIB(3)
FIBONACCI(4) FIBONACCI(3)
FIBONACCI(1) FIBONACCI(0)
FIB(1) = 1 FIB(0) = 0
return FIB[N];
}
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
A Figura 4.14 apresenta a funo de clculo das permutaes. Assume-se que LISTA uma
cadeia de N caracteres terminada com o carcter nulo, de maneira a simplificar a sua escrita
no monitor. Para trocar os caracteres utiliza-se a funo TROCA.
#include <stdio.h>
#include <string.h>
Quando o elemento da ltima linha e ltima coluna da matriz nulo a coluna tem de ser
trocada com outra coluna, cujo ltimo elemento no seja nulo, de forma a colocar um valor
no nulo na diagonal. Caso no haja nenhuma coluna nessa situao, ento sinal que
todos os elementos da ltima linha so nulos, pelo que, o determinante nulo. Sempre que
se trocam duas colunas de uma matriz o determinante tem de ser multiplicado por 1. Ao
dividir-se a ltima coluna pelo ltimo elemento pe-se em evidncia esse elemento e depois
para anular a ltima linha da matriz, basta subtrair todas as colunas menos a ltima, pela
ltima coluna multiplicada pelo ltimo elemento da coluna a processar. Uma vez que,
depois deste processamento a ltima linha da matriz constituda apenas por zeros, com
excepo do ltimo elemento da linha, ou seja, o elemento que est na diagonal, agora o
determinante desta matriz igual a este valor multiplicado pelo determinante da matriz de
ordem N1. O processo invocado recursivamente at N ser igual a 1. Nessa altura o
determinante o prprio valor. Em alternativa pode-se parar o processo recursivo quando
N igual a 2, uma vez que fcil calcular o determinante dessa matriz.
#define NMAX 10
...
double CALC_DETERMINANTE (double MATRIZ[][NMAX], unsigned int N)
{
int COL_AUX, NC, NL, UE = N-1; double ELEMENTO;
if (N == 1) return MATRIZ[0][0]; /* condio de paragem */
else
{
COL_AUX = UE; /* procurar coluna com ltimo elemento 0 */
while ( (COL_AUX >= 0) && (MATRIZ[UE][COL_AUX] == 0) )
COL_AUX--;
if (COL_AUX >= 0) /* se existir tal coluna */
{
if (COL_AUX != UE) /* se no for a ltima coluna */
for (NL = 0; NL < N; NL++) /* trocar as colunas */
{
ELEMENTO = MATRIZ[NL][UE];
MATRIZ[NL][UE] = MATRIZ[NL][COL_AUX];
MATRIZ[NL][COL_AUX] = -ELEMENTO;
}
/* dividir a coluna N-1 pelo ltimo elemento pondo-o em evidncia */
for (NL = 0; NL < UE; NL++)
MATRIZ[NL][UE] /= MATRIZ[UE][UE];
/* subtrair todas as colunas menos a ltima pela ltima coluna */
/* multiplicada pelo ltimo elemento da coluna a processar */
for (NC = 0; NC < UE; NC++)
for (NL = 0; NL < UE; NL++)
MATRIZ[NL][NC] -= MATRIZ[NL][UE] * MATRIZ[UE][NC];
/* invocao recursiva para a matriz de dimenso N-1 */
return MATRIZ[UE][UE] * CALC_DETERMINANTE (MATRIZ, N-1);
}
else return 0;
}
}
Figura 4.17 - Funo recursiva que calcula o determinante de uma matriz quadrada.
Figura 4.18 - Execuo da funo recursiva que calcula o determinante de uma matriz quadrada.
13 CAPTULO 4 : RECURSIVIDADE
Assim para o caso apresentado na Figura 4.19 em que temos quatro discos na Torre A, a
soluo passa por movimentar os trs discos de cima da Torre A para a Torre C, depois
mudar o quarto disco da Torre A para a Torre B e finalmente mudar os trs discos que se
encontram na Torre C para a Torre B.
Aps termos mudado os trs primeiros discos da Torre A para a Torre C e termos atingido
a situao apresentada na terceira linha da Figura 4.19, ento para mudar os trs discos que
se encontram na Torre C para a Torre B aplica-se de novo a soluo recursiva, mas agora a
torre de partida a Torre C, a torre de chegada a Torre B e a torre auxiliar a Torre A.
A Figura 4.21 e a Figura 4.22 apresentam o programa que simula as Torres de Hani. Para
implementar as trs torres utilizam-se trs agregados, sendo os discos representados por
nmeros inteiros de 1 a N. O programa principal l do teclado o nmero de discos, valida o
seu valor, constri a situao inicial, imprime-a no monitor e depois invoca a funo para
simular a mudana dos discos.
#include <stdio.h>
#define D_MAX 10
/* agregados para as torres */
static int TORREA[D_MAX], TORREB[D_MAX], TORREC[D_MAX];
static int NDA, NDB, NDC; /* nmero de discos de cada torre */
void INICIALIZAR (int, int [], int *, int [], int *, int [], int *);
void IMPRIMIR (void);
void MUDAR_DISCOS (int, int [], int *, int [], int *, int [], int *);
int main (void)
{
int NDISCOS; /* nmero de discos a colocar na Torre A */
do
{
printf ("Numero de discos = "); scanf ("%d", &NDISCOS);
} while ( (NDISCOS <= 0) || (NDISCOS > DISCOS_MAX) );
INICIALIZAR (NDISCOS, TORREA, &NDA, TORREB, &NDB, TORREC, &NDC);
printf ("---------------------------------\n");
printf ("| Torres de Hanoi |\n");
printf ("| Numero de discos = %2d |\n", NDISCOS);
printf ("---------------------------------\n");
printf ("| TORRE A TORRE B TORRE C |\n");
printf ("---------------------------------\n");
IMPRIMIR ();
MUDAR_DISCOS (NDISCOS, TORREA, &NDA, TORREB, &NDB, TORREC, &NDC);
return 0;
}
void INICIALIZAR (int ND, int TA[], int *NDA, int TB[], int *NDB,\
int TC[], int *NDC);
{
int I;
for (I = 0; I < D_MAX; I++)
{
TA[I] = 0; TB[I] = 0; TC[I] = 0; /* limpar os agregados */
}
for (I = 0; I < ND; I++) TA[I] = ND - I; /* discos na Torre A */
void MUDAR_DISCOS (int ND, int TA[], int *NDA, int TB[], int *NDB,\
int TC[], int *NDC);
{
if (ND == 1) /* condio de paragem */
{ /* mudar o disco da Torre A para a Torre B */
(*NDB)++; TB[*NDB-1] = TA[*NDA-1]; (*NDA)--;
IMPRIMIR ();
}
else
{
/* mudar os N-1 discos de cima da Torre A para a Torre C */
MUDAR_DISCOS (ND-1, TA, NDA, TC, NDC, TB, NDB);
/* mudar o ltimo disco da Torre A para a Torre B */
(*NDB)++; TB[*NDB-1] = TA[*NDA-1]; (*NDA)--;
IMPRIMIR ();
/* mudar os N-1 discos da Torre C para a Torre B */
MUDAR_DISCOS (ND-1, TC, NDC, TB, NDB, TA, NDA);
}
}
---------------------------------
| Torres de Hanoi |
| Numero de discos = 3 |
---------------------------------
| TORRE A TORRE B TORRE C |
---------------------------------
1
2
3
---------------------------------
2
3 1
---------------------------------
3 1 2
---------------------------------
1
3 2
---------------------------------
1
3 2
---------------------------------
1 3 2
---------------------------------
2
1 3
---------------------------------
1
2
3
---------------------------------
4.10 Exerccios
1. Pretende-se escrever um programa que imprima no monitor, uma tabela em que se
compara os valores da funo co-seno calculada pela expanso em srie de Taylor para 5,
10 e 15 termos e pela funo matemtica cos. Os valores inicial e final da tabela, bem como
o nmero de elementos da tabela so lidos do teclado. O termo geral da expanso em srie
de Taylor da funo co-seno dado pela seguinte expresso esquerda, sendo apresentada
direita a sua expanso para os primeiros cinco termos da srie.
N 1 2n
x2 x4 x6 x8
coseno( x, N ) 1n u x coseno( x,5 ) 1 ...
n 0 2n ! 2! 4! 6! 8!
----------------------------------------------------------------------
| x | coseno(x,5) | coseno(x,10) | coseno(x,15) | cos(x) |
----------------------------------------------------------------------
| ##.#### | #.######### | #.######### | #.######### | #.######### |
----------------------------------------------------------------------
...
----------------------------------------------------------------------
| ##.#### | #.######### | #.######### | #.######### | #.######### |
----------------------------------------------------------------------
Sumrio
O subprograma pode ser visto como uma caixa preta, que recebe informao entrada e
que produz informao sada, escondendo no entanto os detalhes da implementao da
operao, ou seja, as aces que dentro do subprograma transformam a informao de
entrada na informao de sada. Estas aces so invisveis externamente e, portanto, no
originam qualquer interaco com o exterior. Este conjunto de aces detalhadas, que
representam a soluo do problema, designa-se por algoritmo. Ao encapsulamento do
algoritmo dentro de um subprograma, designa-se por abstraco procedimental.
Neste paradigma de programao, os subprogramas podem ser vistos como blocos que
podem ser interligados para construir programas mais complexos, fazendo uma montagem
do programa da base para o topo (Bottom-Up Assembly). Permitindo assim, validar a soluo
do problema de uma maneira controlada e integrar de um modo progressivo os diferentes
subprogramas, avaliando possveis solues alternativas atravs de diferentes arranjos de
subprogramas.
3 CAPTULO 5 : MEMRIAS
Por outro lado, ao longo dos anos, a nfase na implementao do software dirigiu-se em
direco organizao de estruturas de dados cada vez mais complexas. Para estruturas de
dados complexas no faz sentido implementar subprogramas de uma forma isolada, mas
sim providenciar um conjunto de subprogramas que as manipulam e que permitem que as
estruturas de dados sejam encaradas como um novo tipo de dados da linguagem. Um tipo
de dados permite a declarao de variveis desse tipo e tem associado um conjunto de
operaes permitidas sobre essas variveis.
Esta filosofia de programao permite a criao de estruturas de dados abstractas, uma vez
que os detalhes da sua implementao esto escondidos, ou seja encapsulados no mdulo.
Permite tambm a sua proteco uma vez que no esto acessveis a partir do exterior a
no ser atravs das operaes de processamento disponibilizadas pelo mdulo. Permite
ainda, a criao de operaes virtuais, uma vez que se podem experimentar vrios
algoritmos alternativos de manipulao das estruturas de dados, atravs da criao de
mdulos de implementao alternativos, para a mesma interface.
Por vezes um mdulo includo noutro mdulo, que por sua vez includo noutro
mdulo e assim sucessivamente at que um ou mais destes mdulos so includos nos
ficheiros fonte da aplicao. De maneira a evitar uma possvel mltipla incluso de um
mdulo na aplicao, ele deve ser includo condicionalmente. Para tal, existe a directiva do
pr-processador #ifndef _MODULO #define _MODULO endif que assegura que o
mdulo, cujo nome MODULO, includo apenas uma vez.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6
Aps a definio das estruturas de dados, o ficheiro de implementao faz a definio das
funes exportadas pelo mdulo, ou seja, das funes que foram declaradas no ficheiro de
interface. Por vezes, para implementar uma funo necessrio recorrer a funes
auxiliares de maneira a estruturar melhor a sua funcionalidade. Se estas funes auxiliares
forem partilhadas por vrias funes, nesse caso devem ser aludidas logo a seguir
declarao das estruturas de dados. Estas funes auxiliares, sendo funes internas do
mdulo, devem ser declaradas com o qualificativo static. Desta forma so invisveis para o
exterior, pelo que, os seus nomes podem ser utilizados noutros mdulos para implementar
outras funes internas a esses mdulos.
O mdulo tambm fornece uma funo que permite libertar a memria atribuda para um
nmero complexo, quando ele no mais preciso, ou seja, uma funo para apagar o
nmero complexo. Para evitar divises por zero, preciso uma funo que verifica se um
nmero complexo o complexo nulo. Existem ainda funes para extrair a parte real e a
parte imaginria do nmero complexo.
struct complexo
{
double Real; double Imag;
};
Numa fila, o posicionamento para a colocao de um novo elemento, que vamos designar
por FIFO_In, a cauda da fila (fifo tail ), e o posicionamento para a remoo de um
elemento, que vamos designar por FIFO_Out, a cabea da fila (fifo head ). A colocao
de um novo elemento consiste na adio de um novo elemento no fim da fila, ficando este
novo elemento a ser a cauda da fila, e na escrita da informao nesse elemento. A remoo
de um elemento consiste na leitura da informao armazenada no elemento que se
encontra na cabea da fila e da eliminao desse elemento da fila. Consequentemente, o
elemento seguinte, caso o haja, passa a ser a cabea da fila. Quando retirado o ltimo
elemento, a fila fica vazia, sendo apenas possvel efectuar a operao de colocao de
elementos na fila. A situao inversa de fila cheia tambm pode acontecer para certo tipos
de implementaes, mais concretamente para implementaes estticas e semiestticas.
Como o acesso memria fila est limitado aos elementos posicionados nos extremos da
memria, a fila mantm dois indicadores de posio para a cabea e a cauda da fila. Ao
contrrio da memria de acesso aleatrio, o tamanho da fila dinmico e depende do
nmero de elementos colocados e do nmero de elementos retirados da fila.
Numa pilha, o posicionamento para a colocao de um novo elemento, que se designa por
STACK_Push, e o posicionamento para a remoo de um elemento, que se designa por
STACK_Pop, o topo da pilha (top of the stack). A colocao de um novo elemento
consiste na adio de um novo elemento, em cima do topo da pilha e na escrita da
informao nesse elemento, ficando este novo elemento a ser o topo da pilha. A remoo
de um elemento consiste na leitura da informao armazenada no elemento que se
encontra no topo da pilha e da eliminao desse elemento da pilha, ficando o elemento
anterior, caso o haja, a ser o topo da pilha. Quando retirado o ltimo elemento, a pilha
fica vazia, sendo apenas possvel efectuar a operao de colocao de elementos na pilha. A
situao inversa de pilha cheia tambm pode acontecer para certo tipos de implementaes,
mais concretamente para implementaes estticas e semiestticas.
Como o acesso memria pilha est limitado a este elemento posicionado no extremo da
memria, a pilha mantm um indicador de posio para o topo da pilha. Tal como na fila, o
tamanho da pilha tambm dinmico.
15 CAPTULO 5 : MEMRIAS
Como a memria tem que estar sempre ordenada pela chave de pesquisa, as operaes de
colocao e de remoo de elementos podem implicar deslocamentos dos elementos da
memria para permitir a colocao do elemento na memria ou para compensar a
eliminao do elemento da memria. O facto da memria estar sempre ordenada, permite
tambm implementar de forma eficiente operaes de leitura de informao, que so
normalmente muito importantes neste tipo de memria. Essas operaes so a leitura do
primeiro elemento da memria que tem uma determinada chave, que vamos designar por
CAM_Read_First, e a leitura de elementos sucessivos com a mesma chave, que vamos
designar por CAM_Read_Next.
ltimo elemento
RAM_Read (I)
RAM esttica RAM_Write (I)
ltimo elemento
RAM semiesttica
Para implementar uma memria de acesso aleatrio dinmica seria preciso recorrer a uma
lista ligada de elementos. S que depois a memria s poderia ser acedida sequencialmente
a partir do elemento inicial e perder-se-ia o acesso indexado, pelo que, a memria deixaria
ento de ter as caractersticas de acesso aleatrio. Portanto, no existe implementao
dinmica da memria de acesso aleatrio.
cabea cauda
da fila da fila
cabea
da fila PtSeg PtSeg PtSeg PtSeg
PtEle PtEle PtEle PtEle
cauda
FIFO_Out FILA dinmica da fila FIFO_In
... Ponteiro
Elemento
STACK[I] Ponteiro
Elemento
STACK[1] Ponteiro
Elemento Elemento
STACK[0] Ponteiro
Elemento Elemento
topo da
pilha PtEle Elemento STACK_Pop
PtAnt
PtEle Elemento
PtAnt
PtEle Elemento
PtAnt
pesquisa binria
CAM_In (CHAVE)
ltimo
CAM_Out (CHAVE) CAM esttica
elemento
ltimo elemento
pesquisa binria
CAM_In (CHAVE)
CAM_Out (CHAVE) CAM semiesttica
Para obter uma implementao mais eficiente da memria associativa, essencial utilizar
uma estrutura de dados que evite a necessidade de fazer deslocamentos dos elementos,
quando se pretende colocar ou retirar um elemento da memria. Para isso precisamos de
uma estrutura de dados dinmica em que os elementos podem ser colocados e retirados
por ajustamento de ligaes entre os elementos. Mas, no pode ser uma lista ligada, porque
para pesquisar a memria procura de um elemento com uma determinada chave, a
memria tem de ser pesquisada em ambos os sentidos. Pelo que, a lista tem de ser biligada.
Uma lista biligada uma lista em que cada n tem um ponteiro para o n seguinte e um
ponteiro para o n anterior. Sendo que, o n inicial aponta para trs para NULL e o n
final aponta para a frente para NULL, para servirem de indicadores de finalizao da lista.
Colocar ou retirar um elemento numa lista biligada, implica fazer ou desfazer mais ligaes.
Mas, em contrapartida todas as operaes de ligao ou desligao ao n de atrs e ao n
da frente so possveis de fazer, tendo apenas um ponteiro a indicar, o n atrs ou frente
do qual se vai colocar o novo elemento, ou o n do elemento que vai ser eliminado.
A Figura 5.21 apresenta esta implementao dinmica linear, que mantm um ponteiro para
o primeiro n da lista, que se designa por cabea da memria. O inconveniente desta
implementao tem a ver com a eficincia da pesquisa de informao. Numa lista, seja ela
ligada ou biligada, s se pode implementar a pesquisa sequencial, a partir da cabea da lista.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 24
cabea
PtSeg PtSeg PtSeg PtSeg
da CAM
PtAnt PtAnt PtAnt PtAnt
PtEle PtEle PtEle PtEle
pesquisa sequencial
CAM_In (CHAVE)
CAM dinmica linear CAM_Out (CHAVE)
CAM_In (CHAVE)
CAM_Out (CHAVE)
CHAVE 1 CHAVE 41
. .
Elemento Elemento
. .
. .
2 pesquisa sequencial
CHAVE 2
CAM semiesttica/dinmica Elemento
A situao de coliso acontece quando dadas duas chaves diferentes, a funo de disperso
calcula a mesma a posio de colocao. O que compromete a implementao da memria,
uma vez que, no se pode colocar dois elementos na mesma posio da tabela. Portanto,
tem que se resolver o problema das colises. Uma maneira passa por reduzir o seu nmero,
utilizando uma funo de disperso que assegure uma boa disperso.
Mas, independentemente da funo usada vo existir sempre colises. Portanto, tem de ser
criada uma estrutura de dados que permita colocar mais do que um elemento na mesma
posio da tabela. Uma soluo possvel consiste em criar uma estrutura semiesttica em
que cada elemento do agregado um ponteiro que aponta para uma lista ligada de
elementos. Temos assim uma tabela de listas ligadas.
No entanto, a grande limitao da tabela de disperso tem a ver com o facto da estrutura de
suporte ser um agregado, que uma estrutura de dados esttica. Se a memria associativa
necessitar de crescer esta soluo no a mais adequada. Portanto, necessria uma
estrutura de dados dinmica que permita colocar e retirar elementos de forma eficiente,
como na lista biligada, mas que suporte tambm uma pesquisa eficiente, como o caso da
pesquisa binria. Tal estrutura de dados a rvore binria de pesquisa. A Figura 5.23
apresenta esta implementao dinmica hierrquica.
Uma rvore uma estrutura de dados constituda por uma coleco de ns. Esta coleco
pode ser nula, ou constituda por um n inicial, que se designa por raiz da rvore, e zero ou
mais subrvores. Portanto, uma estrutura de dados com uma organizao recursiva, em
que cada n tambm uma rvore. Uma rvore diz-se binria se cada n no tiver mais do
que duas subrvores, a subrvore da esquerda e a subrvore da direita. Numa rvore binria
de pesquisa, um elemento colocado na rvore de maneira que, os elementos da sua
subrvore da esquerda tm uma chave menor do que a sua chave e os elementos da sua
subrvore da direita tm uma chave maior do que a sua chave.
Para implementar uma rvore binria, cada n da rvore constitudo por trs ponteiros.
Um para o elemento que armazena a informao e os outros dois para os ns seguintes da
rvore, ou seja, para as subrvores da esquerda e da direita. Quando o n seguinte no
existe, ento o respectivo ponteiro aponta para NULL, para servir de indicador de
inexistncia da subrvore.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C
Figura 5.23 - Implementao dinmica hierrquica da memria associativa.
pesquisa binria
PtEsq PtEle PtDir
CHAVE 4
PtEle Elemento PtEle
PtEsq PtDir PtEsq PtDir
CHAVE 2 CHAVE 6
PtEle Elemento PtEle Elemento PtEle
PtEsq PtDir PtEsq PtDir PtEsq PtDir
26
27 CAPTULO 5 : MEMRIAS
Numa rvore binria de pesquisa pode ser aplicada a pesquisa binria de forma recursiva,
mas, s em rvores completamente balanceadas que se consegue obter uma pesquisa
proporcional a log2N. Portanto, fundamental garantir-se em cada instante que a rvore se
encontra organizada numa estrutura to prxima quanto possvel do balanceamento
completo. Daqui resulta que, para se garantir a operacionalidade mxima de uma
organizao hierrquica em rvore binria, se deve conceber as operaes para colocar e
retirar elementos, como operaes invariantes em termos de balanceamento.
A soluo correcta passa por criar o agregado dinamicamente, recorrendo funo calloc,
assim que se saiba o nmero de nmeros contido no ficheiro. Para tal declara-se o ponteiro
PARINT que vai receber o endereo devolvido pela funo, que o endereo inicial do
bloco de memria atribudo para o agregado. Devido dualidade ponteiro agregado, o
ponteiro PARINT utilizado para aceder aos elementos do agregado, tal como se fosse um
agregado. O programa antes de terminar, deve libertar a memria atribuda dinamicamente,
usando para o efeito a funo free.
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
FILE *FP; int *PARINT, N, I;
if ( argc < 2 ) /* o nmero de argumentos suficiente? */
{
fprintf (stderr, "Uso: %s nome do ficheiro\n", argv[0]);
exit (EXIT_FAILURE);
}
/* abertura do ficheiro de entrada cujo nome argv[1] */
if ( (FP = fopen (argv[1], "r")) == NULL )
{
fprintf (stderr, "No foi possvel abrir o ficheiro %s\n"\
, argv[1]);
exit (EXIT_FAILURE);
}
/* leitura do nmero de nmeros inteiros armazenado no ficheiro */
fscanf (FP, "%d", &N);
/* atribuio de memria para um agregado de N inteiros */
if ( (PARINT = (int *) calloc (N, sizeof (int))) == NULL )
{
fprintf (stderr, "No foi possvel atribuir o agregado\n");
fclose (FP); /* fecho do ficheiro de entrada */
exit (EXIT_FAILURE);
}
for (I = 0; I < N; I++) /* leitura do ficheiro para o agregado */
fscanf (FP, "%d", &PARINT[I]);
fclose (FP); /* fecho do ficheiro de entrada */
... /* processamento do agregado */
free (PARINT);/* libertao da memria atribuda para o agregado */
return EXIT_SUCCESS;
}
Sumrio
Uma das tarefas mais habituais na programao a pesquisa de informao, o que exige o
desenvolvimento de algoritmos eficientes de pesquisa. Neste captulo introduzimos os
mtodos de pesquisa sequencial, de pesquisa binria e de pesquisa por tabela.
Apresentamos vrios algoritmos que utilizam o mtodo de pesquisa sequencial e o
algoritmo de pesquisa binria, nas verses iterativa e recursiva.
Por outro lado a pesquisa fortemente condicionada pela organizao da informao, e por
conseguinte, a ordenao uma tarefa ainda mais frequente. Apresentamos as classes mais
simples dos algoritmos de ordenao, que so, a ordenao por seleco, a ordenao por
troca e a ordenao por insero. Para cada uma delas expomos os algoritmos mais
simples, que apesar de no serem os mais eficientes, so contudo suficientemente rpidos
para pequenos agregados e apropriados para mostrar as caractersticas dos princpios de
ordenao. Explicamos os algoritmos de ordenao Sequencial ( Sequential ), Seleco
( Selection ), Bolha ( Bubble ), Concha ( Shell ), Crivo ( Shaker ) e Insero ( Insertion ),
mostramos a sua execuo com um pequeno agregado e fazemos a comparao do
desempenho de cada um deles. Apresentamos tambm o algoritmo de ordenao Fuso de
Listas ( Merge List ) que muito verstil, porque pode ser aplicado, quer na ordenao de
agregados, quer na ordenao de ficheiros. Apresentamos ainda os algoritmos de
ordenao recursivos Fuso ( Merge ) e Rpido ( Quick ), que so muito eficientes.
6.1 Pesquisa
Uma das tarefas mais comuns no dia a dia a pesquisa de informao. Mas, a pesquisa
depende muito da forma como a informao est organizada. Se a informao estiver
completamente desordenada no temos outra alternativa que no seja analisar toda a
informao por ordem, seja ela do princpio para o fim ou vice-versa, at encontrar o que
pretendemos. Este processo de pesquisa normalmente lento. Imagine, por exemplo,
procurar a nota de um teste numa pauta no ordenada, no caso de uma disciplina com
algumas centenas de alunos. Mas, se a informao estiver ordenada por uma ordem, seja ela
crescente ou decrescente no caso de informao numrica, ascendente ou descendente no
caso de informao textual, como por exemplo a mesma pauta listada por ordem do
nmero mecanogrfico, ento possvel fazer uma procura mais ou menos selectiva,
partindo a informao a procurar em intervalos sucessivos cada vez menores, evitando
assim analisar informao irrelevante e acelerar o processo de pesquisa. E, se a informao
alm de estar ordenada por ordem, estiver ainda composta com entradas especficas, como
por exemplo, uma pauta de uma disciplina com alunos de vrios cursos, que se apresenta
dividida em pautas por cursos, cada uma delas ordenada por nmero mecanogrfico, ento
possvel fazer uma procura dirigida pauta do respectivo curso e depois fazer uma
procura em funo do nmero mecanogrfico, optimizando ainda mais o processo de
pesquisa. Portanto, o mtodo de pesquisa inevitavelmente dependente da forma como a
informao est organizada e apresentada. Quanto mais ordenada estiver a informao,
mais eficiente poder ser o mtodo de pesquisa.
Tal como no dia a dia, a pesquisa de informao tambm uma tarefa trivial em
programao. Pesquisar um agregado procura da localizao de um determinado valor, ou
de uma determinada caracterstica acerca dos seus valores, uma tarefa muito frequente e
simples. Mas, computacionalmente dispendiosa porque, o agregado pode ser constitudo
por centenas ou mesmo milhares de elementos. Pelo que, exige o desenvolvimento de
algoritmos eficientes.
analisados seja reduzido a zero, o que significa, que o valor procurado no existe no
agregado.
Para os algoritmos apresentados neste captulo vamos considerar que a estrutura de dados a
pesquisar ou ordenar, um agregado de elementos inteiros de nome seq, com capacidade
para armazenar NMAX elementos, tendo no entanto apenas nelem elementos teis.
A Figura 6.1 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento com o maior valor armazenado na parte restante do agregado. No caso
de existir mais do que um elemento com o mesmo valor a funo devolve o ndice do
primeiro elemento encontrado. No incio assume-se que o maior valor o elemento de
ndice inicial e depois o agregado analisado at ao ltimo elemento, procura de um valor
ainda maior. Se pretendermos procurar o maior valor armazenado no agregado, basta
invocar a funo a partir do primeiro elemento do agregado.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 4
A Figura 6.2 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento com o menor valor armazenado na parte restante do agregado. No caso
de existir mais do que um elemento com o mesmo valor a funo devolve o ndice do
primeiro elemento encontrado. No incio assume-se que o menor valor o elemento de
ndice inicial e depois o agregado analisado at ao ltimo elemento, procura de um valor
ainda menor. Se pretendermos procurar o menor valor armazenado no agregado, basta
invocar a funo a partir do primeiro elemento do agregado.
A Figura 6.3 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento que armazena o valor que se pretende procurar na parte restante do
agregado. Como no temos a certeza de que o valor existe de facto no agregado, se a
pesquisa ultrapassar o ltimo elemento do agregado sem o ter encontrado, ento a funo
devolve o ndice 1 como sinal de pesquisa falhada. A pesquisa deve acabar assim que a
primeira ocorrncia do valor pretendido seja encontrado, pelo que, se usa a instruo de
return dentro do ciclo for, para terminar a pesquisa assim que se encontrar um elemento
com o valor pretendido. Se pretendermos pesquisar todo o agregado, basta invocar a
funo a partir do primeiro elemento do agregado.
Existem alguns problemas concretos do dia a dia, cuja soluo optimizada requer que se
procure um determinado valor num agregado, mas, se este valor no existir podemos em
alternativa utilizar um valor prximo do valor pretendido. Nesta situao podemos aplicar
uma de trs estratgias. A primeira e mais simples consiste em procurar no agregado, o
primeiro valor que no excede o valor procurado. Este algoritmo designa-se por o primeiro
que serve ( first fit ). A Figura 6.4 apresenta a funo que determina, a partir de um ndice
inicial de pesquisa, o ndice do primeiro elemento do agregado com um valor que no
excede o valor procurado. Como nesta pesquisa, no temos a certeza de que existe de facto
tal valor, se a pesquisa ultrapassar o ltimo elemento do agregado sem ter encontrado um
valor, ento a funo devolve o ndice 1 como sinal de pesquisa falhada. Como o nome
indica, a pesquisa deve acabar assim que seja encontrado o primeiro valor que sirva, pelo
que, se usa a instruo de return dentro do ciclo for, para terminar a pesquisa assim que se
encontrar um elemento com o valor pretendido. Se pretendermos pesquisar todo o
agregado, basta invocar a funo a partir do primeiro elemento do agregado.
Figura 6.4 - Funo que determina o elemento do agregado com o primeiro valor que serve.
Figura 6.5 - Funo que determina o elemento do agregado com o melhor valor que serve.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6
A Figura 6.6 apresenta a funo que determina, a partir de um ndice inicial de pesquisa, o
ndice do elemento do agregado com o pior valor que no excede o valor pretendido.
Comea-se por procurar o primeiro valor que serve. Caso ele no exista, a funo devolve
o ndice 1 como sinal de pesquisa falhada. Caso contrrio procura-se at ao ltimo
elemento do agregado um elemento com um valor pior, ou seja, um elemento com um
valor que seja ainda menor do que o j encontrado. Se pretendermos pesquisar todo o
agregado, basta invocar a funo a partir do primeiro elemento do agregado.
Figura 6.6 - Funo que determina o elemento do agregado com o pior valor que serve.
O primeiro elemento com valor que no excede 40, a comear no elemento de ndice 7, o
elemento com ndice 7, cujo valor 32. Nas mesmas circunstncias, o elemento com o
melhor valor o elemento com ndice 15, cujo valor 39, e o elemento com o pior valor
o elemento com ndice 11, cujo valor 1. No entanto, se comearmos a pesquisa no
elemento de ndice 17 do agregado, no encontraremos nenhum valor que no exceda 40,
pelo que, as funes, o primeiro que serve, o melhor que serve e o pior que serve devolvem
o ndice 1.
7 CAPTULO 6 : PESQUISA E ORDENAO
Posio
Inicial de Menor Valor Maior
Pesquisa Valor 55 Valor
Figura 6.8 - Funo de pesquisa binria que procura um valor no agregado (verso iterativa).
Vamos mostrar na Figura 6.9, o funcionamento deste algoritmo, para o caso de um
agregado com vinte elementos ordenado por ordem crescente. Pretendemos procurar o
valor 34. Vamos invocar a funo para todos os elementos teis do agregado, ou seja, do
elemento de ndice 0 at ao elemento nelem1, que neste caso o elemento de ndice 19.
Calcula-se o elemento mdio do agregado, atravs da diviso inteira da soma das posies
mnima e mxima, e que neste caso o elemento de ndice 9. Como o valor procurado, que
34, menor do que valor armazenado no elemento mdio, neste caso 44, ento isso
significa que ele se encontra na primeira metade do agregado, pelo que, a posio mxima
passa para o elemento esquerda da posio mdia, ou seja, a nova posio mxima
agora o elemento de ndice 8. Se pelo contrrio, o valor procurado fosse maior do que 44
ento isso significava que ele se encontrava na segunda metade do agregado, pelo que, a
nova posio mnima passaria para o elemento direita da posio mdia, ou seja, a nova
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 8
posio mnima seria o elemento de ndice 10. Agora que estamos a analisar a primeira
metade do agregado, calcula-se a nova posio mdia, que o elemento de ndice 4. Como
o valor procurado maior do que valor armazenado no elemento mdio, que 26, ento
isso significa que ele se encontra na segunda metade do intervalo em anlise, pelo que, a
posio mnima passa para o elemento direita da posio mdia, ou seja, para o elemento
de ndice 5. Agora, a nova posio mdia passa a ser o elemento de ndice 6. Como o valor
procurado ainda maior do que valor armazenado no elemento mdio, que 30, ento isso
significa que ele se encontra na segunda metade do intervalo em anlise, pelo que, a posio
mnima passa para o elemento direita da posio mdia, ou seja, para o elemento de
ndice 7. A nova posio mdia passa agora a ser o elemento de ndice 7, cujo valor o
valor procurado, pelo que, a pesquisa termina com sucesso e devolve o ndice 7. Para
obtermos este resultado, foi preciso analisar quatro elementos do agregado enquanto que a
pesquisa sequencial necessitaria de analisar oito elementos.
MIN MAX
MAX MIN
Se por exemplo, o valor procurado fosse 40, que maior do que 34, ento a nova posio
mnima passaria para o elemento direita da posio mdia, ou seja, para o elemento de
ndice 8, exactamente igual ao valor da posio mxima. Consequentemente, a nova
posio mdia passaria a ser o elemento de ndice 8, cujo valor 42, pelo que, a posio
mxima passaria para o elemento esquerda da posio mdia, ou seja, para o elemento de
ndice 7 e portanto, as posies mnima e mxima trocavam de posio parando o ciclo de
pesquisa. Como o valor armazenado no elemento cujo ndice a posio mdia final, que
o elemento de ndice 8, diferente do valor procurado, ento a pesquisa terminaria sem
sucesso e devolveria o ndice 1 como sinal de pesquisa falhada. Neste caso a funo
analisava cinco elementos do agregado enquanto que a pesquisa sequencial necessitaria de
analisar os vinte elementos do agregado para chegar ao mesmo resultado.
Esta estratgia de pesquisa tambm pode ser implementada de forma recursiva, sendo que
cada nova pesquisa analisa uma das metades do agregado anteriormente analisado at que o
valor seja encontrado ou at no existirem mais elementos para analisar. A verso recursiva
apresentada na Figura 6.10. Compare-a com a verso iterativa que foi apresentada na
Figura 6.8. A funo comea por testar a situao de paragem no caso da pesquisa sem
sucesso, o que acontece quando as posies inicial e final trocam de posio. Nesse caso, a
funo devolve o ndice 1 como sinal de pesquisa falhada. Caso tal no se verifique,
calcula o ndice do elemento central do agregado em anlise. Se o elemento for o valor
procurado, estamos perante a situao de paragem com sucesso e a funo devolve o ndice
do elemento central do agregado. Caso contrrio, em funo da comparao do valor
procurado com o valor do elemento central, a funo invoca-se recursivamente de forma
alternativa para a primeira metade ou para a segunda metade do agregado.
Figura 6.10 - Funo de pesquisa binria que procura um valor no agregado (verso recursiva).
A implementao recursiva no tem qualquer vantagem sobre a implementao repetitiva,
quando aplicada a agregados. No entanto normalmente aplicada para pesquisar estruturas
de dados dinmicas organizadas de forma binria, como o caso das rvores binrias de
pesquisa, permitindo combinar a eficincia da pesquisa binria com a flexibilidade destas
estruturas de dados dinmicas.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10
Para um agregado com N elementos, o pior caso da pesquisa sequencial quando o valor
procurado est no ltimo elemento do agregado ou no existe no agregado, o que exige a
anlise dos N elementos do agregado. Se considerarmos ainda, que a probabilidade do
valor procurado estar em qualquer um dos elementos do agregado igual, ento a pesquisa
sequencial analisa em mdia (N+1)/2 elementos. Se considerarmos que a probabilidade do
valor no existir no agregado igual a ser qualquer um dos elementos do agregado, ento a
pesquisa sequencial analisa em mdia (N+2)/2 elementos. Portanto, a eficincia do
algoritmo de pesquisa sequencial de ordem O(N).
Para o mesmo agregado, o pior caso da pesquisa binria quando se reduz o intervalo em
anlise a apenas um elemento do agregado, o que exige a anlise de log2(N+1) elementos
do agregado. Se considerarmos ainda, que a probabilidade do valor estar em qualquer um
dos elementos do agregado igual, ento a pesquisa binria analisa em mdia log2(N+1) 1
elementos. Se considerarmos que a probabilidade do valor no existir no agregado igual a
ser qualquer um dos elementos do agregado, ento a pesquisa binria analisa em mdia
log2(N+1) elementos. Portanto, a eficincia do algoritmo de pesquisa binria de
ordem O(log2 N).
6.2 Ordenao
A ordenao o processo de organizar um conjunto de objectos segundo uma determinada
ordem. Como j foi dito anteriormente, se a informao estiver ordenada possvel utilizar
algoritmos de pesquisa mais eficientes, como por exemplo a pesquisa binria ou a pesquisa
por tabela. Pelo que, a ordenao uma tarefa muito importante no processamento de
dados e feita para facilitar a pesquisa.
depois I
da
terceira 2 8 15 330 209 42 32 25 55 145
passagem
NC=7 NT=4 J J
depois I
da
quarta 2 8 15 25 330 209 42 32 55 145
passagem
NC=6 NT=4 J J
depois I
da
quinta 2 8 15 25 32 330 209 42 55 145
passagem
NC=5 NT=3 J J
depois I
da
sexta 2 8 15 25 32 42 330 209 55 145
passagem
NC=4 NT=2 J J
depois I
da
stima 2 8 15 25 32 42 55 330 209 145
passagem
NC=3 NT=2 J J
depois I
da
oitava 2 8 15 25 32 42 55 145 330 209
passagem
NC=2 NT=2 J J
depois I
da
nona 2 8 15 25 32 42 55 145 209 330
passagem
NC=1 NT=1 J
TOTAL
NC = 45
NT = 25 2 8 15 25 32 42 55 145 209 330
Para um agregado com N elementos, este algoritmo faz tantas comparaes como o
algoritmo Sequencial. No entanto, como faz no mximo uma troca por passagem, o
nmero de trocas no pior caso pode atingir as N1 trocas. Este algoritmo tem uma
eficincia de comparao de ordem O(N2) e uma eficincia de trocas de ordem O(N).
Apesar de ser mais eficiente no nmero de trocas efectuadas, no entanto, um algoritmo
que pertence classe O(N2).
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
depois I MIN
da
terceira 2 8 15 25 42 209 32 330 55 145
passagem
NC=7 NT=1 J J
depois I
da
quarta 2 8 15 25 42 209 32 330 55 145
passagem
NC=6 NT=0 MIN J J
depois I MIN
da
quinta 2 8 15 25 32 209 42 330 55 145
passagem
NC=5 NT=1 J J
depois I MIN
da
sexta 2 8 15 25 32 42 209 330 55 145
passagem
NC=4 NT=1 J J
depois I MIN
da
stima 2 8 15 25 32 42 55 330 209 145
passagem
NC=3 NT=1 J J
depois I MIN
da
oitava 2 8 15 25 32 42 55 145 209 330
passagem
NC=2 NT=1 J J
depois I
da
nona 2 8 15 25 32 42 55 145 209 330
passagem
NC=1 NT=0 MIN J
TOTAL
NC = 45
NT = 7 2 8 15 25 32 42 55 145 209 330
A Figura 6.16 apresenta o algoritmo de Ordenao Bolha ( Bubble Sort ) para a ordenao
crescente do agregado. Existe a varivel auxiliar indinicial que inicializada com o ndice do
segundo elemento do agregado e que representa o ndice do elemento da parte inicial do
agregado onde termina a comparao de elementos em cada passagem. Para todos os
elementos finais do agregado, desde o ltimo elemento at ao elemento de ndice indinicial,
cada elemento comparado com o elemento atrs dele, ou seja, compara-se o elemento de
ndice indi com o elemento de ndice indi1, e caso o valor seja menor trocam-se os
elementos. Deste modo os elementos de menor valor vo sendo deslocados em direco
parte inicial do agregado. Em cada passagem, contabiliza-se o nmero de trocas efectuadas,
e quando uma passagem no tiver efectuado qualquer troca, isso sinal de que o agregado
j est ordenado. Em cada passagem, pelo menos um novo elemento fica ordenado, pelo
que, a varivel indinicial incrementada de uma unidade, de maneira a evitar fazer-se
comparaes desnecessrias com os elementos que j esto ordenados na parte inicial do
agregado. Portanto, a ordenao tambm acaba quando a varivel indinicial excede o fim
do agregado, ou seja, ao fim de nelem1 passagens.
depois INICIAL
da
terceira 2 8 15 209 330 25 32 42 55 145
passagem
NC=7 NT=4 I I
depois INICIAL
da
quarta 2 8 15 25 209 330 32 42 55 145
passagem
NC=6 NT=2 I I
depois INICIAL
da
quinta 2 8 15 25 32 209 330 42 55 145
passagem
NC=5 NT=2 I I
depois INICIAL
da
sexta 2 8 15 25 32 42 209 330 55 145
passagem
NC=4 NT=2 I I
depois INICIAL
da
stima 2 8 15 25 32 42 55 209 330 145
passagem
NC=3 NT=2 I I
depois INICIAL
da
oitava 2 8 15 25 32 42 55 145 209 330
passagem
NC=2 NT=2 I I
depois INICIAL
da
nona 2 8 15 25 32 42 55 145 209 330
passagem
NC=1 NT=0 I
TOTAL INICIAL
NC = 45
NT = 25 2 8 15 25 32 42 55 145 209 330
Para um agregado com N elementos, este algoritmo faz no melhor caso apenas uma
passagem, fazendo N1 comparaes e faz no pior caso N1 passagens, fazendo (N2N)/2
comparaes. Em mdia faz aproximadamente N2/3 comparaes. O nmero de trocas se
bem que dependente do grau de desordenao dos elementos, no pior caso pode atingir as
(N2N)/2 trocas. Portanto, este algoritmo tem uma eficincia de comparao de ordem
O(N2), uma eficincia de trocas de ordem O(N2), pelo que, pertence classe O(N2).
Como este algoritmo tem a capacidade de aps cada passagem determinar se o agregado
est ou no ordenado, ento indicado para ordenar agregados que estejam parcialmente
desordenados. No entanto, caso o agregado esteja muito desordenado, ele o pior dos
algoritmos de ordenao, uma vez que faz muitas trocas. De maneira a diminuir o nmero
de trocas, Donald L. Shell criou uma variante deste algoritmo, que em vez de comparar
elementos adjacentes, compara elementos distanciados de um incremento que vai sendo
progressivamente diminudo, at que nas ltimas passagens compara elementos adjacentes.
A Figura 6.18 apresenta o algoritmo de Ordenao Concha ( Shell Sort ) para a ordenao
crescente do agregado. Existe a varivel auxiliar incremento que representa a distncia de
comparao e cujo valor inicial metade do comprimento do agregado. Para os ltimos
elementos do agregado, mais concretamente para os nelemincremento ltimos elementos,
compara-se o elemento de ndice indi, com o elemento distanciado incremento elementos,
ou seja com o elemento de ndice indiincremento, e caso o valor seja menor trocam-se os
elementos. Deste modo os elementos de menor valor vo sendo deslocados em direco
parte inicial do agregado. Em cada passagem, contabiliza-se o nmero de trocas efectuadas,
e quando uma passagem no tiver efectuado qualquer troca, isso sinal de que os
elementos que esto separados da distncia de comparao, j esto ordenados e a distncia
de comparao reduzida, sendo dividida ao meio. O algoritmo repetido at que a ltima
distncia de comparao usada seja igual a um. Quando numa passagem com distncia de
comparao unitria, no se efectuar qualquer troca de elementos, ento sinal que o
agregado est ordenado. Ao contrrio dos outros algoritmos de ordenao, at esta
passagem final no h garantia que algum elemento do agregado j esteja ordenado. A srie
de incrementos utilizada a que foi proposta pelo Shell.
depois INCREMENTO=2
da
terceira 2 15 8 32 42 25 55 145 330 209
passagem
NC=8 NT=4 I I
depois INCREMENTO=2
da
quarta 2 15 8 25 42 32 55 145 330 209
passagem
NC=8 NT=1 I I
depois INCREMENTO=2
da
quinta 2 15 8 25 42 32 55 145 330 209
passagem
NC=8 NT=0 I I
depois INCREMENTO=1
da
sexta 2 8 15 25 32 42 55 145 209 330
passagem
NC=9 NT=3 I I
depois INCREMENTO=1
da
stima 2 8 15 25 32 42 55 145 209 330
passagem
NC=9 NT=0 I I
TOTAL
NC = 52
NT = 11 2 8 15 25 32 42 55 145 209 330
Existe uma implementao alternativa deste algoritmo, que se apresenta na Figura 6.20, que
no necessita de fazer esta passagem extra, para determinar a passagem ao incremento
seguinte. Utiliza uma tcnica de insero de elementos em vez de troca de elementos. Pega
no agregado constitudo pelos elementos que esto distncia de comparao e para cada
um desses elementos faz a sua insero na posio correcta, de maneira que este agregado
fique ordenado. Depois passa distncia de comparao seguinte. Quando a distncia de
comparao unitria, ento estamos perante o algoritmo de Insero, que se apresenta na
Figura 6.23. Para ordenar o mesmo agregado, esta verso faz um total de 29 comparaes e
de 55 instrues de atribuio, ou seja, cpias de elementos, que correspondem a
aproximadamente 18 trocas. Se por um lado esta verso mais eficiente nas comparaes,
menos 23 o que d uma eficincia na ordem dos 50%. Por outro lado, ela menos eficiente
nas trocas, com aproximadamente mais 7 trocas para trocar os 11 elementos do agregado
que esto fora do stio, ou seja, existe uma ineficincia na ordem dos 70%. Esta ineficincia
caracterstica da tcnica de insero.
Voltando ao algoritmo de ordenao Bolha, foi dito que em cada passagem pelo agregado,
pelo menos um novo elemento fica ordenado. De facto todos os elementos atrs do
elemento onde foi feita a ltima troca, j esto ordenados, pelo que, podemos colocar a
varivel indinicial, que representa o ndice do elemento da parte inicial do agregado onde
termina a comparao de elementos em cada passagem ascendente, na posio seguinte
posio onde foi feita a ltima troca. Desta forma evitam-se fazer ainda mais comparaes
desnecessrias com os elementos que j esto ordenados na parte inicial do agregado. Se
aplicarmos esta tcnica fazendo tambm passagens descendentes no agregado, alternadas
com as passagens ascendentes, usando uma varivel auxiliar indfinal que representa o ndice
do elemento da parte final do agregado onde termina a comparao de elementos em cada
passagem descendente, podemos assim ordenar o agregado com menos comparaes de
elementos. Estas variveis auxiliares podem ainda ser usadas para determinar quando o
agregado est ordenado, pelo que, no necessrio contar o nmero de trocas efectuadas.
A Figura 6.21 apresenta o algoritmo de Ordenao Crivo ( Shaker Sort ) para a ordenao
crescente do agregado. Existe a varivel auxiliar indinicial que inicializada com o ndice do
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 20
NC=9 NT=6 I I
depois INICIAL FINAL
da
segunda 2 209 25 15 42 8 32 55 145 330
passagem UT
NC=8 NT=7 I I
depois INICIAL FINAL
da
terceira 2 8 209 25 15 42 32 55 145 330
passagem UT
NC=7 NT=4 I I
NC=6 NT=6 I I
NC=5 NT=2 I I
depois INICIAL FINAL
da
sexta 2 8 15 25 32 42 55 145 209 330
passagem UT
NC=4 NT=0 I I
Para um agregado com N elementos, este algoritmo faz no pior caso (N2N)/2
comparaes e deslocamentos, mas, em termos mdios faz aproximadamente N 2/4
comparaes e deslocamentos, pelo que, pertence classe O(N 2). Se o agregado estiver
parcialmente desordenado este o algoritmo de ordenao mais indicado.
Para a anlise dos algoritmos devemos ter em conta que o pior caso na ordenao de um
agregado acontece quando o agregado est invertido em relao ordenao pretendida,
enquanto que o melhor caso acontece quando o agregado est ordenado de acordo com a
ordenao pretendida. Para avaliar o caso mdio deve-se utilizar agregados gerados
aleatoriamente e fazer uma estimativa mdia.
25 CAPTULO 6 : PESQUISA E ORDENAO
Comea-se por apontar para o primeiro elemento dos agregados a fundir, ou seja, para o
ndice 0. O ndice do agregado de sada indk aponta sempre para a primeira posio livre
do agregado, que inicialmente a posio zero. Enquanto existirem elementos nos dois
agregados, compara-se o elemento do agregado seqa, de ndice indi, com o elemento do
agregado seqb, de ndice indj. O menor dos valores copiado para o agregado de sada
seqc, cujo ndice indk previamente incrementado para contabilizar mais um elemento. O
ndice do agregado, cujo elemento copiado para o agregado de sada tambm
incrementado de uma posio, para sinalizar que o elemento j foi ordenado. Quando um
dos agregados de entrada estiver esgotado, copiam-se os restantes elementos do outro
agregado para o agregado de sada. No final da fuso, o indicador de elementos do
agregado de sada nelemc, que passado por referncia, deve conter o nmero de
elementos armazenados no agregado, que igual a nelema mais nelemb, uma vez que
estamos a considerar que no existem elementos repetidos nos agregados de entrada.
I J
antes A B
8 32 42 209 330 2 15 25 55 145
da
ordenao C
comear
K
I J
depois
de copiar
A 8 32 42 209 330 B 2 15 25 55 145
o primeiro
elemento
do C 2
agregado B
K
I J
depois
de copiar
A 8 32 42 209 330 B 2 15 25 55 145
o primeiro
elemento
do C 2 8
agregado A
K
I J
depois
de copiar
A 8 32 42 209 330 B 2 15 25 55 145
mais dois
elementos
do 2 8 15 25
agregado B
K
I J
depois
de copiar
A 8 32 42 209 330 B 2 15 25 55 145
mais dois
elementos
do C 2 8 15 25 32 42
agregado A
K
I J
depois
de esgotar A B
8 32 42 209 330 2 15 25 55 145
os
elementos
C 2 8 15 25 32 42 55 145
do
agregado B
K
depois I J
de copiar
os A 8 32 42 209 330 B 2 15 25 55 145
restantes
elementos C 2 8 15 25 32 42 55 145 209 330
do
agregado A K
void Merge_Sort (int seq[], unsigned int inicio, unsigned int fim)
{
unsigned int medio;
if (inicio < fim) /* condio de paragem */
{
medio = (inicio + fim) / 2; /* partio do agregado */
/* invocao recursiva para ordenar a primeira metade do agregado */
Merge_Sort (seq, inicio, medio);
/* invocao recursiva para ordenar a segunda metade do agregado */
Merge_Sort (seq, medio+1, fim);
/* fuso das duas metades ordenadas do agregado */
Merge_List_Sort (seq, inicio, medio, fim);
}
}
void Merge_List_Sort (int seq[], unsigned int inicio,\
unsigned int medio, unsigned int fim)
{
unsigned int inica = inicio, inicb = medio+1, indi = 0, indc;
/* atribuio de memria para o agregado local */
int *seqtemp = (int *) calloc (fim-inicio+1, sizeof (int));
while (inica <= medio && inicb <= fim)
if (seq[inica] < seq[inicb])
seqtemp[indi++] = seq[inica++]; /* elemento da 1 parte */
else seqtemp[indi++] = seq[inicb++]; /* elemento da 2 parte */
/* copiar os restantes elementos da primeira parte do agregado */
while (inica <= medio) seqtemp[indi++] = seq[inica++];
/* copiar os restantes elementos da segunda parte do agregado */
while (inicb <= fim) seqtemp[indi++] = seq[inicb++];
/* copiar o resultado para o agregado a ordenar */
for (indc = 0, inica = inicio; indc < indi; indc++, inica++)
seq[inica] = seqtemp[indc];
free (seqtemp); /* libertao da memria do agregado local */
}
Para comparar este algoritmo com os algoritmos que ordenam fazendo trocas dos
elementos do agregado a ordenar, temos que ter em considerao, que para ordenar cada
elemento preciso copi-lo de e para o agregado. Cada cpia custa uma instruo de
atribuio, o que equivalente, a um tero das instrues de atribuio que so necessrias
para efectuar a troca de dois elementos do agregado. Ou seja, NT = NA/3.
Quando comparado com os algoritmos, cujos resultados esto coligidos na Tabela 6.1, ele
o melhor algoritmo de ordenao no que diz respeito ao nmero de comparaes. Tem, no
entanto, um nmero elevado de trocas quando comparado com os algoritmos Insero e
Concha (2 verso), que so os algoritmos com o melhor desempenho global de todos os
algoritmos apresentados anteriormente.
i r
209 330 25 15 42 2 32 8 55 145 n e
v c
o u
209 330 2 32
209 330 2 32
f a
u g
TOTAL
NC = 21
NA = 68 2 8 15 25 32 42 55 145 209 330
void Quick_Sort (int seq[], unsigned int inicio, unsigned int fim)
{
unsigned int medio, nelem = fim-inicio+1, indi, indj;
if (nelem <= 1) return; /* o agregado tem no mximo 1 elemento */
if (nelem == 2) /* o agregado s tem 2 elementos */
{
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
return;
}
medio = (inicio + fim) / 2; /* clculo do ndice do pivot */
/* colocar os extremos e o pivot por ordem crescente */
if (seq[inicio] > seq[medio]) Swap (&seq[inicio], &seq[medio]);
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
if (seq[medio] > seq[fim]) Swap (&seq[medio], &seq[fim]);
/* se o agregado s tem 3 elementos, ento j est ordenado */
if (nelem == 3) return;
indi = inicio+1; indj = fim-1;
while (indi < indj)
{
/* procurar elementos na parte esquerda maiores do que pivot */
while (indi < medio)
if (seq[indi] > seq[medio]) break; else indi++;
aps processamento
I R I R
42 55 25 15 8 2 32 209 330
aps processamento
15 2 25 8 32 55 42
I R I R
15 2 25 8 42 55
aps processamento
2 8 25 15
I R I R
2 15 25
2 8 15 25
2 8 15 25 32 42 55
TOTAL
NC = 28
NT = 19 2 8 15 25 32 42 55 145 209 330
Com o objectivo de diminuir o nmero de trocas, existe uma verso optimizada, que se
apresenta na Figura 6.31 e que frequentemente apresentada na literatura. A optimizao
consiste em evitar as trocas que envolvem o pivot e o deslocam da posio inicial. Aps a
escolha do pivot ele escondido na penltima posio do agregado. Os elementos so
analisados e trocados at serem todos comparados com o pivot. Quando a anlise do
agregado terminar, ou seja, quando indi for maior ou igual do que indj, o pivot colocado
no stio, por troca com o elemento que est na posio mais esquerda do agregado que
maior do que ele, ou seja, com o elemento que est na posio indi. Esta verso do
algoritmo faz um total de 30 comparaes e de 17 trocas de elementos. Quando
comparado com a primeira verso temos mais 2 comparaes, mas menos 2 trocas.
void Quick_Sort (int seq[], unsigned int inicio, unsigned int fim)
{
unsigned int medio, nelem = fim-inicio+1, indi, indj;
if (nelem <= 1) return; /* o agregado tem no mximo 1 elemento */
if (nelem == 2) /* o agregado s tem 2 elementos */
{
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
return;
}
medio = (inicio + fim) / 2; /* clculo do ndice do pivot */
/* colocar os extremos e o pivot por ordem crescente */
if (seq[inicio] > seq[medio]) Swap (&seq[inicio], &seq[medio]);
if (seq[inicio] > seq[fim]) Swap (&seq[inicio], &seq[fim]);
if (seq[medio] > seq[fim]) Swap (&seq[medio], &seq[fim]);
/* se o agregado s tem 3 elementos, ento j est ordenado */
if (nelem == 3) return;
/* esconder o pivot na penltima posio do agregado */
Swap (&seq[medio], &seq[fim-1]);
indi = inicio; medio = indj = fim-1;
for ( ; ; )
{
/* procurar elementos na parte esquerda maiores do que pivot */
while (seq[++indi] < seq[medio]) ;
/* procurar elementos na parte direita menores do que pivot */
while (seq[--indj] > seq[medio]) ;
if (indi < indj) Swap (&seq[indi], &seq[indj]); else break;
}
/* recuperar o pivot para a posio mdia do agregado */
medio = indi;
Swap (&seq[medio], &seq[fim-1]);
/* invocao recursiva para a parte esquerda do agregado */
Quick_Sort (seq, inicio, medio-1);
/* invocao recursiva para a parte direita do agregado */
Quick_Sort (seq, medio+1, fim);
}
Vamos utilizar o exemplo concreto que se apresenta na Figura 6.32. Vamos considerar que
se pretende gerir uma base de dados constituda por elementos com a seguinte informao.
O nmero de registo de uma pessoa na base de dados que do tipo inteiro, um nome que
uma cadeia de caracteres e uma data constituda por dia, ms e ano. Vamos considerar
ainda que pretendemos ordenar a base de dados por ordem numrica do nmero de
registo, por ordem alfabtica do nome e por ordem cronolgica da data. Assim sendo,
precisamos de implementar as trs funes de comparao CompNRegisto, CompNome e
CompData.
A partir desta definio podem-se declarar variveis do tipo PtFComp e atribuir-lhe uma
qualquer funo, desde que seja uma funo inteira com dois parmetros de entrada do
tipo TElem. A Figura 6.34 apresenta um exemplo da passagem de um parmetro de
entrada deste tipo para uma funo de ordenao. A Figura 6.35 apresenta a utilizao de
uma varivel deste tipo para ser colocada a apontar para diferentes funes ao longo da
execuo do programa, de forma a parametrizar a ordenao de um agregado. Tal como
nos agregados, o nome de uma funo tambm um ponteiro para a funo, pelo que, a
instruo de atribuio fcomp = CompNome; coloca o ponteiro para funo fcomp a
apontar para a funo CompNome.
A funo de troca de elementos do agregado que foi apresentada na Figura 6.11, tem de ser
alterada de maneira a poder trocar dois elementos do tipo TElem. A Figura 6.33 apresenta
a nova verso que vamos designar por SwapElementos.
Figura 6.33 - Funo para trocar dois elementos de um agregado de elementos do tipo TElem.
A Figura 6.34 apresenta a verso generalizada do algoritmo de ordenao Sequencial.
Escolhemos este algoritmo, apenas porque o mais simples em termos de cdigo. O
primeiro parmetro da funo o parmetro de entrada-sada que representa o agregado a
ordenar, o segundo parmetro o parmetro de entrada que representa o nmero de
elementos do agregado, o terceiro parmetro o parmetro de entrada que representa a
funo de comparao e o quarto parmetro o parmetro de entrada que representa o
tipo de ordenao a efectuar. Estes dois ltimos parmetros de entrada configuram o
algoritmo de ordenao, tornando-o assim genrico e reutilizvel.
#include <stdio.h>
#include <stdlib.h>
#include "elemento.h" /* caracterizao do tipo elemento */
#define CRESCENTE 1
#define DECRESCENTE -1
void SwapElementos (TElem *, TElem *);
void Sequential_Sort (TElem [], unsigned int, PtFComp, int);
void Display (TElem [], unsigned int);
funes tm o parmetro de entrada modo de tipo inteiro, que indica o modo de actuao
sobre a varivel contadora.
Figura 6.36 - Funo para trocar elementos do agregado com contabilizao de atribuies.
Para calcular o nmero de comparaes utiliza-se uma varivel de durao permanente em
conjuno com a funo de comparao pretendida. Assim encapsula-se a funo de
comparao dentro de uma nova funo CCount, tal como se mostra na Figura 6.37, que
alm de efectuar a comparao pretendida, usando para o efeito o ponteiro para a funo
de comparao, tambm contabiliza o nmero de vezes que invocada. Os elementos a
comparar so passados funo por referncia, ou seja, atravs de ponteiros. No modo
NORM a funo compara os dois elementos e incrementa a varivel contadora uma
unidade, para contabilizar mais uma comparao.
int CCount (TElem *x, TElem *y, PtFComp fcomp, int modo)
{
static unsigned int cont; /* varivel contadora */
if (modo == REP) return cont;
else if (modo == INIC)
{
cont = 0; return 0;
}
else if (modo == NORM)
{
cont++; /* contagem de 1 instruo de comparao */
return fcomp (*x, *y); /* efectuar a comparao */
}
}
Figura 6.37 - Funo para comparar elementos do agregado com contabilizao de comparaes.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 38
6.7 Exerccios
1. Pretende-se escrever uma funo de ordenao Fuso de Listas que contemple a
situao da existncia de elementos repetidos nos agregados de entrada, e que nessa
situao copie apenas um dos elementos repetidos para o agregado de sada.
Sumrio
7.1 Introduo
As filas e as pilhas so estruturas de dados lineares que tm implementaes em tudo
semelhantes, sendo que diferem apenas na prioridade da retirada de informao. Enquanto
que uma fila implementa a poltica do primeiro a chegar primeiro a sair ( first in first out ), a
pilha implementa a poltica do ltimo a chegar primeiro a sair ( last in first out ).
As filas so muito usadas nos sistemas operativos para funcionarem como reas de
armazenamento de informao que deve ser processada por ordem de chegada, como por
exemplo, o atendimento de pedidos de utilizao de uma impressora de rede que recebe
ficheiros para imprimir, enviados por diferentes computadores instalados na rede. Para
sincronizar a interaco entre processos concorrentes que executam a diferentes
velocidades de processamento. So tambm utilizadas para simular modelos que descrevem
o comportamento de situaes reais de atendimento de pedidos que so processados por
ordem de chegada, como por exemplo, simular o atendimento de uma fila de espera num
qualquer servio do dia a dia.
As pilhas so usadas para gerir algoritmos em que existem processos que invocam
subprocessos do mesmo tipo, como o caso dos algoritmos recursivos. Para processar
estruturas imbricadas, ou seja, estruturas que contm outras estruturas do mesmo tipo
dentro delas, como por exemplo, expresses aritmticas que so compostas por
subexpresses aritmticas do mesmo tipo. Assim uma pilha pode ser usada para verificar o
balanceamento dos parnteses numa expresso aritmtica, que pode conter vrios nveis de
parnteses, que podem ser de tipos diferentes como parnteses curvos, rectos e chavetas.
Podem ser usadas para fazer a avaliao de expresses, que contm expresses internas que
tm de ser avaliadas previamente, antes do clculo da expresso final. Por exemplo, para
calcular uma expresso em notao polaca, que utiliza os smbolos das operaes depois
dos operadores. Para fazer a anlise sintctica durante a compilao de um programa, onde
existem ciclos repetitivos dentro de ciclos repetitivos, blocos dentro de blocos e portanto,
existem estruturas imbricadas que tm de estar balanceadas e correctamente imbricadas.
Este ficheiro de interface, que se apresenta na Figura 7.1, define a constante que
parametriza a dimenso da estrutura de dados de suporte, necessrio apenas no caso das
implementaes esttica e semiesttica, bem como o tipo de dados do elemento
constituinte da memria. No caso da implementao dinmica, este ficheiro precisa apenas
de definir o tipo de dados do elemento constituinte da memria. Assim o utilizador do
mdulo, pode concretiz-lo para uma estrutura de dados que corresponda s suas
necessidades, sem ter a necessidade de reprogramar o ficheiro de implementao do
mdulo. Em relao criao de um mdulo abstracto, esta soluo exige a recompilao
do mdulo, sempre que este ficheiro modificado.
3 CAPTULO 7 : FILAS E PILHAS
7.2 Filas
Uma memria fila ( queue/FIFO ) uma memria em que s possvel processar a
informao pela ordem de chegada, da que, tambm seja apelidada de memria do
primeiro a chegar primeiro a sair. Numa memria fila, o posicionamento para a colocao
de um novo elemento na fila, que vamos designar por Fifo_In, a cauda da fila (fifo tail ),
e o posicionamento para a remoo de um elemento da fila, que vamos designar por
Fifo_Out, a cabea da fila (fifo head ).
Como um agregado tem uma dimenso fixa, antes de se colocar um elemento na fila
necessrio verificar se ela est cheia. Em caso afirmativo, mais nenhum elemento pode ser
colocado na fila e assinalada a situao de erro, usando o cdigo de erro FIFO_FULL. O
elemento que se pretende copiar para a fila passado funo Fifo_In por valor. Por
outro lado, o elemento que vai receber a cpia do elemento que se pretende retirar da fila
passado funo Fifo_Out por referncia. Pode acontecer que o ponteiro passado
funo seja um ponteiro nulo. Nestas circunstncias a funo no pode retirar o elemento
da fila, pelo que, no faz nada e assinala esta anomalia, usando o cdigo de erro
NULL_PTR. Antes de se retirar um elemento da fila preciso detectar se ela est vazia.
Em caso afirmativo, nenhum elemento pode ser retirado da fila e assinalada a situao de
erro, usando o cdigo de erro FIFO_EMPTY. Sempre que colocado ou retirado um
elemento da fila devolvido o cdigo OK sinalizando que a operao foi realizada com
sucesso.
cauda cabea
estado inicial
da fila da fila
cabea cauda
colocao do primeiro elemento
da fila da fila
Sempre que se coloca um elemento na fila, a cauda da fila deslocada para o elemento
seguinte da fila. Existem duas situaes distintas. Na primeira situao, que se apresenta na
Figura 7.4, a posio seguinte da fila est ainda dentro da dimenso do agregado, pelo que,
a cauda continua atrs da cabea.
cabea cauda
colocao de um elemento
da fila da fila
cabea cauda
da fila da fila
cabea cauda
colocao de um elemento
da fila da fila
cauda cabea
da fila da fila
A Figura 7.6 apresenta a situao de colocao do ltimo elemento na fila. Nesta situao a
cauda da fila fica a apontar para o mesmo elemento que a cabea da fila, o que significa que
a fila ficou cheia. Enquanto este estado durar, no possvel colocar mais elementos na fila.
cauda cabea
colocao do ltimo elemento
da fila da fila
Na primeira situao, que se apresenta na Figura 7.7, a posio seguinte da fila est ainda
dentro da dimenso do agregado, pelo que, a cabea ainda no deu a volta ao agregado.
Neste caso ainda est atrs da cauda, porque a cauda j excedeu o fim do agregado.
cauda cabea
remoo de um elemento
da fila da fila
cauda cabea
da fila da fila
Na segunda situao, que se apresenta na Figura 7.8, a posio seguinte da fila est fora da
dimenso do agregado, pelo que, a cabea passa para o incio do agregado e neste caso fica
de novo frente da cauda. Para conseguir este efeito circular da cabea da fila utiliza-se o
operador mdulo, para calcular sempre um valor que est entre 0 e N_ELEMENTOS1.
cauda cabea
remoo de um elemento
da fila da fila
cabea cauda
da fila da fila
cabea cauda
remoo do ltimo elemento
da fila da fila
No caso da colocao do primeiro elemento na fila, ele no precisa de ser ligado fila, mas,
em contrapartida a cabea da fila que estava a apontar para NULL colocada a apontar
para o n deste elemento. Ele simultaneamente o primeiro e ltimo elemento da fila.
11 CAPTULO 7 : FILAS E PILHAS
cabea cauda
da fila da fila estado inicial
cabea
da fila PtSeg
PtEle
cauda
da fila colocao
Elemento do primeiro
1 elemento
cabea
da fila PtSeg PtSeg
PtEle PtEle
Elemento Elemento
1 2 colocao
cauda de um
da fila elemento
cabea
da fila PtSeg PtSeg PtSeg
PtEle PtEle PtEle
A Figura 7.17 mostra a remoo do ltimo elemento da fila. Como este ltimo n aponta
para NULL, ao ser atribudo o valor NULL cabea da fila isso sinal de que a fila ficou
vazia, pelo que, a cauda da fila tambm colocada a apontar para NULL. Aps esta
operao, a fila fica num estado igual ao estado inicial.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
cabea
da fila PtSeg PtSeg PtSeg
PtEle PtEle PtEle
cabea
da fila PtSeg PtSeg
PtEle PtEle
Elemento Elemento
remoo 2 3
de um cauda
elemento da fila
cabea
da fila PtSeg
PtEle
cauda
da fila remoo
Elemento do ltimo
3 elemento
7.3 Pilhas
Uma memria pilha ( stack/LIFO ) uma memria em que s possvel processar a
informao pela ordem inversa ordem de chegada. Da que, tambm seja apelidada de
memria do ltimo a chegar primeiro a sair. Numa memria pilha, o posicionamento para
a colocao de um elemento na pilha, que vamos designar por Stack_Push, e o
posicionamento para a remoo de um elemento da pilha, que vamos designar por
Stack_Pop, o topo da pilha (top of the stack).
Como um agregado tem uma dimenso fixa, antes de se colocar um elemento na pilha
necessrio verificar se a pilha est cheia. Em caso afirmativo, mais nenhum elemento pode
ser colocado na pilha e assinalada a situao de erro, usando o cdigo de erro
STACK_FULL. O elemento que se pretende copiar para a pilha passado funo
Stack_Push por valor. Por outro lado, o elemento que vai receber a cpia do elemento
que se pretende retirar da pilha passado funo Stack_Pop por referncia. Pode
acontecer que o ponteiro passado funo seja um ponteiro nulo. Nestas circunstncias a
funo no pode retirar o elemento da pilha, pelo que, no faz nada e assinala esta
anomalia, usando o cdigo de erro NULL_PTR. Antes de se retirar um elemento da pilha
preciso detectar se a pilha est vazia. Em caso afirmativo, nenhum elemento pode ser
retirado da pilha e assinalada a situao de erro, usando o cdigo de erro
STACK_EMPTY. Sempre que colocado ou retirado um elemento da pilha devolvido o
cdigo OK sinalizando que a operao foi realizada com sucesso.
A Figura 7.20 mostra o estado inicial da pilha. Por uma questo de implementao, vamos
considerar que o topo da pilha indica sempre a primeira posio livre para a prxima
operao de colocao de um elemento na pilha, pelo que, inicialmente aponta para a
posio 0 do agregado. A Figura 7.20 mostra tambm a colocao do primeiro elemento na
pilha e o estado da pilha aps a operao. O topo da pilha deslocado para a posio 1 do
agregado, que a primeira posio livre para colocar o prximo elemento.
. Elemento . Elemento
. .
. .
Elemento Elemento
. Elemento . Elemento
. .
. .
Elemento Elemento
Sempre que um elemento retirado da pilha, primeiro preciso deslocar o topo da pilha
para a posio anterior da pilha, onde est armazenado o ltimo elemento da pilha. S
depois que o elemento copiado para fora da pilha.
A Figura 7.22 apresenta a situao de remoo de um elemento, quando existe mais do que
um elemento na pilha. O topo da pilha deslocado para a posio anterior e a informao
armazenada no elemento retirado da pilha.
remoo de um elemento
topo da
. Elemento . Elemento
pilha
. .
. . topo da
Elemento Elemento
pilha
. Elemento . Elemento
. .
. .
Elemento Elemento
PILHA
topo da STACK[1]
STACK[1] Elemento Elemento VAZIA
pilha
STACK[0] topo da
STACK[0] Elemento Elemento
pilha
No caso da colocao do primeiro elemento na pilha, ele no precisa de ser ligado pilha,
mas, em contrapartida colocado a apontar para NULL, indicando que ele o primeiro
elemento da pilha. Ele simultaneamente o primeiro e o ltimo elemento da pilha.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 20
topo da topo da
Elemento
pilha pilha PtEle
1
PtAnt
Figura 7.28 - Situao inicial da pilha e aps a colocao do primeiro elemento na pilha.
A Figura 7.29 apresenta a colocao de mais um elemento na pilha. O n do novo
elemento posto a apontar para o antigo topo da pilha, atravs do ponteiro PtAnt, e
depois passa a ser o novo topo da pilha. Ou seja, o topo da pilha actualizado, ficando a
apontar para este novo n.
topo da
Elemento Elemento
pilha PtEle PtEle
1 1
PtAnt PtAnt
remoo de um elemento
topo da
Elemento
pilha PtEle
2
PtAnt
topo da
Elemento Elemento
PtEle pilha PtEle
1 1
PtAnt PtAnt
A Figura 7.31 mostra a remoo do ltimo elemento da pilha. Como este ltimo n aponta
para NULL, ao ser atribudo o valor NULL ao topo da pilha isso sinal de que a pilha
ficou vazia, e a pilha fica num estado igual ao estado inicial.
curvos. Por vezes para melhor identificar os vrios nveis de subexpresses, utilizam-se
parnteses curvos, rectos e chavetas, tal se mostra na seguinte expresso.
{ A + B * [ C / (F+A) + (C+D) * (EF) ] } / [ (A+B) * C ]
Para que uma expresso aritmtica possa ser correctamente calculada necessrio assegurar
o correcto balanceamento dos parnteses, como o caso da expresso anterior. Uma
expresso com um balanceamento correcto de parnteses, tem tantos parnteses a abrir, ou
seja, parnteses esquerdos, como parnteses a fechar, ou seja, parnteses direitos. Mas
tambm preciso assegurar que um nvel interno de parnteses fechado antes que um
nvel externo e com um parntese direito equivalente ao parntese esquerdo. Pelo que, a
anlise no pode apenas limitar-se a contar o nmero de parnteses direitos e esquerdos de
cada tipo. Por exemplo, a expresso seguinte tem o mesmo nmero de parnteses direitos e
esquerdos e no est balanceada, porque a chaveta fechada antes do parntese recto.
{ A + B * [ C / (F+A) + (C+D) * (EF) } ] / [ (A+B) * C ]
Uma vez que necessrio assegurar que cada parntese direito equivalente ao ltimo
parntese esquerdo, esta anlise pode ser feita usando uma pilha O algoritmo o seguinte.
Sempre que aparece um parntese esquerdo na expresso, este colocado na pilha.
Quando aparece um parntese direito, tira-se da pilha o ltimo parntese esquerdo l
colocado e verifica-se se so equivalentes. Caso o teste seja positivo ento este nvel interno
de parnteses est balanceado e ambos os parnteses so descartados. Caso os parnteses
no sejam equivalentes, ento o processo interrompido porque este nvel interno de
parnteses no est balanceado. Se por acaso no existir um parntese esquerdo na pilha,
ento sinal que existem mais parnteses direitos que esquerdos at esta posio da
expresso, pelo que, a expresso tambm no est balanceada. Quando a anlise da
expresso terminar preciso verificar se a pilha est vazia. Porque, caso no esteja vazia,
ento sinal que existem mais parnteses esquerdos que direitos na expresso, pelo que, a
expresso tambm no est balanceada.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pilha_est.h" /* ficheiro de interface do mdulo da pilha */
int parenteses_equivalentes (char, char);
Figura 7.34 - Programa que verifica o balanceamento dos parnteses numa expresso.
25 CAPTULO 7 : FILAS E PILHAS
A Figura 7.35 apresenta o programa que implementa este algoritmo. A pilha e a fila
utilizadas so a implementao esttica. Para utilizar a fila e a pilha, primeiro preciso
editar o ficheiro de interface elemento.h, e definir o nmero de elementos das estruturas
de dados da fila e da pilha, com um valor suficiente para armazenar o nmero de caracteres
da palavra e definir tambm o tipo dos elementos da fila e da pilha como sendo do tipo
char. Depois os mdulos so compilados, com a opo c, para ficarem concretizados
para uma fila e uma pilha de caracteres. Finalmente, o programa compilado, indicando no
comando de compilao os ficheiros objectos dos mdulos fila_est.o e pilha_est.o.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fila_est.h" /* ficheiro de interface do mdulo da fila */
#include "pilha_est.h" /* ficheiro de interface do mdulo da pilha */
A Figura 7.36 apresenta o ficheiro de interface da fila dinmica, abstracta e com capacidade
de mltipla instanciao, cujo ficheiro de implementao se apresenta na Figura 7.37 e na
Figura 7.38.
Para que o mdulo possa criar e manipular mais do que uma fila, necessrio que exista
uma referncia para cada fila criada de forma a identificar de forma inequvoca a fila onde
se pretende colocar ou retirar elementos. Para isso preciso uma estrutura de suporte que
possa armazenar os elementos que controlam a fila e que so os ponteiros para a cabea e
para a cauda da fila e um indicador do tamanho em nmero de bytes do elemento da fila.
Esta estrutura vai ser criada na memria na altura em que pedido a criao de uma fila e o
seu endereo devolvido de forma a permitir o posterior acesso fila criada, quer para a
colocao e remoo de elementos, quer para a destruio da fila, quando ela deixa de ser
necessria. Pelo que, o ficheiro de interface define o tipo PtFifo, que um ponteiro para
struct fifo, que permite ao utilizador do mdulo manipular as filas criadas, sem no entanto
ter acesso estrutura de dados da fila.
Por sua vez, a funo de destruio da fila Fifo_Destroy, liberta toda a memria ocupada
pelos elementos da fila e pela estrutura de suporte da fila e coloca a referncia da fila a
NULL, de maneira a no ser mais possvel aceder fila.
27 CAPTULO 7 : FILAS E PILHAS
A funo de colocao de elementos na fila Fifo_In comea por assegurar que a fila existe
e depois comporta-se tal como na implementao dinmica. Mas, a cpia do elemento para
a fila feita pela funo memcpy indicando o ponteiro para o elemento a colocar na fila, o
ponteiro para a cauda da fila e o nmero de bytes a copiar, que est armazenado no campo
que especifica o tamanho dos elementos da fila. Desta maneira possvel manipular os
elementos da fila sem que se saiba de que tipo eles so.
A funo de remoo de elementos da fila Fifo_Out comea por assegurar que a fila existe
e depois comporta-se tal como na implementao dinmica. Tal como na funo Fifo_In,
a cpia do elemento da fila feita pela funo memcpy.
Figura 7.37 - Ficheiro de implementao da fila abstracta com mltipla instanciao (1 parte).
29 CAPTULO 7 : FILAS E PILHAS
Figura 7.38 - Ficheiro de implementao da fila abstracta com mltipla instanciao (2 parte).
A Figura 7.39 apresenta o ficheiro de interface da pilha dinmica, abstracta e com
capacidade de mltipla instanciao, cujo ficheiro de implementao se apresentam na
Figura 7.40 e na Figura 7.41.
Para que o mdulo possa criar e manipular mais do que uma pila, necessrio que exista
uma referncia para cada pilha criada de forma a identificar de forma inequvoca a pilha
onde se pretende colocar ou retirar elementos. Para isso preciso uma estrutura de suporte
que possa armazenar os elementos que controlam a pilha e que so o ponteiro para o topo
da pilha e um indicador do tamanho em nmero de bytes do elemento da pilha. Esta
estrutura vai ser criada na memria na altura em que pedido a criao de uma pilha e o
seu endereo devolvido de forma a permitir o posterior acesso pilha criada, quer para a
colocao e remoo de elementos, quer para a destruio da pilha, quando ela deixa de ser
necessria. Pelo que, o ficheiro de interface define o tipo PtStack, que um ponteiro para
struct stack, que permite ao utilizador do mdulo manipular as filas criadas, sem no
entanto ter acesso estrutura de suporte da fila.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 30
Por sua vez, a funo de destruio da pilha Stack_Destroy, liberta toda a memria
ocupada pelos elementos da pilha e pela estrutura de suporte da pilha e coloca a referncia
da pilha a NULL, de maneira a no ser mais possvel aceder pilha.
A funo de remoo de elementos da pilha Stack_Pop comea por assegurar que a pilha
existe e depois comporta-se tal como na implementao dinmica. Tal como na funo
Stack_Push, a cpia do elemento da pilha feita pela funo memcpy.
Figura 7.40 - Ficheiro de implementao da pilha abstracta com mltipla instanciao (1 parte).
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 32
Figura 7.41 - Ficheiro de implementao da pilha abstracta com mltipla instanciao (2 parte).
A Figura 7.42 apresenta a utilizao da pilha abstracta para a resoluo do problema da
dupla inverso de uma linha de texto. um exemplo meramente acadmico, uma vez que
para inverter uma cadeia de caracteres no necessrio uma pilha, mas serve para mostrar a
utilizao de duas pilhas em simultneo no mesmo programa. Os caracteres so colocados
numa pilha e depois ao serem retirados so escritos no monitor por ordem inversa. Mas, se
forem de novo colocados noutra pilha, depois ao serem retirados da segunda pilha so
escritos no monitor pela ordem inicial. Temos assim uma dupla inverso.
Para podermos usar duas pilhas, primeiro preciso declarar duas variveis de tipo PtStack,
que vo apontar para as pilhas criadas atravs da invocao da funo Stack_Create. Na
invocao desta funo indicado que os elementos da pilha devem ter o tamanho de um
char, pelo que estamos a criar pilhas para caracteres. Para colocar e retirar caracteres numa
pilha obrigatrio passar s funes Stack_Push e Stack_Pop, como parmetro de
entrada, a pilha que se est a processar usando o respectivo ponteiro. Assim que as pilhas
no so mais precisas, elas devem ser destrudas invocando a funo Stack_Destroy para
cada uma das pilhas. Para simplificar o programa, o resultado de sada das operaes de
colocao e remoo de caracteres nas pilhas no testado para conferir se so bem
sucedidas ou no.
33 CAPTULO 7 : FILAS E PILHAS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pilha_abs.h" /* ficheiro de interface do mdulo da pilha */
int main (void)
{
char exp[81], cpilha;
int nc, c, st;
PtStack pilha1 = NULL, pilha2 = NULL;
pilha1 = Stack_Create (sizeof (char)); /* Criao da pilha 1 */
pilha2 = Stack_Create (sizeof (char)); /* Criao da pilha 2 */
printf ("Texto de entrada -> "); scanf ("%80s", exp);
nc = strlen (exp);
for (c = 0; c < nc; c++)
st = Stack_Push (pilha1, &exp[c]); /* Colocar texto na pilha 1 */
printf ("Texto de sada depois de colocado na pilha 1 -> ");
for (c = 0; c < nc; c++)
{
st = Stack_Pop (pilha1, &cpilha); /* Retirar texto da pilha 1 */
printf ("%c", cpilha);
st = Stack_Push (pilha2, &cpilha); /* Colocar texto na pilha 2 */
}
printf ("\n");
printf ("Texto de sada depois de colocado na pilha 2 -> ");
for (c = 0; c < nc; c++)
{
st = Stack_Pop (pilha2, &cpilha); /* Retirar texto da pilha 2 */
printf ("%c", cpilha);
}
printf ("\n");
Stack_Destroy (&pilha1); /* Destruio da pilha 1 */
Stack_Destroy (&pilha2); /* Destruio da pilha 2 */
return EXIT_SUCCESS;
}