Escolar Documentos
Profissional Documentos
Cultura Documentos
Programação, Estruturas de Dados e Algoritmos em C - Adrego Da Rocha
Programação, Estruturas de Dados e Algoritmos em C - Adrego Da Rocha
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.
Comeamos por apresentar os aspectos essenciais da linguagem C em dois captulos. Depois
introduzirmos as construes mais complexas da linguagem de forma gradual, medida que so
necessrias construo de estruturas de dados mais complexas, bem como para a optimizao e
generalizao de algoritmos. Os aspectos fundamentais apresentados no texto so os seguintes:
x A familiarizao progressiva com a linguagem de programao C e com as suas bibliotecas.
x A apresentao de algoritmos recursivos e sua comparao com os algoritmos iterativos
equivalentes.
x A introduo da metodologia de decomposio modular das solues, ou seja, o paradigma da
programao modular.
x O estudo da organizao da Memria de Acesso Aleatrio (RAM), nas suas implementaes
esttica e semiesttica, e, de um conjunto significativo de algoritmos de pesquisa e de ordenao.
x O estudo da organizao de memrias mais complexas que a Memria de Acesso Aleatrio, como
por exemplo, a Memria Fila de Espera (FIFO), a Memoria Pilha (Stack) e a Memria Associativa
(CAM), nas suas implementaes esttica, semiesttica e dinmica, e, dos algoritmos associados
para pesquisa, introduo e retirada de informao.
Assume-se que os alunos frequentaram a disciplina de Programao I, e portanto, j esto
familiarizados com a metodologia de decomposio hierrquica das solues, estabelecendo
dependncias de informao e no encapsulamento da informao com a criao de novas instrues no
mbito da linguagem Pascal, ou seja, com o paradigma da programao procedimental. Bem como,
com a criao de estruturas de dados estticas com alguma complexidade que modelam correctamente a
resoluo dos problemas. Pelo que, a apresentao da linguagem C feita por comparao com a
linguagem Pascal.
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.
A principal caracterstica da linguagem C que combina as vantagens de uma linguagem de
alto nvel descendente do AlGOL 68, com a eficincia da linguagem assembly, uma vez que
permite a execuo de operaes aritmticas sobre ponteiros e operaes sobre palavras
binrias. A linguagem C tambm tem uma sintaxe muito compacta e permite que
operadores de tipos diferentes possam ser combinados livremente.
Esta liberdade e poder da linguagem C, permite aos programadores experientes escreverem
cdigo compacto e eficiente que dificilmente poderiam ser escritos noutras linguagens de
programao. Mas, como fracamente estruturada em termos semnticos, tambm permite
que construes sem sentido aparente, escritas por programadores inexperientes, sejam
aceites pelo compilador como vlidas. O facto da linguagem C ser muito poderosa, exige
portanto, do programador muita disciplina e rigor na utilizao das construes da
linguagem, para que o cdigo escrito seja legvel e facilmente altervel.
Apesar da linguagem C ter sido desenvolvida no princpio da dcada de 1970, a norma
ANSI (American National Standards Institute) foi apenas aprovada em 1989 (norma
ISO/IEC 9899-1990).
1.2 A estrutura de um programa em C
Ao contrrio do que se passa em Pascal, em que um programa apresenta uma organizao
hierrquica que reflecte directamente o algoritmo que lhe deu origem, na linguagem C, um
programa organizado horizontalmente como um agrupamento de variveis e funes
colocadas todas ao mesmo nvel, uma estrutura conhecida pelo nome de mar de funes.
Neste contexto, a diferenciao entre o programa principal e os diversos subprogramas
associados feita pelo facto de existir uma funo particular, de nome main, que sempre
invocada em primeiro lugar aquando da execuo do programa. Assim, a funo main
desempenha na prtica o papel do programa principal do Pascal. Segundo a norma ANSI, a
funo main uma funo de tipo inteiro, em que o valor devolvido serve para informar
sobre o estado de execuo do programa.
3 CAPTULO 1 : INTRODUO AO C
De facto, a noo de devolver um valor associado ao estado de execuo de um programa
corresponde filosofia subjacente arquitectura de comandos do Unix, em que a
linguagem de comandos (shell) , no fundo, uma verdadeira linguagem de programao, que
permite construir comandos mais complexos por combinao de comandos mais simples
(shell scripts), usando um conjunto de estruturas de controlo muito semelhantes aos
encontrados na linguagem C ou Pascal. Neste contexto, os comandos mais simples so
programas, de cujo sucesso de execuo vai eventualmente depender a continuao das
operaes.
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.
Figura 1.1 - Estrutura de um programa em C.
Vamos analisar as diversas partes de um programa em C atravs do exemplo do programa
de converso de distncias de milhas para quilmetros apresentado na Figura 1.2. O
ficheiro fonte que contm o programa comea por mencionar as funes e estruturas de
dados externas necessrias execuo do programa, que esto implementadas noutros
ficheiros providenciados pela linguagem C, as chamadas bibliotecas da linguagem, ou que
em alternativa so desenvolvidos pelo utilizador. A linguagem C foi concebida tendo em
mente facilitar a construo descentralizada de aplicaes, atravs do fraccionamento do
cdigo de um programa por diferentes ficheiros fonte. Por isso, necessrio e inevitvel,
mesmo em programas muito simples, recorrer aluso a funes e definies feitas
externamente, ou seja, noutros ficheiros. Para tornar mais rigorosa esta referncia, foi
criado o conceito de ficheiro de interface, onde todas as aluses e definies associadas a
um dado tipo de funcionalidade so colocadas. Estes ficheiros de interface distinguem-se
dos ficheiros fonte, propriamente ditos, por terem a terminao .h em vez de .c.
A norma ANSI fornece um conjunto muito variado de ficheiros de interface que
descrevem as diferentes funcionalidades fornecidas pelas bibliotecas de execuo ANSI.
Em Unix, por defeito, todos eles esto armazenados no directrio /usr/include. Da, no ser
necessrio referenciar este caminho de um modo explcito. Quando o ficheiro em causa
est neste directrio, basta colocar o seu nome entre os smbolos < e >. Em todos os
outros casos, a especificao do caminho deve ser includa no nome do ficheiro e o
conjunto ser colocado entre aspas duplas.
aluso a funes e definies externas
aluso a funes e definies locais
int main ( void )
{
declarao de variveis
aluso a funes
sequncia de instrues
return 0;
}
definio de funes locais
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 4
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.
Figura 1.2 - Programa da converso de distncias.
Vamos agora analisar com detalhe na Figura 1.3 a definio da funo main, que tal como
qualquer outra funo na linguagem C, supe a especificao do seu cabealho e do seu
corpo. No cabealho, indica-se o tipo do valor devolvido, que como j foi referido
anteriormente sempre do tipo inteiro, o nome, e entre parnteses curvos, a lista de
/* Programa de converso de distncias de milhas para quilmetros */
/* Instrues para o pr-processador */
#include <stdio.h> /* interface com a biblioteca de entrada/sada */
#define MIL_QUI 1.609 /* factor de converso */
/* Instrues em linguagem C propriamente ditas */
int main ( void )
{
double MILHAS, /* distncia expressa em milhas */
QUILOMETROS; /* distncia expressa em quilmetros */
do /* Leitura com validao de uma distncia expressa em milhas */
{
printf ("Distncia em milhas? ");
scanf ("%lf", &MILHAS);
} while (MILHAS < 0.0);
QUILOMETROS = MIL_QUI * MILHAS; /* Converso da distncia */
/* Impresso da distncia expressa em quilmetros */
printf ("Distncia em quilmetros %8.3f\n", QUILOMETROS);
return 0;
}
5 CAPTULO 1 : INTRODUO AO C
parmetros de comunicao. Neste caso a funo no comunica directamente com o
exterior, pelo que, no existe lista de parmetros de comunicao. Quando tal acontece,
utiliza-se o identificador void. O corpo da 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. Constitui aquilo que em linguagem C se designa
por um bloco.
int main ( void )
{
double MILHAS,
QUILOMETROS;
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;
}
cabealho
corpo
sequncia de instrues
declarao de variveis locais
Figura 1.3 - A funo main.
1.3 Elementos bsicos da linguagem C
Os identificadores so nomes que so usados para designar os diferentes objectos
existentes no programa, como por exemplo, o nome do programa, os nomes das funes,
os nomes das constantes, tipos de dados e variveis. Os identificadores obedecem regra
de produo apresentada na Figura 1.4.
Figura 1.4 - Definio formal de um identificador.
Ou seja, so formados por uma sequncia de caracteres alfanumricos e o carcter
underscore, em que o primeiro carcter obrigatoriamente uma letra do alfabeto ou o
carcter underscore. Embora seja possvel comear um identificador pelo carcter underscore,
tal deve ser evitado. Este tipo de notao , em princpio, reservado para o compilador.
No h limite para o comprimento de um identificador. Na prtica, esse limite imposto
pelo compilador. A norma ANSI exige um comprimento mnimo de 31 e 6 caracteres,
respectivamente, para os identificadores internos e externos.
identificador ::= letra do alfabeto | carcter underscore |
identificador letra do alfabeto |
identificador carcter underscore |
identificador algarismo decimal
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6
Na linguagem C, os alfabetos maisculo e minsculo so distintos, ou seja, a linguagem
sensvel ao tipo de letra (case sensitive). Assim sendo, o identificador conv_dist diferente do
identificador CONV_DIST. Embora no seja obrigatrio, costume designar todos os
identificadores da responsabilidade do programador com caracteres maisculos de maneira
a distingui-los dos identificadores da linguagem C, que tm de ser escritos obrigatoriamente
com caracteres minsculos. Em alternativa, h programadores que gostam de usar um
carcter maisculo no primeiro carcter de cada palavra que compe o identificador e os
restantes caracteres minsculos. Nesse caso, devem designar pelo menos os nomes das
constantes em caracteres maisculos. Mas, o importante que cada programador defina o
seu prprio estilo, e que exista uma certa coerncia nas regras que adopte.
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.
Para melhorar a legibilidade do programa, devem ser introduzidos comentrios relevantes,
que expliquem o significado dos diferentes objectos, ou que operao efectuada por
grupos bem definidos de instrues. Um comentrio uma qualquer sequncia de
smbolos inserida entre /* e */ e que no contenha */. Isto significa que no se pode
nunca encapsular comentrios. A Figura 1.5 apresenta a definio formal do comentrio.
Figura 1.5 - Definio formal do comentrio.
O uso adequado de comentrios melhora extraordinariamente a legibilidade e a
compreenso de um segmento de cdigo. Assim, devem introduzir-se comentrios, pelo
menos, nas situaes seguintes:
x Em ttulo, para explicar o que faz o segmento de cdigo e descrever, caso exista, o
mecanismo de comunicao associado.
x Sempre que se declarem constantes ou variveis, para explicar o seu significado, a
menos que este seja trivial.
x A encabear as pores de cdigo correspondentes decomposio algortmica que lhe
deu origem.
1.4 Representao da informao
Em Pascal, os tipos de dados predefinidos esto directamente relacionados com o tipo de
informao neles armazenado. Assim, para informao numrica, existem os tipos inteiro
(integer) para a representao exacta e real (real) para a representao aproximada; para
quantidades lgicas existe o tipo booleano (boolean); e para a representao de smbolos
grficos existe o tipo carcter (char).
Em linguagem C, pelo contrrio, os tipos de dados predefinidos reflectem apenas o
formato de armazenamento. So sempre tipos numricos, embora em alguns casos
possibilitem uma interpretao alternativa, funo do contexto em que so usados.
comentrio ::= /* qualquer sequncia de smbolos que no contenha */ */
7 CAPTULO 1 : INTRODUO AO C
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.
Tipos de Dados Simples
Enumerado
enum
Aritmticos
float
Ponteiro
pointer
Tipos Inteiros Tipos Reais
double char int
Tipos Bsicos
Qualificativos
short
long
signed
unsigned
Figura 1.6 - Tipos de dados simples existentes na linguagem C.
1.4.1 Tipos de dados inteiros
Na linguagem C existem os seguintes tipos de dados inteiros:
x O tipo char permite a representao de quantidades com sinal num byte e portanto,
permite armazenar valores entre -128 a 127.
x O tipo unsigned char permite a representao de quantidades sem sinal num byte e
portanto, permite armazenar valores entre 0 e 255.
x O tipo short [int] permite a representao de nmeros negativos e positivos em 2 bytes e
portanto, permite armazenar valores entre -32768 e 32767.
x O tipo unsigned short [int] permite a representao de nmeros positivos em 2 bytes e
portanto, permite armazenar valores entre 0 e 65535.
x O tipo int permite a representao de nmeros negativos e positivos em 2 bytes ou 4
bytes, consoante o computador.
x O tipo unsigned int permite a representao de nmeros positivos em 2 bytes ou 4 bytes,
consoante o computador.
x O tipo long [int] permite a representao de nmeros negativos e positivos em 4 bytes
ou 8 bytes, consoante o computador.
x O tipo unsigned long [int] permite a representao de nmeros positivos em 4 bytes ou
8 bytes, consoante o computador.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 8
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.
1.4.2 Tipos de dados reais
Na linguagem C existem os seguintes tipos de dados reais: float; double; e long double. O
tamanho, a preciso e a gama dinmica dos tipos reais dependem do compilador e do
hardware utilizados. Esta informao indicada no ficheiro float.h localizado no directrio
include do ambiente de desenvolvimento. Para o caso do computador utilizado nesta
disciplina, o tamanho, a preciso e a gama dinmica dos tipos reais so os seguintes:
x O tipo float utiliza 4 bytes, o que permite armazenar valores entre 1.2x10
-38
e 3.4x10
38
,
com uma mantissa de 6-7 algarismos significativos.
x O tipo double utiliza 8 bytes, o que permite armazenar valores entre 2.2x10
-308
e
1.8x10
308
, com uma mantissa de 15-16 algarismos significativos.
x O tipo long double utiliza 12 bytes, o que permite armazenar valores entre 3.4x10
-4932
e
1.2x10
4932
, com uma mantissa de 18-19 algarismos significativos.
1.4.3 Representao de caracteres e inteiros
A maioria das linguagens de programao, entre as quais se inclui o Pascal, faz a distino
entre o tipo inteiro e o tipo carcter. Mesmo, apesar de nessas linguagens, os caracteres
serem armazenados na memria em numrico, usando para o efeito o cdigo ASCII.
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.
Figura 1.7 - Utilizao do tipo char.
char CAR; /* declarao da varivel CAR de tipo char */
...
CAR = 'A'; /* ambas as atribuies armazenam */
CAR = 65; /* na posio de memria CAR o valor 65 */
char NUM, CAR; /* declarao das variveis NUM e CAR de tipo char */
...
NUM = 3; /* armazena na posio de memria NUM o valor 3 */
CAR = '3'; /* armazena na posio de memria CAR o valor 51 */
9 CAPTULO 1 : INTRODUO AO C
1.5 Constantes e variveis
Uma constante um objecto, cujo valor se mantm invariante durante a execuo do
programa. A utilizao de um valor constante num programa, no fornece qualquer
indicao sobre o seu significado ou finalidade. Pelo que, a utilizao de uma mnemnica,
ou seja, um nome associado a um valor constante, permite aumentar a legibilidade de um
programa. Por outro lado, se um valor constante aparecer mais do que uma vez ao longo
do programa, pode acontecer que o programador cometa algum lapso na repetio do valor
e ter um erro algortmico que no detectvel pelo compilador e que pode ser muito difcil
de detectar pelo prprio programador. A utilizao de uma constante permite assim
parametrizar um programa, melhorar a legibilidade, j que os valores so substitudos por
nomes com significado explcito e, a robustez, porque a alterao do valor realizada de
um modo centralizado.
Ao contrrio do que se passa em Pascal, a linguagem C no contempla a definio explcita
de identificadores associados com constantes. Esta restrio pode ser ultrapassada, tal
como se apresenta na Figura 1.2, usando a seguinte directiva do pr-processador.
#define identificador de constante expresso
O que o pr-processador faz, ao encontrar esta directiva no ficheiro fonte, efectuar a
partir desse ponto a substituio de todas as ocorrncias do identificador de constante pela
expresso, o que se designa por uma macro de substituio. Como se trata de um processo
de substituio puro, e no de clculo do valor associado, torna-se necessrio, sempre que a
expresso no for um literal, coloc-la entre parnteses curvos para garantir o clculo
correcto do seu valor, independentemente do contexto em que est localizada.
#define identificador de constante ( expresso )
Um dos erros mais frequentemente cometido, por programadores que se esto a iniciar na
utilizao da linguagem C, quando utilizam esta directiva na definio de um identificador
constante a sua terminao com o ;. Nesse caso o ; torna-se parte da substituio
podendo gerar situaes de erro.
As constantes numricas inteiras podem ser representadas no sistema decimal, no sistema
octal, em que a constante precedida pelo dgito 0 ou no sistema hexadecimal, em que a
constante precedida pelo dgito 0 e pelo carcter x. Quando as constantes esto
representadas nos sistemas octal ou hexadecimal, o sinal normalmente expresso de uma
maneira implcita, usando a representao em complemento verdadeiro. A Figura 1.8
apresenta alguns exemplos, considerando uma representao do tipo int em 32 bits.
Figura 1.8 - Exemplos de constantes inteiras.
O compilador atribui por defeito o tipo int a uma constante numrica inteira. Quando este
tipo no tem capacidade de armazenamento suficiente, os tipos seguintes so
sucessivamente atribudos. No caso de uma constante decimal, usa-se o tipo long, ou se
ainda for insuficiente o tipo unsigned long. No caso de uma constante octal ou
hexadecimal, usa-se pela seguinte ordem o tipo unsigned int, ou o tipo long, ou o tipo
unsigned long.
Sistema decimal Sistema octal Sistema hexadecimal
54 066 0x36
-135 -0207 -0x87
em complemento verdadeiro 037777777571 0xFFFFFF79
0 00 0x0
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10
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.
As constantes numricas reais so sempre expressas no sistema decimal, usando quer a
representao em parte inteira e parte fraccionria, quer a chamada notao cientfica. O
compilador atribui por defeito o tipo double a uma constante numrica real. A atribuio
do tipo pode ser forada pelos sufixos F e L. Uma constante seguida do sufixo F do tipo
float. Uma constante seguida do sufixo L do tipo long double. So exemplos de
constantes reais os valores 0.0148, 1.48e-2 e 0.0.
As constantes de tipo carcter podem ser expressas indiferentemente pelo respectivo
smbolo grfico, colocado entre aspas simples, ou atravs do valor do seu cdigo de
representao nos sistemas octal e hexadecimal, precedidas do carcter '\', tal como se
mostra na Figura 1.9.
Figura 1.9 - Exemplo de constante de tipo carcter.
Para alguns caracteres de controlo, pode ainda ser usada uma representao alternativa que
consiste numa letra do alfabeto minsculo precedida do carcter '\'. Por exemplo o carcter
de fim de linha o '\n', o carcter de backspace o '\b', o carcter de tabulao o '\t', o
carcter aspas duplas o '\' e o carcter ponto de interrogao o '\?'.
As constantes de tipo cadeia de caracteres so expressas como uma sequncia de caracteres,
representados por qualquer dos mtodos anteriores, colocados entre aspas duplas. Por
exemplo Ola malta!\n.
Uma varivel um objecto, cujo valor se altera em princpio durante a execuo do
programa, excepo eventualmente feita s variveis de entrada, cujo valor depois de lido
do teclado , em princpio, mantido inalterado at ao fim da execuo do programa.
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 Figura 1.10 apresenta a definio formal da declarao de variveis na linguagem C. Para
declarar variveis comea-se por identificar o tipo de dados seguido da varivel, ou lista de
variveis que se pretendem declarar desse tipo, separadas por vrgulas, terminando a
declarao com o separador ;. conveniente agrupar a declarao de variveis do mesmo
tipo na mesma linha para aumentar a legibilidade do programa.
A reserva de espao em memria no pressupe, em princpio, a atribuio de um valor
inicial varivel. Em consequncia, nada deve ser presumido sobre o seu valor antes que
uma primeira atribuio tenha sido efectivamente realizada. No entanto, a linguagem C
permite combinar a definio de uma varivel com a atribuio de um valor inicial, usando
para o efeito o operador de atribuio seguido da expresso de inicializao.
Sistema decimal Sistema octal Sistema hexadecimal
'B' '\102' '\x42'
11 CAPTULO 1 : INTRODUO AO C
Figura 1.10 - Definio formal da declarao de variveis.
Ao contrrio de outras linguagens de programao a linguagem C usa apenas um nico
smbolo, o smbolo =, como operador de atribuio. A diferena do operador de atribuio
em relao ao Pascal um dos erros mais frequentemente cometido por programadores
que se esto a iniciar na utilizao da linguagem C.
A regio de reserva de espao em memria principal no necessariamente contgua, pelo
que, frequente surgirem buracos resultantes do alinhamento das variveis, em reas de
endereos mltiplos de 4, para maximizar a taxa de transferncia de informao entre o
processador e a memria principal. A Figura 1.11 apresenta alguns exemplos de declarao
de variveis e sua colocao na memria.
-2.5
1324
'G'
A
B
C
D
E
4 bytes
4 bytes
8 bytes
8 bytes
rea reservada
mas no inicializada
rea no reservada
char A, B = 'G';
unsigned int C = 1324;
double D = -2.5, E;
rea reservada
e inicializada
Figura 1.11 - Alguns exemplos de declarao de variveis e sua colocao na memria.
declarao de variveis ::= declarao de variveis de um tipo |
declarao de variveis |declarao de variveis de um tipo
declarao de variveis de um tipo ::= tipo de dados lista de variveis ;
tipo de dados ::= qualquer tipo de dados vlido na linguagem C
lista de variveis ::= identificador de varivel genrico |
lista de variveis , identificador de varivel genrico
identificador de varivel genrico ::= identificador de varivel |
identificador de varivel = expresso de inicializao
identificador de varivel ::= identificador vlido na linguagem C
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
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 }.
Figura 1.12 - Definio formal da sequncia de instrues.
Em Pascal, a invocao de uma funo, sendo uma expresso, no pode ser considerada
uma instruo. Na linguagem C, contudo, o conceito de procedimento no tem uma
existncia separada. Define-se como sendo uma funo de um tipo especial, o tipo void.
Pelo que, a invocao de um procedimento , por isso, em tudo semelhante invocao de
uma funo de qualquer outro tipo, quando o valor devolvido no tido em considerao.
Logo, nestas circunstncias, na linguagem C, a pura e simples invocao de uma funo de
qualquer tipo considerada uma instruo.
A sequenciao de instrues a forma mais simples de controlo de fluxo num programa,
em que as instrues so executadas pela ordem em que aparecem no programa. Dentro
das instrues simples, apenas as instrues de atribuio, de entrada-sada e de invocao
de uma funo, so verdadeiramente instrues de sequenciao, j que, as instrues
decisrias e repetitivas permitem alterar a ordem do fluxo do programa.
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.
Figura 1.13 - Definio formal da instruo composta.
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.
instruo composta ::= { sequncia de instrues simples }
sequncia de instrues simples ::= instruo simples |
sequncia de instrues simples instruo simples
instruo simples ::= instruo de atribuio |
instruo decisria |
instruo repetitiva |
instruo de entrada-sada |
invocao de uma funo
13 CAPTULO 1 : INTRODUO AO C
Figura 1.14 - Definio formal de uma expresso.
Os primeiros trs tipos de expresses so a frmula mais simples de produzir um valor e
resumem-se atribuio a uma varivel, do valor de uma constante, ou do valor de uma
varivel ou do valor devolvido por uma funo. As expresses mais complexas envolvem
operadores unrios ou binrios. Uma expresso pode ser composta por uma expresso
colocada entre parnteses curvos. Este tipo de expresso usa-se para compor expresses
mais complexas, ou para modificar a prioridade do clculo de expresses parcelares.
Vamos agora analisar as expresses aritmticas. O clculo de uma expresso complexa
supe um processo de decomposio prvio em expresses mais simples, que
determinado pela precedncia e pela associatividade dos operadores presentes.
Precedncia significa importncia relativa entre os operadores. Operadores de maior
precedncia foram a ligao a si dos operandos, antes dos operadores de menor
precedncia Por exemplo, como a multiplicao tem precedncia sobre a adio, ento a
expresso a + b * c tem subjacente o agrupamento a + (b * c).
Quando numa expresso todos os operadores tm a mesma precedncia, a associatividade
permite determinar a ordem de ligao dos operandos aos operadores, da direita para a
esquerda, ou da esquerda para a direita. Por exemplo, como a associatividade da adio da
direita para a esquerda, ento a expresso a + b + c tem subjacente o agrupamento
(a + b) + c.
No entanto, qualquer que seja o agrupamento imposto pela precedncia e pela
associatividade dos operadores presentes, ele pode ser sempre alterado pela introduo de
parnteses curvos.
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.
Os operadores binrios supem normalmente operandos do mesmo tipo. Quando so de
tipo diferente, a expresso do tipo com menor capacidade de armazenamento,
automaticamente convertida no tipo da outra expresso. A hierarquia dos diferentes tipos
de dados, expressa por ordem decrescente da sua capacidade de armazenamento, a que se
apresenta a seguir.
long double o double o float o unsigned long o long o unsigned int o int
Alm das converses automticas que foram referidas, a converso de uma expresso num
tipo especfico qualquer pode ser sempre forada atravs do operador cast.
( qualquer tipo de dados escalar vlido em C ) ( expresso numrica )
expresso ::= constante | varivel | invocao de uma funo |
operador unrio expresso | expresso operador unrio
expresso operador binrio expresso | ( expresso )
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
A Figura 1.15 apresenta um exemplo da utilizao do operador cast. A diviso de duas
variveis inteiras d um resultado inteiro. Para forar a diviso real preciso forar um dos
operandos a double, fazendo um cast de um dos operandos, neste caso da varivel A.
Figura 1.15 - Exemplo da utilizao do operador cast.
Se a converso se efectua no sentido crescente da hierarquia, no h risco de overflow ou de
perda de preciso. Caso contrrio, estes problemas podem ocorrer. Concretamente, quando
a converso se efectua no sentido decrescente da hierarquia, podemos ter as situaes
apresentadas na Figura 1.16.
Figura 1.16 - Situaes possveis se a converso se efectuar no sentido decrescente da hierarquia.
Vamos agora apresentar alguns exemplos representativos destes problemas. A Figura 1.17
apresenta um exemplo da situao em que se atribui um valor negativo de uma varivel int
a uma varivel unsigned int. O valor armazenado na memria em binrio vai ser
interpretado como sendo positivo, pelo que, h uma mudana na interpretao do valor.
Figura 1.17 - Mudana na interpretao do valor.
A Figura 1.18 apresenta um exemplo da situao em que se atribui uma varivel unsigned
int a uma varivel unsigned char, que tem uma menor capacidade de armazenamento. O
valor que vai ser armazenado em B constitudo pelos ltimos 8 bits de A, ou seja, o resto
da diviso de A por 256, que o mximo valor que se pode armazenar num byte.
int A = -1024; unsigned int B;
...
B = A;
/* A = -1024
10
= 1111 1111 1111 1111 1111 1100 0000 0000
2
*/
/* B = 1111 1111 1111 1111 1111 1100 0000 0000
2
= 4294966272
10
*/
Tipos de dados de partida Tipos de dados de chegada
long double double ou float
existe arredondamento e portanto pode ocorrer overflow
double float
existe arredondamento e portanto pode ocorrer overflow
qualquer tipo real qualquer tipo inteiro
existe truncatura e portanto pode ocorrer overflow
tipo inteiro signed (unsigned) mesmo tipo inteiro unsigned (signed)
mudana na interpretao do valor
tipo inteiro unsigned tipo hierarquicamente inferior unsigned
resto do mdulo do registo de chegada
tipo inteiro signed tipo hierarquicamente inferior signed
pode ocorrer overflow
int A = 5, B = 2; double DIVISAO;
...
DIVISAO = A / B; /* DIVISAO = 2.0 */
if ( expresso )
{
instruo simples;
...
instruo simples;
}
if ( expresso )
{
instruo simples;
...
instruo simples;
}
else
{
instruo simples;
...
instruo simples;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 24
Figura 1.39 - Solues para a situao do else desligado.
Devido ao facto das instrues de atribuio constiturem tambm expresses, um erro
muito frequentemente cometido por programadores que se esto a iniciar na utilizao da
linguagem C e que no sinalizado pelo compilador, consiste em trocar o operador
identidade pelo operador de atribuio na escrita da condio decisria, tal como se
apresenta na Figura 1.40. O valor da expresso A = 5 5 e como um valor diferente de
zero, ento a expresso verdadeira e vai ser executada a instruo B = 2. Pelo que, aps a
execuo da instruo, as variveis A e B vo assumir respectivamente, os valores 5 e 2.
Figura 1.40 - Erro devido troca do operador identidade pelo operador de atribuio.
1.7.1.2 A instruo decisria mltipla switch
Muitas das situaes de estruturas if encadeadas podem ser resolvidas atravs da instruo
decisria mltipla switch (comutador), cuja definio formal se apresenta na Figura 1.41.
Figura 1.41 - Definio formal da instruo switch.
Embora as palavras reservadas na linguagem C sejam distintas das do Pascal, a estrutura
sintctica da instruo de deciso mltipla essencialmente a mesma nas duas linguagens.
Na norma ANSI, a expresso de deciso, bem como as constantes que formam a lista de
constantes, so de qualquer tipo escalar bsico inteiro. A principal diferena decorre do
modo como os vrios ramos de seleco esto organizados. Em Pascal, eles so
mutuamente exclusivos, enquanto que, na linguagem C, a execuo sequencial a partir do
instruo decisria mltipla ::= switch ( expresso )
{
bloco de execuo
}
bloco de execuo ::= bloco de execuo selectivo |
bloco de execuo bloco de execuo terminal
bloco de execuo selectivo ::= lista de constantes sequncia de instrues simples |
bloco de execuo selectivo
lista de constantes sequncia de instrues simples
lista de constantes ::= case constante inteira : |
lista de constantes
case constante inteira :
bloco de execuo terminal ::= default : sequncia de instrues simples
if (A = 5) B = 2; /* o que se pretendia era if (A == 5) B = 2; */
else B = 4;
if (V1 > 0)
{
if (V2 > 0) V2++;
}
else V1++;
if (V1 > 0)
if (V2 > 0) V2++;
else ;
else V1++;
25 CAPTULO 1 : INTRODUO AO C
ponto de entrada. Para se conseguir o mesmo tipo de execuo encontrado em Pascal, a
ltima instruo de cada sequncia de instrues ter que ser obrigatoriamente a instruo
break, tal como se mostra na Figura 1.43.
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.
Figura 1.42 - Exemplo da utilizao e alinhamento da instruo switch.
A Figura 1.43 faz a comparao da instruo switch com a instruo case.
Figura 1.43 - Comparao da instruo switch com a instruo case.
preciso ter em considerao que a instruo switch no to poderosa como a instruo
case do Turbo Pascal, uma vez que a ltima permite que a lista de valores enumerada
possa ser constituda por um literal, ou por um conjunto de literais separados pela vrgula,
ou por um intervalo de valores, ou por combinaes de todas estas situaes.
1.7.2 Instrues repetitivas
Tal como na linguagem Pascal, na linguagem C existem dois tipos de instrues de
repetio. As instrues while e do while, cujo nmero de iteraes previamente
desconhecido, tm uma estrutura de controlo condicional. A instruo for, cujo nmero de
iteraes previamente conhecido, tem normalmente uma estrutura de controlo contadora.
case CAR of /* na linguagem Pascal */
'a', 'e', 'o' : writeln ('Vogais speras');
'i', 'u' : writeln ('Vogais doces');
else writeln ('Outros smbolos grficos')
end;
while ( expresso )
{
instruo simples;
...
instruo simples;
}
27 CAPTULO 1 : INTRODUO AO C
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.
Figura 1.46 - Alinhamento da instruo do while.
A Figura 1.47 faz a comparao dos ciclos repetitivos do while e while, para calcular a
mdia de um nmero indeterminado de nmeros lidos do teclado, sendo que, a leitura
termina quando lido o valor zero. No caso do ciclo repetitivo do while, as instrues
constituintes do corpo do ciclo repetitivo, contagem do nmero til de nmeros lidos e sua
soma, necessitam de ser protegidas quando lido o valor de terminao, para no provocar
um clculo errneo.
Figura 1.47 - Exemplo comparativo da utilizao dos ciclos repetitivos do while e while.
1.7.2.2 A instruo repetitiva for
A Figura 1.48 apresenta a definio formal da instruo repetitiva for (para fazer enquanto),
cujo nmero de iteraes previamente conhecido. A instruo for da linguagem C
constitui uma superinstruo que no tem correspondncia em mais nenhuma linguagem.
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.
SOMA = 0.0; /* clculo da mdia com o ciclo do while */
do
{
printf ("Introduza um numero? ");
scanf ("%lf", &NUMERO);
if (NUMERO != 0.0)
{
SOMA += NUMERO;
N++;
}
} while (NUMERO != 0.0);
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);
}
31 CAPTULO 1 : INTRODUO AO C
Compare as solues e repare que as instrues condicionais na verso em linguagem C
esto simplificadas, uma vez que no necessitam de ser encadeadas, para evitar as situaes
em que os caracteres no precisam de ser convertidos. Se o carcter no um carcter
alfabtico, ou se um carcter alfabtico, mas no uma vogal, ento fora-se a prxima
iterao do ciclo repetitivo, usando para esse efeito a instruo continue.
1.7.2.4 Ciclos repetitivos infinitos
Um ciclo repetitivo infinito um ciclo repetitivo que no tem condio de terminao, ou
cuja condio de terminao est mal implementada. A maior parte desses ciclos repetitivos
so criados por engano. Mas, existem situaes em que se pretende de facto implementar
um ciclo repetitivo infinito. Por exemplo, quando se pretende uma aplicao que repete
sistematicamente uma determinada operao at que uma condio particular se verifica.
Nessa situao mais fcil construir um ciclo repetitivo, que terminado atravs do
recurso instruo break. A Figura 1.53 apresenta as duas formas mais simples de
implementar ciclos repetitivos infinitos.
Figura 1.53 - Ciclos repetitivos infinitos.
1.8 Entrada e sada de dados
Tal como em qualquer outra linguagem, as instrues de entrada e de sada da linguagem C
so instrues especiais que estabelecem uma interface com o sistema operativo na
comunicao com os diferentes dispositivos do sistema computacional, em particular com
os dispositivos convencionais de entrada e de sada. Normalmente, o dispositivo de entrada
o teclado e mencionado pelo identificador stdin, e o dispositivo de sada o monitor e
mencionado pelo identificador stdout.
Os dispositivos de entrada e de sada funcionam da seguinte forma. Sempre que se prime
uma tecla do teclado, gerada uma palavra binria, representando o valor atribudo ao
carcter correspondente, em cdigo ASCII por exemplo, que armazenada num registo do
controlador do teclado. O sistema operativo alertado para esse facto e, eventualmente, vai
ler o contedo do registo e armazenar sequencialmente o valor lido numa regio da
memria principal que se designa por armazenamento tampo de entrada (input buffer).
O valor tambm quase sempre colocado no armazenamento tampo de sada do monitor,
para posterior envio para o monitor, produzindo-se assim a interaco visual com o
utilizador. Em termos da instruo de entrada, o armazenamento tampo de entrada visto
como uma sequncia ordenada de caracteres, organizada em linhas que consistem em zero
ou mais caracteres, com representao grfica, ou com funes de controlo, seguidos do
carcter de fim de linha que o '\n'. A esta sequncia de caracteres d-se o nome de fluxo de
caracteres (text stream). De um modo geral, s linhas completas presentes no fluxo de
caracteres de entrada esto disponveis para leitura. medida que os caracteres vo sendo
processados pela instruo de entrada, vo sendo retirados do fluxo de caracteres de
entrada.
while ( 1 ) /* com o ciclo while */
instruo simples ou composta
char FRASE[80];
...
scanf ("%79[^\n]", FRASE);
/* ou em alternativa scanf ("%79[^\n]", &FRASE[0]); */
37 CAPTULO 1 : INTRODUO AO C
uma cadeia de caracteres nula, colocando o carcter terminador na primeira posio da
cadeia de caracteres.
Figura 1.59 - Leitura de uma cadeia de caracteres equivalente instruo readln do Pascal.
1.8.1.3 Leitura de valores numricos
Na leitura de valores numricos, a introduo do especificador de dimenso no
especificador de converso permite atribuir os valores lidos a variveis de tipo short ou de
tipo long, em vez de tipo int, ou a variveis de tipo double ou de tipo long double, em
vez de tipo float. O recurso largura mxima de campo s deve ser efectuado quando se
pretende ler valores com um formato bem definido. Caso contrrio, no deve ser usado
para possibilitar uma leitura em formato livre. Se os valores lidos forem demasiado grandes
para poderem ser armazenados nas variveis que lhes esto associadas, vai ocorrer overflow.
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.
Figura 1.60 - Tabela de sequncias admissveis em funo do carcter de converso.
#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';
...
}
Carcter de converso Sequncia admissvel Observao
tipos inteiros
i (tipo signed) [+/-]#...# # - algarismo decimal
[+/-]0x#...# # - algarismo hexadecimal
[+/-]0#...# # - algarismo octal
d (tipo signed) [+/-]#...# # - algarismo decimal
u (tipo unsigned) [+/-]#...# # - algarismo decimal
x (tipo unsigned) [+/-]0x#...# # - algarismo hexadecimal
o (tipo unsigned) [+/-]0#...# # - algarismo octal
tipos reais
f,e,g [+/-]#...# # - algarismo decimal
[+/-][#...#][.]#...#
[+/-][#...#][.]#...#[e/E[+/-]#...#]
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 38
A Figura 1.61 apresenta alguns exemplos de leituras de valores numricos. A execuo da
funo para a linha de dados introduzida pelo teclado e constituda pelos seguintes
caracteres -123 0xF2'\t'-1.47e1'\n', resulta no armazenamento das quantidades -123 na
varivel I, 242 na varivel S e -14.7 na varivel D. No caso de uma linha de dados
constituda pelos caracteres '\t'12 -023 e-4'\n', resulta no armazenamento das quantidades
12 na varivel I, -19 na varivel S e nenhum valor na varivel D, porque ocorreu um
conflito. O conflito deve-se ao facto de que a sequncia e-4 no ser uma sequncia de
caracteres admissvel para representar uma quantidade real em notao cientfica. Para
corrigir este conflito deve-se usar a sequncia de caracteres 1e-4.
Figura 1.61 - Exemplo de leitura de valores numricos.
O uso do carcter separador no formato de leitura deve ser evitado porque, ou
irrelevante, quando colocado no princpio, ou entre especificadores de converso, j que o
carcter separador funciona como elemento de separao de sequncias numricas, ou tem
efeitos colaterais nocivos, quando colocado no fim, j que obriga deteco de um carcter
distinto no fluxo de texto de entrada, antes da concluso da instruo. Procure descobrir o
que vai acontecer quando forem executadas as funes scanf apresentadas na Figura 1.62,
para as duas linhas de dados introduzidas pelo teclado e constitudas pelos seguintes
caracteres 1 2'\n' e 3'\n'.
Figura 1.62 - Exemplo incorrecto de leitura de valores numricos.
Por causa do literal '\n' no fim da cadeia de caracteres de definio, a primeira invocao da
funo scanf s termina quando se tecla o carcter 3, pelo que, a mensagem da segunda
invocao da funo printf s escrita no monitor depois de introduzida a segunda linha
de dados, quando deveria aparecer antes.
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.
int A, B, C;
...
printf ("Valores 1 e 2? ");
scanf ("%d%d\n", &A, &B);
printf ("Valor 3? ");
scanf ("%d", &C);
int I;
short S;
double D;
...
scanf ("%d%hi%lf", &I, &S, &D);
39 CAPTULO 1 : INTRODUO AO C
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.
Figura 1.64 - Leitura de uma informao horria.
O especificador de converso %n permite contar a nmero de caracteres lidos at ao seu
aparecimento na cadeia de caracteres de definio. utilizado quando queremos verificar
se o formato dos dados est de acordo com a cadeia de caracteres de definio que se est a
utilizar. A Figura 1.65 apresenta um exemplo da sua aplicao. Vamos considerar que
queremos ler um valor numrico mas, que este se encontra a seguir a outro valor, e que este
primeiro valor deve ser descartado porque no nos interessa. Se por algum motivo existir
um erro de formato e existir apenas um valor para ser lido, esse valor descartado e no
lido nem armazenado na varivel NUM. Para detectar se h ou no um erro no formato
esperado, podemos determinar o nmero de caracteres lidos antes da leitura da varivel
NUM. Se o valor de N for nulo sinal que no havia nenhum valor inicial, e, portanto, o
formato previsto dos dados de entrada no se verifica.
#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 */
...
}
#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 */
...
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 40
Figura 1.65 - Exemplo da utilizao do especificador de converso %n.
1.8.2 A funo printf
Na linguagem C, a sada de dados implementada pela funo printf cuja sintaxe se
apresenta na Figura 1.66. A funo printf escreve sucessivamente sequncias de caracteres
no fluxo de texto de sada (stdout), representativas de texto e dos valores das expresses que
formam a lista de expresses, segundo as regras impostas pelo formato de escrita.
O texto especificado no formato de escrita pela introduo de literais, que so copiados
sem modificao para o fluxo de texto de sada. O modo como o valor das expresses
convertido, descrito pelos especificadores de converso. Deve, por isso, existir uma
relao de um para um entre cada especificador de converso e cada expresso da lista de
expresses. Se o nmero de expresses for insuficiente, o resultado da operao no est
definido. Se, ao contrrio, esse nmero for demasiado grande, as expresses em excesso
so ignoradas.
O tipo da expresso e o especificador de converso devem ser compatveis, j que a
finalidade deste ltimo indicar, em cada caso, o formato da sequncia convertida. Quando
o especificador de converso no vlido, o resultado da operao no est definido.
O processo de escrita s termina quando o formato de escrita se esgota, ou ocorreu um
erro. A funo devolve o nmero de caracteres escritos no fluxo de texto de sada, ou o
valor -1, se ocorreu um erro.
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 %%.
A largura mnima de campo indica o nmero mnimo de caracteres usados na
representao do valor da expresso. Quando a expresso pode ser representada com
menos caracteres, so introduzidos espaos ou zeros at perfazer o nmero indicado.
Quando a largura mnima de campo insuficiente, o campo alargado para conter o valor
convertido.
int NUM, /* valor a ser lido */
N; /* contador de caracteres lidos */
...
N = 0; /* inicializao do contador de caracteres lidos */
scanf ("Numero = %*d%n%d", &N, &NUM);
/* leitura do valor e utilizao do contador */
41 CAPTULO 1 : INTRODUO AO C
Figura 1.66 - Definio formal da funo printf.
int printf ( formato de escrita , lista de expresses )
formato de escrita ::= "cadeia de caracteres de definio"
cadeia de caracteres de definio ::= especificador de converso | literal |
cadeia de caracteres de definio especificador de converso |
cadeia de caracteres de definio literal
especificador de converso ::= %carcter de converso |
%modificador de converso carcter de converso
carcter de converso ::= i d escreve uma quantidade inteira decimal (signed)
u escreve uma quantidade inteira em decimal (unsigned)
o escreve uma quantidade inteira em octal (unsigned)
x escreve uma quantidade inteira em hexadecimal (unsigned)
f escreve uma quantidade real em notao PI.PF
e E escreve uma quantidade real em notao cientfica. O
carcter indicador de expoente e ou E conforme se usa o carcter de converso e ou E
g G escreve uma quantidade real no formato f ou e dependente
do valor. O carcter indicador de expoente e ou E conforme se usa o carcter de
converso g ou G
c escreve um caracter
s escreve uma cadeia de caracteres
p escreve o valor de um ponteiro para void
modificador de converso ::= modificador de formato | especificador de preciso |
especificador de dimenso |
modificador de formato especificador de preciso |
modificador de formato especificador de dimenso |
especificador de preciso especificador de dimenso |
modificador de formato especificador de preciso especificador de dimenso
modificador de formato ::= modificador de aspecto | largura mnima de campo |
modificador de aspecto largura mnima de campo
modificador de aspecto ::= qualquer combinao de espao, , +, #, 0
largura mnima de campo ::= valor decimal positivo que indica o nmero mnimo de
caracteres a serem escritos
especificador de preciso ::= . | . valor decimal positivo
especificador de dimenso ::= h com i, d, u, o, x, indica tipo short
l com i, d, u, o, x, indica tipo long
L com f, e, g, indica tipo long double
literal ::= carcter diferente de % | literal carcter diferente de %
lista de expresses ::= expresso de tipo aritmtico | expresso de tipo ponteiro para void |
lista de expresses , expresso de tipo aritmtico |
lista de expresses , expresso de tipo ponteiro para void
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 42
O papel do modificador de aspecto controlar a apresentao dos valores convertidos.
Assim, tem-se que:
x O carcter , significa justificao da sequncia convertida esquerda no campo. Por
defeito, ela justificada direita.
x O carcter 0, significa que quando for necessrio introduzir caracteres extra para
perfazer a largura mnima de campo na representao de valores numricos, so
introduzidos zeros, em vez de espaos, para uma justificao direita.
x O carcter +, significa que todos os valores numricos passam a ter sinal, os positivos, o
sinal + e os negativos, o sinal . Por defeito, s os valores negativos so representados com
sinal.
x O carcter espao, significa que os valores numricos positivos passam a ser
representados com um espao na posio do sinal. Por defeito, s os valores negativos so
representados com sinal.
x O carcter #, significa que os valores numricos no sistema hexadecimal ou octal so
representados precedidos de '0x' ou '0', respectivamente. Os valores numricos reais so
representados com o separador ., mesmo que no haja parte fraccionria. Alm disso,
quando for usado o carcter de converso g, os zeros direita na parte fraccionria so
representados.
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 Figura 1.67 apresenta alguns exemplos da aplicao de vrios formatos de escrita de
valores numricos e as respectivas sequncias de caracteres escritas no monitor.
43 CAPTULO 1 : INTRODUO AO C
Figura 1.67 - Exemplos de formatos de escrita.
1.8.2.1 Escrita de caracteres
Para a escrita de um carcter, deve-se utilizar o seguinte especificador de converso.
%nmero de colunas de impressoc
Sendo que o modificador de aspecto , usado quando se pretender a justificao do
carcter esquerda dentro do nmero de colunas de impresso indicado. No possvel
escrever-se mais do que um carcter de cada vez. A expresso argumento de tipo int
convertida para tipo unsigned char e o valor obtido impresso. A Figura 1.68 apresenta
alguns exemplos de escrita do carcter *. No primeiro caso no se especifica a largura
mnima de campo, pelo que, o carcter escrito numa nica coluna de impresso. Nos
segundo e terceiro casos, especifica-se cinco colunas de impresso, com justificao
direita, que a situao por defeito, e com justificao esquerda usando o modificador de
aspecto .
Figura 1.68 - Escrita de um carcter com vrios formatos.
1.8.2.2 Escrita de cadeias de caracteres
Para a escrita de uma cadeia de caracteres, deve-se utilizar o seguinte especificador de
converso.
% nmero mnimo de colunas de impresso.nmero mximo de colunas de impressos
Sendo que o modificador de aspecto , usado quando se pretender a justificao da
cadeia de caracteres esquerda dentro do nmero mnimo de colunas de impresso
indicado. A expresso argumento deve referenciar a regio de memria onde a sequncia
de caracteres, terminada necessariamente pelo carcter '\0', est armazenada, sendo
tipicamente um agregado de caracteres. O nmero de caracteres a imprimir igual ao
nmero de caracteres da cadeia de caracteres, se o especificador de preciso no tiver sido
usado, ou se tiver um valor maior ou igual ao comprimento da cadeia de caracteres, ou,
alternativamente, igual ao valor do especificador de preciso, em caso contrrio.
printf ("->%c<-\n", '*'); /* ->*<- */
printf ("->%5c<-\n", '*'); /* -> *<- */
printf ("->%-5c<-\n", '*'); /* ->* <- */
printf ("->%12d\n", 675); /* -> 675 */
printf ("->%012d\n", 675); /* ->000000000675 */
printf ("->%12.4d\n", 675); /* -> 0675 */
printf ("->%12f\n", 675.95); /* -> 675.950000 */
printf ("->%12.4f\n", 675.95); /* -> 675.9500 */
printf ("->%12.1f\n", 675.95); /* -> 676.0 */
printf ("->%12.6g\n", 675.0000); /* -> 675 */
printf ("->%#12.6g\n", 675.0000); /* -> 675.000 */
printf ("->%12.3g\n", 675.0); /* -> 675 */
printf ("->%12.2g\n", 675.0); /* -> 6.8e+02 */
printf ("->%12.1g\n", 675.0); /* -> 7e+02 */
printf ("->%#12.1g\n", 675.0); /* -> 7.e+02 */
printf ("->%#12.1G\n", 675.0); /* -> 7.E+02 */
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 44
A Figura 1.69 apresenta alguns exemplos de escrita de uma cadeia de caracteres. No
primeiro caso no se usa qualquer formato, pelo que, a escrita ocupa o nmero de colunas
de impresso igual ao comprimento da cadeia de caracteres. Nos segundo e terceiro casos
como o nmero mnimo de colunas insuficiente, ento ignorado. No quarto caso
apenas imprimido o nmero de caracteres indicado pelo especificador de preciso. No
quinto caso no impresso nada, uma vez que o especificador de preciso nulo. Nos
sexto e stimo casos usa-se um nmero mnimo de colunas de impresso igual ao nmero
mximo de colunas de impresso, pelo que, a cadeia de caracteres impressa com esse
nmero de caracteres. Como a cadeia de caracteres tem um nmero de colunas inferior ao
pretendido, ento so acrescentados espaos direita ou esquerda, conforme a
justificao pedida.
Figura 1.69 - Escrita de uma cadeia de caracteres com vrios formatos.
1.8.2.3 Escrita de valores numricos
A introduo do especificador de dimenso no especificador de converso permite
caracterizar o tipo do valor que vai ser convertido, o tipo short ou o tipo long em vez do
tipo int, ou o tipo long double em vez do tipo double. Note-se o significado distinto
atribudo ao especificador de preciso, tratando-se de valores inteiros ou de valores reais.
No primeiro caso significa o nmero mnimo de algarismos da representao. No segundo
caso significa o nmero de algarismos da parte fraccionria, quando so usados os
caracteres de converso f ou e, ou o nmero mximo de algarismos significativos, que 6
por defeito, quando usado o carcter de converso g. Segundo a norma ANSI, a seleco
do estilo de representao, quando usado o carcter de converso g, baseia-se na regra
seguinte. A notao cientfica utilizada, quando o expoente resultante for inferior a -4 ou
maior ou igual preciso estabelecida. Em todos os outros casos utilizada a representao
em parte inteira, a vrgula, que nos computadores o . e a parte fraccionria.
A Figura 1.70 apresenta alguns exemplos da aplicao de vrios formatos de escrita de
valores numricos inteiros e as respectivas sequncias de caracteres escritas no monitor.
Figura 1.70 - Escrita de valores numricos inteiros com vrios formatos.
printf ("->%ld***%ld<-\n", 1095L, -1095L); /* ->1095***-1095<- */
printf ("->%+li***%+li<-\n", 1095L, -1095L); /* ->+1095***-1095<- */
printf ("->% .2ld***% .2ld<-\n", 1095L, -1095L);
/* -> 1095***-1095<- */
printf ("->%5.5hd***%5.hd<-\n", -13505, -13505);
/* ->-13505***-13505<- */
printf ("->%05u***%5u<-\n", 17, 17); /* ->00017*** 17<- */
printf ("->%-5.2x***%#-5.2x<-\n", 17, 17); /* ->11 ***0x11<- */
printf ("->%5.2ho***%#5.2o<-\n", 17, 17); /* -> 21*** 021<- */
char FRASE[30] = "pim pam pum, cada bola ...";
...
printf ("->%s<-\n", FRASE); /* ->pim pam pum, cada bola ...<- */
printf ("->%11s<-\n", FRASE); /* ->pim pam pum, cada bola ...<- */
printf ("->%-11s<-\n", FRASE); /* ->pim pam pum, cada bola ...<- */
printf ("->%.11s<-\n", FRASE); /* ->pim pam pum<- */
printf ("->%.s<-\n", FRASE); /* -><- */
printf ("->%8.8s<-\n", "pim"); /* -> pim<- */
printf ("->%-8.8s<-\n", "pim"); /* ->pim <- */
45 CAPTULO 1 : INTRODUO AO C
A Figura 1.71 apresenta alguns exemplos da aplicao de vrios formatos de escrita de
valores numricos reais e as respectivas sequncias de caracteres escritas no monitor.
Figura 1.71 - Escrita de valores numricos reais com vrios formatos.
1.9 Bibliotecas de execuo ANSI
J referimos a biblioteca de execuo ANSI stdio, que descreve as funes de acesso aos
dispositivos de entrada e de sada. Agora vamos apresentar mais algumas das bibliotecas de
execuo ANSI.
1.9.1 Biblioteca ctype
A biblioteca ctype contm um conjunto de funes para processamento de caracteres.
Tem a particularidade de as rotinas poderem ser tornadas independentes do cdigo usado
na representao dos caracteres. Para utilizar as suas funes preciso fazer a incluso do
ficheiro de interface ctype.h com a seguinte directiva do pr-processador.
#include <ctype.h>
As funes dividem-se funcionalmente em dois grupos. O grupo das funes de teste de
caracteres, que determinam se um dado carcter pertence ou no a uma dada classe. O
grupo das funes de converso, que permitem a converso de um carcter do alfabeto
maisculo num carcter do alfabeto minsculo e vice-versa.
As funes de teste de caracteres tm o seguinte mecanismo de comunicao com o
exterior.
int nome da funo (int carcter);
O valor devolvido no nulo, ou seja, verdadeiro, se o carcter em argumento pertencer
classe associada e nulo, ou seja, falso, caso contrrio. Se o valor em argumento no puder
ser representado como um unsigned int, o resultado indefinido.
As funes de converso tm o seguinte mecanismo de comunicao com o exterior.
int nome da funo (int carcter);
O valor devolvido o do carcter em argumento, se o carcter j estiver convertido, ou no
se tratar de um carcter alfabtico e o do carcter convertido, no caso contrrio.
printf ("->%f***%e<-\n", 13.595, 13.595);
/* ->13.595000***1.359500e+01<- */
printf ("->%.f***%.e<-\n", 13.595, 13.595);
/* ->14***1e+01<- */
printf ("->%#.f***%#.e<-\n", 13.595, 13.595);
/* ->14.***1.e+01<- */
printf ("->%g***%g<-\n", -13.595, -0.000011);
/* ->-13.595***-1.1e-05<- */
printf ("->%8.2f***%10.2e<-\n", -13.345, -13.345);
/* -> -13.35*** -1.33e+01<- */
printf ("->%08.2f***%010.2e<-\n", -13.345, -13.345);
/* ->-0013.35***-01.33e+01<- */
printf ("->%8.2g***%#08.2g<-\n", -13.345, -13.345);
/* -> -13***-000013.<- */
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 46
A Figura 1.72 apresenta as funes de teste de caracteres e as funes de converso.
Figura 1.72 - Funes da biblioteca ctype.
1.9.2 Biblioteca math
A biblioteca math contm um conjunto de funes matemticas. Para utilizar as suas
funes preciso fazer a incluso do ficheiro de interface math.h com a seguinte directiva
do pr-processador.
#include <math.h>
Quando da invocao destas funes podem ocorrer dois tipos de erros. Erros de domnio,
quando o valor do argumento est fora da gama permitida. Erros de contradomnio
(ERANGE), quando o valor devolvido no pode ser representado por uma varivel de tipo
double. Quando tal acontece, a varivel global errno assume o valor EDOM ou
ERANGE.
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.
Durante a compilao de um programa que referencia a biblioteca math.h, para que o
processo de ligao funcione, a biblioteca tem que ser explicitamente referenciada no
comando de compilao. Para tal, preciso acrescentar ao comando de compilao a opo
de compilao -lm, da seguinte forma.
cc programa.c -o programa ... -lm
As funes da biblioteca matemtica dividem-se funcionalmente em trs grupos: funes
trigonomtricas e hiperblicas; funes exponenciais e logartmicas; e funes
diversas.
funes de teste de caracteres
Nome da funo Classe de pertena
isalnum caracteres alfabticos e algarismos decimais
isalpha caracteres alfabticos
iscntrl caracteres de controlo
isdigit algarismos decimais
isgraph todos os caracteres com representao grfica
islower caracteres alfabticos minsculos
isprint todos os caracteres com representao grfica
mais o carcter espao
ispunct todos os caracteres com representao grfica
menos os caracteres alfabticos e os algarismos decimais
isspace espao, '\f', '\n', '\r', '\t' e '\v'
isupper caracteres alfabticos maisculos
isxdigit algarismos hexadecimais
funes de converso
Nome da funo Tipo de converso
tolower do alfabeto maisculo para o alfabeto minsculo
toupper do alfabeto minsculo para o alfabeto maisculo
47 CAPTULO 1 : INTRODUO AO C
A Figura 1.73 apresenta as funes trigonomtricas e hiperblicas.
Figura 1.73 - Funes trigonomtricas e hiperblicas.
A Figura 1.74 apresenta as funes exponenciais e logartmicas.
Figura 1.74 - Funes exponenciais e logartmicas.
A Figura 1.75 apresenta as funes diversas.
Figura 1.75 - Funes diversas.
1.9.3 Biblioteca errno
Algumas funes da biblioteca de execuo ANSI, particularmente as funes matemticas,
para alm de devolverem um valor especfico em situaes de erro, afectam uma varivel
global de tipo int, designada por errno, com um cdigo de erro. A biblioteca errno contm
a aluso varivel errno e a definio dos cdigos de erro. Para utilizar as suas funes
preciso fazer a incluso do ficheiro de interface errno.h com a seguinte directiva do pr-
processador.
#include <errno.h>
Nome da funo Significado
double ceil (double x); truncatura por excesso
double fabs (double x); |x|
double floor (double x); truncatura por defeito
double fmod (double x, double y); resto da diviso de x por y (yz0)
Nome da funo Significado
double acos (double x); x [-1, 1] arco coseno(x) [0,S]
double asin (double x); x [-1, 1] arco seno(x) [-S/2,S/2]
double atan (double x); arco tangente(x) [-S/2,S/2]
double atan2 (double y, double x); xz0 ou yz0
arco tangente(y/x) ]-S,S]
double cos (double x); coseno(x)
double sin (double x); seno(x)
double tan (double x); tangente(x)
double cosh (double x); (e
x
+e
-x
)/2
double sinh (double x); (e
x
-e
-x
)/2
double tanh (double x); (e
x
-e
-x
)/(e
x
+e
-x
)
Nome da funo Significado
double exp (double x); e
x
double frexp (double x, int *y); x = frexp (x,y) * 2*
y
, com
frexp (x,y) ]0.5,1]
double ldexp (double x, int y); x * 2
y
double log (double x); log
e
(x), com x>0
double log10 (double x); log
10
(x), com x>0
double modf (double x, double *y); x = modf (x,y) + *y, com
|modf (x,y)| [0,1[
double pow (double x, double y); x
y
, com xz0 ou y>0
e xt0 ou ento y no tem parte fraccionria
double sqrt (double x); x
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 48
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.
Figura 1.76 - Exemplo da utilizao da funo perror.
1.9.4 Biblioteca stdlib
A biblioteca stdlib contm funes de utilidade geral e define quatro tipos de dados. Para
utilizar as suas funes preciso fazer a incluso do ficheiro de interface stdlib.h com a
seguinte directiva do pr-processador.
#include <stdlib.h>
As funes dividem-se funcionalmente em vrios grupos. As funes para gerao de
sequncias pseudo-aleatrias, que permitem fazer uma aproximao gerao de uma
sequncia de valores que segue uma funo densidade de probabilidade uniformemente
distribuda. As funes para realizao de operaes inteiras elementares, como a
obteno do valor absoluto e do quociente e do resto de divises inteiras. As funes para
converso de cadeias de caracteres em quantidades numricas.
A Figura 1.77 apresenta as funes para gerao de sequncias pseudo-aleatrias.
Figura 1.77 - Funes para gerao de sequncias pseudo-aleatrias.
Nome da funo Significado
int rand (void); rand() {0, 1, ... , RAND_MAX}
void srand (unsigned int s); aps a invocao desta funo,
sucessivas invocaes de rand() produzem uma sequncia de valores
pseudo-aleatrios determinada por s, sendo s a semente de gerao
#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 ...");
...
}
...
49 CAPTULO 1 : INTRODUO AO C
A Figura 1.78 apresenta as funes para realizao de operaes inteiras elementares.
Figura 1.78 - Funes para realizao de operaes inteiras elementares.
Para as duas ltimas funes, os valores do quociente e do resto da diviso de operandos
de tipo int ou de tipo long so devolvidos conjuntamente numa varivel de tipo complexo,
div_t ou ldiv_t, assim definida para o primeiro caso.
typedef struct
{
int quot; /* quociente */
int rem; /* resto */
} div_t;
A definio do tipo ldiv_t formalmente semelhante, substitui-se apenas int por long em
todas as ocorrncias.
A Figura 1.79 apresenta as funes para converso de cadeias de caracteres em quantidades
numricas.
Figura 1.79 - Funes para converso de cadeias de caracteres em quantidades numricas.
Para alm dos tipos div_t e ldiv_t, a biblioteca stdlib define ainda o tipo size_t que o tipo
do resultado do operador sizeof. Contm tambm dois identificadores que podem ser
usados pela funo exit para indicar o estado de execuo de um programa. A constante
EXIT_FAILURE, que vale 1, pode ser usada para indicar a finalizao do programa sem
sucesso. A constante EXIT_SUCCESS, que vale 0, pode ser usada para indicar a
finalizao do programa com sucesso.
Esta biblioteca contm ainda as funes para gesto de memria, que permitem adjudicar e
libertar memria dinmica, que sero apresentadas mais tarde.
1.10 Leituras recomendadas
x 3, 4, 5 e 6 captulos do livro C A Software Approach, 3 edio, de Peter A.
Darnell e Philip E. Margolis, da editora Springer-Verlag, 1996.
Nome da funo Significado
double atof (const char *str); converte a cadeia de caracteres
apontada por str numa quantidade real de tipo double
int atoi (const char *str); converte a cadeia de caracteres
apontada por str numa quantidade inteira de tipo int
Nome da funo Significado
int abs (int x); |x|
long labs (long x); |x|
div_t idiv (int x, int y); quociente e resto da diviso
inteira de x por y
ldiv_t ldiv (long x, long y); quociente e resto da diviso
inteira de x por y
Captulo 2
COMPLEMENTOS SOBRE C
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.
De seguida, apresentamos o modo como se definem os tipos de dados estruturados,
nomeadamente, os agregados unidimensionais, bidimensionais e tridimensionais, as cadeias
de caracteres e a biblioteca string que providencia funes para a sua manipulao e os
registos, que na linguagem C se designam por estruturas. Apresentamos tambm como se
definem tipos de dados enumerados.
Devido interaco entre os agregados e os ponteiros, o que se costuma designar por
dualidade ponteiro agregado, e necessidade de implementar programas mais eficientes
recorrendo a este novo tipo de dados, vamos expondo as suas caractersticas mais
avanadas medida que explicamos os tipos de dados estruturados. Apresentamos,
designadamente, os seus operadores especficos, a aritmtica de ponteiros, a dualidade
ponteiro agregado, para agregados unidimensionais e multidimensionais e a construo de
estruturas de dados versteis envolvendo agregados e ponteiros.
Apresentamos tambm as classes de armazenamento das variveis e redefinimos os
conceitos de objectos locais e globais, bem como dos nveis de visibilidade dos objectos
aplicados ao facto da linguagem C permitir a construo de aplicaes distribudas por
vrios ficheiros fonte.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 2
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.
A transcrio da soluo numa linguagem de programao deve, depois, procurar reflectir a
hierarquia de decomposio que foi explicitada na descrio. Isto consegue-se fazendo um
uso intensivo dos mecanismos de encapsulamento de informao, presentes na
linguagem utilizada, para implementar cada operao como uma seco autnoma de
cdigo, ou seja, com um subprograma e promover, assim, uma visibilidade controlada dos
detalhes de implementao.
Desta forma, possvel:
x Separar a especificao da operao, ou seja, a descrio da sua funcionalidade interna e
do mecanismo de comunicao com o exterior, constituda pelas variveis de entrada e de
sada, da sua implementao.
x Introduzir caractersticas de robustez e de desenho para o teste no cdigo produzido.
x Validar de uma maneira controlada e integrar de um modo progressivo os diferentes
componentes da aplicao
x Planear com eficcia a distribuio de tarefas pelos diversos membros da equipa de
trabalho.
Na linguagem Pascal existem dois tipos de subprogramas. O procedimento, que se
apresenta na Figura 2.1, um mecanismo de encapsulamento de informao que permite a
construo de operaes mais complexas a partir de uma combinao de operaes mais
simples. Aps a sua definio, a nova operao identificada por um nome e por uma lista
opcional de parmetros de comunicao. Os parmetros de entrada, que representam
valores necessrios realizao da operao, e os parmetros de sada, que representam
valores produzidos pela realizao da operao.
3 CAPTULO 2 : COMPLEMENTOS SOBRE C
Figura 2.1 - Esquema de um procedimento.
A funo, que se apresenta na Figura 2.2, um mecanismo de encapsulamento de
informao que permite a descrio de um processo de clculo complexo a partir de uma
combinao de operaes mais simples. Aps a sua definio, a nova operao
identificada por um nome e por uma lista opcional de parmetros de comunicao. Os
parmetros de entrada, que representam valores necessrios realizao da operao e pela
indicao do tipo do resultado de sada, ou seja, do valor calculado e devolvido pela funo.
Figura 2.2 - Esquema de uma funo.
Na linguagem C s temos um tipo de subprograma que a funo generalizada, que se
apresenta na Figura 2.3. A funo generalizada um mecanismo de encapsulamento de
informao que combina as caractersticas apresentadas pelo procedimento e pela funo.
Semanticamente, pode ser encarada como um procedimento que devolve o estado de
realizao da operao. Pode ser visto como uma caixa preta que recebe informao
entrada e que produz informao sada, sendo que os detalhes associados com a
implementao da operao so invisveis externamente e no originam qualquer interaco
com o exterior. A informao de entrada, quando necessria realizao da operao,
fornecida funo generalizada atravs de um conjunto de parmetros de entrada. A
informao de sada, quando produzida pela realizao da operao, fornecida atravs
de um conjunto de parmetros de sada e/ou pela produo de um resultado de sada.
Figura 2.3 - Esquema de uma funo generalizada da linguagem C.
Parmetros
de
Entrada
( 0 )
Procedimento
Parmetros
de
Sada
( 0 )
Resultado
de
Sada
Funo
Parmetros
de
Entrada
( 0 )
Resultado
de Sada
Funo
Generalizada
Parmetros
de
Entrada
( 0 )
Parmetros
de
Sada
( 0 )
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.
A Figura 2.4 apresenta a definio da funo de converso de distncias de milhas para
quilmetros no Pascal e na linguagem C.
Figura 2.4 - Exemplo da funo da converso de distncias de milhas para quilmetros.
Uma funo pode aparecer num programa, de trs formas diferentes:
x A definio a declarao que define o que a funo faz, bem como o nmero e tipo
de parmetros e o tipo de resultado de sada. Normalmente colocada depois da funo
main, ou num ficheiro de implementao.
x A invocao invoca a execuo da funo.
x A aluso, ou prottipo a declarao que define a interface da funo, ou seja, o tipo, o
nome e a lista de parmetros. Normalmente colocada no incio do ficheiro fonte logo
aps as directivas de include, ou num ficheiro de interface, que tem a extenso .h.
2.1.1 Definio de uma funo
Tal como j foi mostrado para a funo main na Figura 1.3, qualquer funo na linguagem
C, supe a especificao do seu cabealho e do seu corpo. A Figura 2.5 apresenta
detalhadamente os vrios componentes da funo CONVERTE_DISTANCIA.
No cabealho, indica-se o tipo do valor devolvido, o nome da funo, e entre parnteses
curvos, a lista de parmetros de comunicao. Se a funo no tiver lista de parmetros
utilizada a declarao de void. Se a funo no tiver resultado de sada utilizada a
declarao de tipo void, como sendo o tipo do valor devolvido.
O corpo da funo delimitado pelos separadores { e }. Para devolver o valor calculado
pela funo, utiliza-se a palavra reservada return seguido da expresso a devolver. O valor
de retorno pode ser de qualquer tipo, excepto um agregado ou uma funo. possvel
devolver uma estrutura ou unio, mas no recomendado porque ineficiente. Uma
funo pode conter qualquer nmero de instrues return. Se no houver instruo de
return, a execuo da funo termina quando atingido o separador }. Nesse caso, o valor
devolvido indefinido. O valor devolvido na instruo de return deve ser compatvel em
termos de atribuio com o tipo de sada da funo. Para determinar se o valor devolvido
aceitvel, o compilador usa as mesmas regras de compatibilidade da instruo de atribuio.
function CONVERTE_DISTANCIA (ML: real): real; (* no Pascal *)
const MIL_QUI = 1.609;
begin
CONVERTE_DISTANCIA := MIL_QUI * ML
end;
int X[10];
...
INICIALIZAR (X, 10); /* invocao da funo */
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
2.3 Cadeias de caracteres ( strings )
Na linguagem C, uma cadeia de caracteres ( string ) entendida como um agregado
unidimensional de caracteres terminado obrigatoriamente pelo carcter nulo '\0', tambm
reconhecido pelo identificador NUL. A expresso de inicializao para variveis de tipo
cadeia de caracteres, pode por isso ser estabelecida alternativamente segundo as regras
anteriores de inicializao de agregados unidimensionais, tendo o cuidado de garantir que a
lista de constantes inclui em ltimo lugar o carcter nulo, ou atravs de uma constante de
tipo cadeia de caracteres, ou seja, uma sequncia de caracteres delimitada por aspas duplas,
onde o compilador coloca automaticamente no fim o carcter nulo. Quando se declara e
inicializa uma cadeia de caracteres e o descritor de dimenso maior do que o nmero de
caracteres de inicializao, ento todos os restantes caracteres da cadeia so inicializados
com o carcter nulo.
A Figura 2.18 apresenta alguns exemplos de declarao e inicializao de cadeias de
caracteres. A varivel VOGAIS no uma cadeia de caracteres porque no foi terminada
com o carcter nulo e foi declarada para armazenar apenas as cinco vogais. Pelo que,
apenas um agregado de caracteres. A varivel CIDADE tambm est declarada como um
agregado de caracteres, mas, como foi declarado com o tamanho de 10 caracteres, ento
todos os caracteres no inicializados, que so quatro, so colocados a '\0'. Pelo que, de
facto uma cadeia de caracteres. A varivel OLA uma cadeia de caracteres e est
explicitamente declarada com o carcter nulo. A varivel CARTA no uma cadeia de
caracteres, porque apesar de ser inicializada com uma constante de tipo cadeia de
caracteres, foi declarada com um tamanho insuficiente para armazenar o carcter nulo. A
varivel TEXTO est declarada sem especificao de tamanho e portanto, criado com a
dimenso exacta para conter a cadeia de caracteres de inicializao mais o carcter nulo, ou
seja, com a dimenso de 16 caracteres.
Figura 2.18 - Exemplos de declarao e inicializao de cadeias de caracteres.
A Figura 2.19 apresenta o mapa de reserva de espao em memria da declarao de duas
cadeias de caracteres. A varivel OLA foi declarada e inicializada com quatro caracteres. A
varivel TB inicializada com uma constante de 8 caracteres e o carcter nulo
automaticamente colocado no fim.
char VOGAIS[5] = {'a', 'e', 'i', 'o', 'u'};
/* VOGAIS no uma cadeia de caracteres */
char CIDADE[10] = {'A', 'v', 'e', 'i', 'r', 'o'};
/* CIDADE uma cadeia de caracteres */
char OLA[4] = {'o', 'l', 'a', '\0'};
/* OLA uma cadeia de caracteres */
char CARTA[4] = "tres";
/* CARTA no uma cadeia de caracteres */
char TEXTO[] = "era uma vez ...";
/* TEXTO uma cadeia de caracteres */
13 CAPTULO 2 : COMPLEMENTOS SOBRE C
Figura 2.19 - Mapa de reserva de espao em memria de duas cadeias de caracteres.
possvel criar um ponteiro para uma cadeia de caracteres e simultaneamente inici-lo com
uma cadeia de caracteres constante, usando para esse efeito a seguinte instruo.
char *ponteiro = "constante cadeia de caracteres"
A Figura 2.20 apresenta o mapa de reserva de espao em memria de um exemplo deste
tipo de declarao. O ponteiro PTS aponta para a cadeia de caracteres Aveiro, que uma
cadeia de caracteres constante, pelo que, no pode ser alterada. O valor de PTS pode ser
alterado, por exemplo, atribuindo-lhe outra constante, ou uma varivel, de tipo cadeia de
caracteres, e caso o seja, perde-se o acesso cadeia de caracteres Aveiro.
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.
OLA[0]
OLA[1]
OLA[2]
OLA[3]
TB[0]
char OLA[4] = {'o', 'l', 'a', '\0'};
char TB[] = "tudo bem";
TB[1]
TB[2]
TB[3]
TB[4]
TB[5]
TB[6]
TB[7]
TB[8]
'o'
'l'
'a'
'\0'
'\0'
'u'
'd'
'o'
' '
'b'
'm'
'e'
't'
char *PTS = "Aveiro";
PTS 4 bytes
'\0'
'A'
'v'
'e'
'i'
'r'
'o'
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
2.3.1 Atribuio de cadeias de caracteres
Uma cadeia de caracteres um agregado de caracteres, portanto, uma constante de tipo
cadeia de caracteres tambm um ponteiro para o seu primeiro carcter. Pelo que, quando
uma varivel de tipo cadeia de caracteres declarada com o operador [ ], no possvel
atribuir-lhe directamente uma constante de tipo cadeia de caracteres, uma vez que o nome
da varivel cadeia de caracteres um ponteiro constante, logo no pode ser alterado. Mas,
j possvel, atribuir uma constante de tipo carcter a um elemento da varivel, indicando
para esse efeito o ndice do elemento entre parnteses rectos. Para atribuir a uma varivel
de tipo cadeia de caracteres, uma constante de tipo cadeia de caracteres ou outra varivel de
tipo cadeia de caracteres, existe a funo strcpy da biblioteca de execuo ANSI string.
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.
Figura 2.21 - Exemplo da atribuio de cadeias de caracteres.
2.3.2 Cadeias de caracteres versus caracteres
Uma constante de tipo carcter colocado entre pelicas, por exemplo 'a', enquanto que
uma constante de tipo cadeia de caracteres colocada entre aspas, por exemplo "a". Existe
uma diferena importante entre um carcter e uma cadeia de caracteres, mesmo quando
esta apenas constituda por um nico carcter. Enquanto que um carcter ocupa apenas
um byte, uma cadeia de caracteres ocupa tantos bytes quantos os caracteres, mais um byte
para armazenar o carcter nulo '\0'. A Figura 2.22 apresenta alguns exemplos da utilizao
de um ponteiro para carcter. Pode-se atribuir um carcter a um dos elementos de uma
cadeia de caracteres atravs de um ponteiro para carcter, desde que a cadeia de caracteres
no seja constante, como o caso do exemplo apresentado. Portanto, a instruo de
atribuio *PTS = 'A' d erro de execuo, devido a violao de memria protegida. A
instruo de atribuio *PTS = "A" est errada, uma vez que, no se pode atribuir um
ponteiro para carcter a um carcter. preciso ter em ateno que *PTS do tipo char,
enquanto que, "A" do tipo char *. Como j foi referido, possvel atribuir uma constante
de tipo cadeia de caracteres a um ponteiro para carcter, mas no possvel atribuir-lhe um
carcter, pelo que, a instruo de atribuio PTS = 'A' est errada.
Figura 2.22 - Exemplo da manipulao de um ponteiro para carcter.
char *PTS = "aveiro"; /* PTS aponta para "aveiro" */
...
*PTS = 'A'; /* ERRO "Aveiro" uma cadeia de caracteres constante */
*PTS = "A"; /* ERRO no se pode fazer esta atribuio */
PTS = 'A'; /* ERRO no se pode fazer esta atribuio */
PTS = "A"; /* agora PTS aponta para "A" */
char CIDADE[10] = "aveiro"; /* CIDADE igual a "aveiro" */
char *PTS = "Aveiro"; /* PTS aponta para "Aveiro" */
...
CIDADE = "Lisboa"; /* ERRO no se pode fazer esta atribuio */
CIDADE[0] = 'A'; /* agora CIDADE igual a "Aveiro" */
strcpy (CIDADE, "Lisboa"); /* agora CIDADE igual a "Lisboa" */
PTS = "Lisboa"; /* agora PTS aponta para "Lisboa" */
15 CAPTULO 2 : COMPLEMENTOS SOBRE C
2.3.3 Cadeias de caracteres como parmetros de funes
A passagem de uma cadeia de caracteres a uma funo semelhante passagem de um
agregado. Existem duas maneiras para indicar que um parmetro de uma funo uma
cadeia de caracteres. A notao recomendada pela norma ANSI utiliza o operador [ ],
declarando que o parmetro um agregado de caracteres. Em alternativa pode-se utilizar o
operador *, declarando que o parmetro um ponteiro para carcter. Quando se passa uma
cadeia de caracteres a uma funo, no necessrio indicar o comprimento da cadeia de
caracteres, uma vez que este valor pode ser calculado recorrendo funo strlen da
biblioteca de execuo ANSI string. A Figura 2.23 apresenta um exemplo da definio e
invocao de uma funo que processa uma cadeia de caracteres.
Figura 2.23 - Funo com um parmetro de tipo cadeia de caracteres.
2.3.4 Codificao circular de caracteres
Na linguagem C, o tipo char permite armazenar quer um carcter quer um valor inteiro.
Devido a esta polivalncia possvel misturar numa mesma expresso caracteres e valores
numricos inteiros, sem ter a necessidade de recorrer a funes de converso de tipos. Pelo
que, a expresso para implementar a codificao circular de um carcter alfabtico
simplificada, quando comparada com o Pascal. A Figura 2.24 apresenta o deslocamento
circular do carcter z trs posies para a frente, sendo transformado no carcter c. Para
obtermos o efeito de deslocamento circular num conjunto linear, temos que utilizar o
operador resto da diviso, que na linguagem C o smbolo %.
w
x
y
. . .
z a
b
c
d
a + ( z - a + 3 ) % 26 = c
alfabeto minsculo
w
x
y
. . .
z a
b
c
d
a + ( z - a + 3 ) % 26 = c
alfabeto minsculo
Figura 2.24 - Deslocamento circular de um carcter trs posies para a frente.
int FUNC (char PST[]) /* definio da funo FUNC */
{ /* ou em alternativa int FUNC (char *PST) */
...
return ...;
}
int AMULT[L][M][N];
...
INICIALIZAR (AMULT, L, M, N); /* invocao da funo */
void INICIALIZAR (int AM[][M][N], int NE) /* definio da funo */
{
int I, J, K;
for (I = 0; I < NE; I++)
for (J = 0; J < M; J++)
for (K = 0; K < N; K++)
AM[I][J][K] = 0; /* para aceder ao elemento AM[I][J][K] */
}
int AMULT[L][M][N];
...
INICIALIZAR (AMULT, L); /* invocao da funo */
27 CAPTULO 2 : COMPLEMENTOS SOBRE C
Esta diferena apresentada no exemplo da Figura 2.45. Embora as variveis PI e PD
contenham o mesmo valor aps a execuo das instrues de atribuio, elas constituem de
facto entidades distintas. O ponteiro PI referencia a regio de memria de armazenamento
de uma varivel de tipo int, representada na figura pela rea mais escura com o tamanho de
4 bytes, enquanto que o ponteiro PD referencia a regio de memria de armazenamento de
uma varivel de tipo double, representada na figura pelas duas reas escura e clara com o
tamanho de 8 bytes.
Figura 2.45 - Exemplo da diferena entre um ponteiro e um endereo.
A declarao de variveis de tipo ponteiro ( pointer ) segue a regra geral de declarao de
variveis na linguagem C, cuja definio formal se apresenta na Figura 2.46.
Figura 2.46 - Definio formal da declarao de variveis de tipo ponteiro.
Podemos declarar um ponteiro de um qualquer tipo de dados genrico. O tipo de dados
genrico inclui os tipos de dados nativos numricos anteriormente referidos e os tipos de
dados definidos pelo utilizador que sero referidos adiante. Alm destes, inclui ainda um
tipo de dados nativo no numrico, o tipo void, que tem um significado muito especial.
Este tipo representa literalmente um tipo de dados que coisa nenhuma. Assim, no
possvel definir-se variveis deste tipo, mas unicamente ponteiros para variveis deste tipo.
Trata-se da forma encontrada pela linguagem C para caracterizar ponteiros genricos e,
portanto, o seu uso bastante restrito.
A Figura 2.47 apresenta um exemplo da utilizao de um ponteiro de tipo void. A primeira
linha de cdigo declara uma varivel A de tipo int. A segunda linha declara uma varivel V
de tipo void, o que ilegal e portanto, d erro de compilao. A terceira linha declara uma
varivel de tipo ponteiro para void, ou seja, um ponteiro genrico. A atribuio *PV = 5
uma instruo ilegal, apesar de PV estar a apontar para A, porque o ponteiro PV de tipo
void. Para colocar o valor 5 na varivel A que de tipo int, atravs do ponteiro PV,
preciso fazer um cast do ponteiro PV, como se mostra na ltima linha.
tipo de dados genrico lista de variveis de tipo ponteiro ;
tipo de dados genrico ::= qualquer tipo de dados vlido na linguagem C
lista de variveis de tipo ponteiro ::= identificador de varivel de tipo ponteiro |
lista de variveis de tipo ponteiro , identificador de varivel de tipo ponteiro
identificador de varivel de tipo ponteiro ::= * identificador de varivel genrico |
*identificador de varivel tipo ponteiro
identificador de varivel genrico ::= identificador de varivel |
identificador de varivel = expresso de inicializao
PI int *PI;
double *PD, VD;
...
PI = (int *) &VD;
PD = &VD;
PD
VD
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 28
Figura 2.47 - Exemplo da declarao e utilizao de um ponteiro de tipo void.
Como se mostra na Figura 2.48, a declarao de variveis de tipo ponteiro no precisa de
ser feita numa linha separada, pelo que, a lista de variveis pode incluir conjuntamente
variveis do tipo indicado, ponteiros para variveis do tipo indicado, ponteiros para
ponteiros para variveis do tipo indicado, e assim sucessivamente. A expresso de
inicializao para variveis de tipo ponteiro consiste alternativamente na localizao de uma
varivel, previamente declarada, do tipo para o qual aponta a varivel de tipo ponteiro,
usando o operador endereo, ou na constante NULL, que o ponteiro nulo e que
representa o valor de um ponteiro que no localiza qualquer regio de memria.
Figura 2.48 - Exemplo da declarao de variveis e de ponteiros de tipo int.
Dado que ponteiros em abstracto no fazem sentido, na atribuio de um valor a uma
varivel de tipo ponteiro obrigatrio que o tipo de dados associado varivel e
expresso seja o mesmo. As nicas excepes so a atribuio a uma varivel de tipo
ponteiro para void de uma expresso que um ponteiro para um tipo qualquer, e a
atribuio a uma varivel que um ponteiro para um tipo qualquer de uma expresso de
tipo ponteiro para void. A Figura 2.49 apresenta um exemplo demonstrativo.
Figura 2.49 - Exemplos da declarao e utilizao de ponteiros de tipos diferentes.
Na Figura 2.49 a varivel PPA foi declarada do tipo ponteiro para ponteiro para int. A
Figura 2.50 apresenta a sua visualizao grfica e a atribuio do valor 45 varivel A,
atravs de uma dupla referncia indirecta com a instruo de atribuio **PPA = 45.
Figura 2.50 - Visualizao grfica de um ponteiro para ponteiro para int.
int A, *PA = &A, **PPA = &PA;
double *PD;
void *PV, **PPV;
...
PD = PA; /* incorrecto porque so ponteiros distintos */
PD = (double *) PA; /* correcto aps converso forada */
PV = PA; /* correcto porque PV um ponteiro de tipo void */
PD = PV; /* correcto porque PV um ponteiro de tipo void */
PPV = PPA; /* incorrecto porque so ponteiros distintos */
int A, *PA = &A, **PPA = NULL;
/* A uma varivel de tipo int, PA uma varivel de tipo ponteiro
para int, inicializada com o endereo de A e PPA uma varivel de
tipo ponteiro para ponteiro para int inicializada a NULL */
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 */
A int A, *PA = &A, **PPA = &PA;
...
**PPA = 45; PA
PPA
45
29 CAPTULO 2 : COMPLEMENTOS SOBRE C
A Figura 2.51 apresenta um erro frequentemente cometido, por programadores que se
esto a iniciar na utilizao da linguagem C. Nunca se deve fazer a atribuio de um valor a
uma varivel apontado por um ponteiro, sem que este tenha sido previamente inicializado.
Por uma questo de segurana deve-se sempre inicializar um ponteiro, quanto mais no
seja com a constante NULL.
Figura 2.51 - Exemplo de m utilizao de um ponteiro devido a falta de inicializao.
2.5.1 Aritmtica de ponteiros
Como um ponteiro armazena um valor inteiro, que representa o endereo do primeiro byte
da regio de armazenamento da varivel para o qual ele aponta, sobre ele pode ser realizada
a operao aritmtica da adio de um valor inteiro. O significado atribudo operao
colocar o ponteiro a referenciar a regio de memria que se encontra localizada mais
abaixo, mais frente, se a expresso inteira for positiva, ou mais acima, mais atrs, se a
expresso inteira for negativa, e a uma distncia que medida em termos de unidades de
armazenamento de variveis do tipo associado ao ponteiro. Logo, a realizao da operao
s faz sentido para ponteiros para tipos de dados especficos, pelo que, no pode por isso
ser aplicada a expresses de tipo ponteiro para void. O ponteiro avana ou recua o nmero
de bytes equivalente ao sizeof (tipo de dados) por cada unidade adicionada. Sobre um
ponteiro, tambm se pode aplicar a operao de subtraco de um valor inteiro, cujo
significado obviamente o inverso da adio.
Tambm possvel fazer a subtraco de dois ponteiros do mesmo tipo. O significado
atribudo operao determinar a distncia entre os dois ponteiros, medida em termos de
unidades de armazenamento de variveis do tipo associado. De novo, a realizao da
operao s faz sentido para ponteiros para tipos de dados especficos, pelo que, no pode
ser aplicada a expresses de tipo ponteiro para void.
Sobre ponteiros tambm possvel fazer operaes de comparao, usando os operadores
relacionais, sendo que, os dois ponteiros tm de ser do mesmo tipo. A Figura 2.52
apresenta alguns exemplos destas operaes.
Figura 2.52 - Operaes aritmticas sobre ponteiros.
int A[8], *PA = A, N; /* PA aponta para A[0] */
...
PA++; /* agora PA aponta para A[1], ou seja PA avana 4 bytes */
PA += 4; /* agora PA aponta para A[5], ou seja PA avana 16 bytes */
PA -= 2; /* agora PA aponta para A[3], ou seja PA recua 8 bytes */
...
N = &A[3] - &A[0]; /* N = 3 */
N = &A[0] - &A[3]; /* N = -3 */
...
if (PA > &A[2]) ... /* a expreso decisria verdadeira */
int A, *PA;
...
*PA = 23; /* incorrecto porque PA no aponta para lado nenhum */
TDADOS_PESSOA PESSOA;
...
LER DADOS PESSOA (&PESSOA); /* invocao da funo */
typedef struct tnodo
{
...;
...;
struct tnodo *PNODO;
} TNODO;
39 CAPTULO 2 : COMPLEMENTOS SOBRE C
Figura 2.74 - Definio e invocao de uma funo com uma estrutura passada por valor.
2.6.6 Estruturas como resultado de sada de funes
Uma funo pode devolver uma estrutura. No entanto, mais eficiente devolver um
ponteiro para a estrutura. Mas, para isso, temos que declarar a estrutura como sendo de
durao permanente, usando para o efeito o qualificativo static. Seno, a estrutura deixa de
existir assim que a funo termina a sua execuo, uma vez que por defeito as variveis das
funes so automticas, ou seja, so alocadas quando a funo invocada e destrudas
quando a funo termina a execuo. A Figura 2.75 apresenta as duas solues para uma
funo que devolve um resultado de tipo TDATA.
Figura 2.75 - Devoluo de uma estrutura por uma funo.
2.6.7 Agregados de estruturas
Uma vez definida uma estrutura possvel declarar agregados de estruturas. A Figura 2.76
apresenta uma funo que processa um agregado de estruturas de tipo
TDADOS_PESSOA e que calcula o nmero de pessoas do sexo masculino existentes no
agregado. Nesta primeira verso, o acesso aos elementos do agregado feito atravs do
ndice do elemento pretendido.
Figura 2.76 - Exemplo de uma funo que processa um agregado de estruturas (1 verso).
int NUMERO_HOMENS (TDADOS_PESSOA GRUPO_PESSOAS[ ], int N)
{
int I, NUM = 0;
for (I = 0; I < N; I++)
if (GRUPO_PESSOAS[I].SEXO == 'M') NUM++;
return NUM;
}
TDATA FUNC_DATA_E (void)
{ /* o resultado de sada da funo de tipo TDATA */
TDATA DATA;
...
return DATA; /* retorno da estrutura */
}
TDADOS_PESSOA PESSOA;
...
ESCREVER DADOS PESSOA (PESSOA); /* invocao da funo */
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.
Figura 2.78 - Programa que invoca a funo NUMERO_HOMENS.
2.7 Tipo enumerado ( enum )
Na linguagem C tambm possvel declarar tipos de dados enumerados ( enum ), cuja
definio formal se apresenta na Figura 2.79. um tipo de dados escalar, em que se
enumera a lista de valores associados a uma varivel deste tipo. Cada valor definido como
um identificador constante no bloco que contm a definio de tipo, bloco esse que
inserido entre chavetas. Os identificadores constantes so numerados pelo compilador,
sendo atribudo ao primeiro identificador da lista o valor 0, ao segundo o valor 1, e assim
sucessivamente. Podemos no entanto, alterar esta numerao e indicar para cada
identificador um valor especfico, de forma a utilizar valores mais explanatrios. Sempre
que no se atribui um valor a um dos identificadores, o compilador atribui-lhe o valor do
identificador anterior incrementado de uma unidade.
Tal como no caso da definio de estruturas, a definio de enumerados tambm pode ser
feita usando a instruo typedef.
int NUMERO_HOMENS (TDADOS_PESSOA GRUPO_PESSOAS[ ], int N)
{
int I, NUM = 0; TDADOS_PESSOA *P = GRUPO_PESSOAS;
for (I = 0; I < N; P++, I++)
if (P->SEXO == 'M') NUM++;
return NUM;
}
int NUMERO_HOMENS (TDADOS_PESSOA [ ], int); /* aluso funo */
...
int main (void)
{
TDADOS_PESSOA PESSOAS[] = {
{"Vincent Van Gogh",'M',{30, 3, 1853}},
{"Vieira da Silva",'F',{13, 6, 1908}}
{"Amedeo Modigliani",'M',{12, 7, 1884}},
{"Claude Monet",'M',{14, 11, 1840}},
{"Georgia O'Keeffe",'F',{15, 11, 1887}}
};
int NHOMENS, NEST;
NEST = sizeof (PESSOAS) / sizeof (PESSOAS[0]);
NHOMENS = NUMERO_HOMENS (PESSOAS, NEST); /* invocao da funo */
...
return 0;
}
41 CAPTULO 2 : COMPLEMENTOS SOBRE C
Figura 2.79 - Definio formal da declarao de um tipo enumerado.
A Figura 2.80 apresenta exemplos da declarao de tipos enumerados. O primeiro exemplo
declara o enumerado t_cor e na mesma instruo a varivel COR. O segundo exemplo
define o enumerado t_dia_util e numa declarao parte variveis desse tipo.
Figura 2.80 - Exemplos da declarao de variveis de tipos enumerados.
A Figura 2.81 apresenta os mesmos exemplos, mas recorrendo ao typedef para definir
primeiro os tipos de dados enumerados T_COR e T_DIA_UTIL, ficando a declarao
das variveis para depois. Como os valores atribudos aos identificadores da lista do dia da
semana so seguidos, ento atribui-se apenas o valor inicial ao primeiro elemento da lista.
Figura 2.81 - Exemplos da definio de tipos enumerados usando o typedef.
Normalmente, as definies de tipos de dados enumerados usando o typedef, so
colocados no incio dos ficheiros fonte logo aps as directivas de include, de maneira a
tornar o tipo de dados visvel por todo o programa, ou num ficheiro de interface, que
depois aludido nos ficheiros fonte onde o tipo de dados necessrio.
O tipo de dados enumerado um tipo escalar, cujos identificadores so reconhecidos
atravs do seu valor numrico. Pelo que, possvel ler variveis deste tipo directamente do
teclado, usando o formato decimal e o valor numrico correspondente ao identificador.
Tambm possvel escrever variveis deste tipo directamente no monitor, se bem que
neste caso o valor impresso poder no ser muito esclarecedor. Tambm possvel
executar operaes aritmticas sobre variveis enumeradas, bem como, operaes de
comparao utilizando os operadores relacionais. A Figura 2.82 apresenta alguns exemplos.
typedef enum { BRANCO, AZUL, VERDE, ROSA, PRETO } T_COR;
...
T_COR COR;
n
x
N x
n
n
N
n
...
! 9
! 7
! 5
! 3
) 5 , ( seno
9 7 5 3
x x x x
x x + + =
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.
|
|
.
|
\
|
|
|
.
|
\
|
|
|
.
|
\
|
|
.
|
\
|
= ...
9 8
1
7 6
1
5 4
1
3 2
1 ) , seno(
2 2 2 2
x x x x
x N x
Esta expresso pode ser calculada de duas maneiras. Podemos calcul-la de baixo para cima
(bottom-up), ou seja, do termo P
4
para o termo P
1
, atravs de um processo iterativo, ou em
alternativa podemos calcular a expresso de cima para baixo (top-down), ou seja, do termo P
1
para o termo P
4
, 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
P
4
at ao termo P
1
, utilizando o passo iterativo dado pela seguinte expresso.
5 CAPTULO 4 : RECURSIVIDADE
i i
P
i i
x
P
1 2 2
1
2
1
u
u u u
Como estamos perante o clculo de um produto acumulado, P
i
tem de ser inicializado a
1.0, que o elemento neutro do produto. Depois do clculo repetitivo, temos que o valor
final do seno
1
) , ( seno P x N x u . 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 P
1
at ao termo P
4
, 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.
) 1 , , ( Seno
1 2 2
1 ) , , ( Seno
2
u
u u u
i N x
i i
x
i N x
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.
1 2 2
1 ) , , ( Seno
2
u u u
N N
x
N N x
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, ) 1 , , ( Seno ) , ( seno N x x N x u .
Pelo que, para implementar o clculo de forma recursiva precisamos de decompor a
soluo em duas funes, tal como se apresenta na Figura 4.6. Uma a funo recursiva
propriamente dita que implementa a expanso em srie de Taylor, e a outra a funo que
calcula o valor final e que invoca, caso seja necessrio, a funo recursiva.
Figura 4.6 - Funo recursiva que calcula a expanso em srie de Taylor da funo seno.
double TAYLOR (double X, int N, int I)
{ /* condio de paragem */
if (I == N) return 1 - X*X / (2*N * (2*N+1));
return 1 - X*X / (2*I * (2*I+1)) * TAYLOR (X, N, I+1);
}
double SENO_TAYLOR_RECURSIVO (double X, int N)
{
if (N == 1) return X;
return X * TAYLOR (X, N, 1);
}
double SENO_TAYLOR_ITERATIVO (double X, int N)
{
int I; double TAYLOR = 1.0;
for (I = N; I > 0; I--)
TAYLOR = 1 - X*X / (2*I * (2*I+1)) * TAYLOR;
return X * TAYLOR;
}
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.
4.4 Nmeros de Fibonacci
Um exemplo de clculo recursivo mais complexo do que o clculo do factorial o clculo
dos nmeros de Fibonacci, que so definidos pela seguinte relao de recorrncia e cuja
implementao recursiva se apresenta na Figura 4.7.
Em muitas situaes, mesmo para valores finais representveis em variveis inteiras, os
valores parcelares podem ficar corrompidos, uma vez que o factorial rapidamente d
overflow em aritmtica inteira. Por exemplo, duas situaes limites so o C(n,0) e o C(n,n),
em que o nmero de combinaes igual a 1 (n!/n!). Pelo que, a utilizao da definio
para calcular C(n,k) est normalmente fora de questo e neste caso nem sequer podemos
recorrer a aritmtica real, devido consequente perda de preciso. Uma forma de resolver
o problema, passa por eliminar a necessidade do clculo do factorial recorrendo a um
processo iterativo, descoberto por Blaise Pascal e que conhecido pelo Tringulo de
Pascal. Como se pode ver na Figura 4.11, cada elemento com excepo dos elementos
terminais de cada linha, ou seja C(n,0) e C(n,n), calculado atravs da soma dos valores da
linha anterior. Ou seja, C(n,k) = C(n-1,k) + C(n-1,k-1), com n>k>0. A Figura 4.11
representa de forma grfica o clculo de C(5,3) que igual a 10. Repare que existem valores
que so usados duas vezes, o que significa que tambm vo ser calculados duas vezes.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
Figura 4.11 - Tringulo de Pascal.
A funo recursiva para calcular o coeficiente binomial, que se apresenta na Figura 4.12,
invoca-se recursivamente duas vezes e tem como condies de paragem os elementos
terminais, cujo valor 1. A primeira condio de teste evita que a funo entre num
processo recursivo infinito, caso a funo seja invocada para k menor do que n.
Figura 4.12 - Funo recursiva para calcular os coeficientes binomiais.
unsigned int COMBINACOES (int N, int K)
{
if (K > N) return 0; /* invocao anormal (valor devolvido 0) */
if ((K == 0) || (K == N)) return 1; /* condies de paragem */
return COMBINACOES (N-1, K) + COMBINACOES (N-1, K-1);
}
9 CAPTULO 4 : RECURSIVIDADE
Esta implementao no entanto pouco prtica para valores de n e k elevados, antes de
mais porque, tal como no caso dos nmeros de Fibonacci, repete o clculo de certos
valores e portanto, computacionalmente ineficiente. Mas, o grande problema deste
algoritmo que devido dupla invocao recursiva a invocao tambm explode
rapidamente.
A soluo dinmica, que se apresenta na Figura 4.13, implica a utilizao de um agregado
bidimensional para armazenar os coeficientes binomiais, medida que estes so calculados.
Figura 4.13 - Funo dinmica para calcular os coeficientes binomiais.
Enquanto que a soluo recursiva calcula 2C(n,k)1 termos, a soluo dinmica calcula
aproximadamente, por defeito, NK termos. Os maiores coeficientes binomiais que se
conseguem calcular em aritmtica inteira de 32 bits so o C(35,16) e o C(35,19), que so
iguais a 4059928950.
4.6 Clculo das permutaes
Consideremos agora que se pretende imprimir todas as permutaes de um conjunto de N
caracteres. Por exemplo, se o conjunto de caracteres for {a,b,c}, ento o conjunto das
permutaes { (a,b,c), (a,c,b), (b,a,c), (b,c,a), (c,a,b), (c,b,a) }, ou seja, existem N!
permutaes. Podemos obter um algoritmo simples para gerar todas as permutaes de um
conjunto de caracteres, se construirmos um algoritmo recursivo.
Por exemplo, as permutaes do conjunto {a,b,c,d} so os quatro seguintes grupos de
permutaes: a seguido das permutaes do conjunto {b,c,d}; b seguido das permutaes
do conjunto {a,c,d}; c seguido das permutaes do conjunto {b,a,d}; e d seguido das
permutaes do conjunto {b,c,a}.
ento possvel resolver o problema para N caracteres, se tivermos um algoritmo
recursivo que funcione para N1 caracteres. Em cada passo do processo coloca-se um
carcter esquerda do conjunto e calculam-se recursivamente as permutaes dos N1
restantes caracteres direita. Depois troca-se sucessivamente o carcter da esquerda com
um dos caracteres da direita, de forma a ter experimentado todos os N grupos possveis de
permutaes dos N1 caracteres. Durante o processo recursivo, quando se tiver atingido o
carcter mais direita, imprime-se a sequncia de permutaes acabada de gerar.
unsigned int COMBINACOES (int N, int K)
{
int I, J, MIN; unsigned int BICOEF[36][36];
if (K > N) return 0; /* invocao anormal (valor devolvido 0) */
if ((K == 0) || (K == N)) return 1; /* C(n,0) = C(n,n) = 1 */
for (I = 0; I <= N; I++)
{
MIN = (I <= K) ? I : K; /* mnimo de I e K */
for (J = 0; J <= MIN; J++)
if ( (J == 0) || (J == I) ) BICOEF[I][J] = 1;
else BICOEF[I][J] = BICOEF[I-1][J-1] + BICOEF[I-1][J];
}
return BICOEF[N][K];
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10
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.
Figura 4.14 - Funo recursiva que gera as permutaes de um conjunto de caracteres.
A Figura 4.15 mostra a utilizao da funo. A invocao inicial da funo
PERMUTACOES (LISTA, 0, N1);, para permutar todos os caracteres que se
encontram na cadeia de caracteres LISTA, sendo que o primeiro carcter est na posio 0
e o ltimo carcter est na posio N1.
Figura 4.15 - Programa que utiliza a funo que gera as permutaes.
#include <stdio.h>
#include <string.h>
void PERMUTACOES (char [], unsigned int, unsigned int);
int main (void)
{
char LISTA[11]; /* cadeia de caracteres LISTA */
unsigned int K; /* nmero de caracteres de LISTA */
printf ("Caracteres a permutar -> ");
scanf ("%10s", LISTA); /* leitura da cadeia de caracteres LISTA */
K = strlen (LISTA); /* determinao do nmero de caracteres */
printf ("Permutaes\n\n");
PERMUTACOES (LISTA, 0, K-1);
return 0;
}
void TROCA (char *CAR_I, char *CAR_J)
...
void PERMUTACOES (char LISTA[], unsigned int I, unsigned int N)
...
void TROCA (char *CAR_I, char *CAR_J)
{
char TEMP;
TEMP = *CAR_I; *CAR_I = *CAR_J; *CAR_J = TEMP;
}
void PERMUTACOES (char LISTA[], int I, int N)
{
int J;
if (I == N) /* condio de paragem */
printf ("%s\n", LISTA[J]); /* imprimir a permutao gerada */
else for (J = I; J <= N; J++) /* para todos os caracteres */
{
TROCA (&LISTA[I], &LISTA[J]); /* por o carcter direita */
PERMUTACOES (LISTA, I+1, N);/* permutar os n-1 caracteres */
TROCA (&LISTA[J], &LISTA[I]);/* repor o carcter no stio */
}
}
11 CAPTULO 4 : RECURSIVIDADE
4.7 Clculo do determinante de uma matriz quadrada
O clculo do determinante de uma matriz com dimenso elevada normalmente
complexo, a no ser que a matriz seja uma matriz diagonal. Pelo que, uma maneira de
simplificar o clculo consiste em transformar a matriz, numa matriz diagonal. Esta
transformao da matriz pode ser feita recursivamente, atravs de operaes de adio e
subtraco de colunas da matriz, operaes essas que no afectam o valor do seu
determinante. A Figura 4.16 apresenta o algoritmo em pseudocdigo e linguagem natural
do clculo recursivo do determinante de uma matriz quadrada de dimenso N.
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.
Figura 4.16 - Algoritmo do clculo recursivo do determinante.
A Figura 4.17 apresenta a funo e a Figura 4.18 apresenta a aplicao da funo sobre uma
matriz quadrada de 44 e as sucessivas matrizes, equivalentes para efeito do clculo do
determinante, que vo sendo obtidas aps cada invocao recursiva. Quando se atinge a
matriz de um s elemento, a matriz original foi completamente transformada numa matriz
diagonal. Agora o determinante calculado no retorno das sucessivas invocaes da funo
recursiva e igual ao produto dos elementos na diagonal. Logo, o valor do determinante
2.0 -1.0 -1.5 5.0, ou seja, igual a 15.
nome: Calculo do determinante (MATRIZ, N) Funo
begin
if (N = 1) then Calculo do determinante := MATRIZ[1,1]
else begin
if (Matriz[N,N] = 0)
then if existe coluna com ltimo elemento diferente de zero?
then Trocar essa coluna com a ltima
e multiplicar o determinante por -1
else Calculo do determinante := 0;
if (Matriz[N,N] <> 0)
then begin
Dividir a ltima coluna pelo ltimo elemento pondo-o
em evidncia;
Subtrair todas as colunas menos a ltima,
pela ltima coluna multiplicada pelo ltimo elemento
da coluna a processar;
Calculo do determinante := Matriz[N,N]
* Calculo do determinante (MATRIZ, N-1);
end
end
end
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
Figura 4.17 - Funo recursiva que calcula o determinante de uma matriz quadrada.
=>
3.0 4.0 2.0 5.0
4.0 2.0 2.0 1.0
1.0 3.0 2.0 2.0
0.0 5.0 3.0 2.0
3.0 -8.5 -5.5 0.0
4.0 -0.5 0.5 0.0
1.0 -2.0 -1.0 0.0
0.0 0.0 0.0 2.0
=>
-2.5 2.5 0.0 0.0
4.5 -1.5 0.0 0.0
0.0 0.0 -1.0 0.0
0.0 0.0 0.0 2.0
5.0 0.0 0.0 0.0
0.0 -1.5 0.0 0.0
0.0 0.0 -1.0 0.0
0.0 0.0 0.0 2.0
Figura 4.18 - Execuo da funo recursiva que calcula o determinante de uma matriz quadrada.
#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;
}
}
13 CAPTULO 4 : RECURSIVIDADE
4.8 Torres de Hani
Vamos agora apresentar um problema cuja nica soluo conhecida a soluo recursiva.
As Torres de Hani o puzzle que se apresenta na primeira linha da Figura 4.19. Temos
um conjunto de discos todos de tamanho diferente enfiados na Torre A. Pretende-se
mud-los para a Torre B, mas, s se pode mudar um disco de cada vez, s se pode mudar o
disco de cima e nunca se pode colocar um disco sobre outro mais pequeno. Para
efectuarmos a mudana podemos usar a Torre C como torre auxiliar.
Torre B Torre C
Torre B
Torre A
Torre A Torre C
Torre A
Torre C Torre A
Torre B
Torre B
Torre C
Figura 4.19 - Torres de Hani.
A soluo para este problema trivial caso o nmero de discos seja um. Nesse caso basta
mud-lo da Torre A para a Torre B. No entanto se tivermos mais do que um disco a
soluo no bvia e at bastante dispendiosa medida que o nmero de discos
aumenta. A soluo que se apresenta na Figura 4.20 a soluo recursiva que resolve o
puzzle para o caso de N discos colocados na Torre A. Consiste em diminuir a
complexidade do problema at situao em que temos apenas um disco e para o qual
conhecemos a soluo.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
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.
Figura 4.20 - Algoritmo das Torres de Hani.
Obviamente que esta soluo funciona se for aplicada recursivamente, porque, resolver o
problema de mudar os trs primeiros discos da Torre A para a Torre C, resolve-se da
mesma forma, s que agora a torre de chegada a Torre C, a torre auxiliar a Torre B e o
nmero de discos a mudar menos um do que no problema inicial. A soluo passa por
mudar os dois discos de cima da Torre A para a Torre B, depois mudar o terceiro disco da
Torre A para a Torre C e finalmente mudar os dois discos que se encontram na Torre B
para a Torre C. Assim estamos perante o problema de mudar dois discos da Torre A para a
Torre B, o que implica mudar o primeiro disco da Torre A para a Torre C, o segundo disco
da Torre A para a Torre B e finalmente o primeiro disco da 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.
A funo INICIALIZAR coloca os N discos na Torre A e nenhum disco na Torre B e na
Torre C. A funo MUDAR_DISCOS implementa o algoritmo recursivo e aps cada
mudana de um disco invoca a funo IMPRIMIR. A funo IMPRIMIR vai imprimir no
monitor o estado das torres, no incio e aps cada mudana de um disco. A funo no tem
parmetros, porque utiliza variveis globais. Uma vez que o algoritmo recursivo, em cada
nova invocao as torres vo mudando de posio, pelo que, a nica forma de poder
imprimir o estado das torres A, B e C, usando variveis globais para as torres e para os
contadores do nmero de discos armazenados em cada torre. Para declarar variveis
globais, elas tm de ser declaradas antes da funo main com o qualificativo static.
nome: Torres de Hanoi (N, TORRE_A, TORRE_B, TORRE_C) Procedimento
begin
if (N = 1)
then Mover o disco da TORRE_A para a TORRE_B
else begin
Torres de Hanoi (N-1, TORRE_A, TORRE_C, TORRE_B);
Torres de Hanoi (1, TORRE_A, TORRE_B, TORRE_C);
Torres de Hanoi (N-1, TORRE_C, TORRE_B, TORRE_A);
end
end
15 CAPTULO 4 : RECURSIVIDADE
Figura 4.21 - Programa das Torres de Hani (1 parte).
#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 */
*NDA = ND; *NDB = 0; *NDC = 0;
}
void IMPRIMIR (void)
{
int I, CMAX = NDA;
if (NDB > CMAX) CMAX = NDB;
if (NDC > CMAX) CMAX = NDC;
for (I = CMAX; I > 0; I--)
{
if (NDA >= I) printf ("%10d", TORREA[I-1]);
else printf ("%10c", ' ');
if (NDB >= I) printf ("%10d", TORREB[I-1]);
else printf ("%10c", ' ');
if (NDC >= I) printf ("%10d", TORREC[I-1]);
else printf ("%10c", ' ');
printf ("\n");
}
printf ("---------------------------------\n"); scanf ("%*c");
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 16
Figura 4.22 - Programa das Torres de Hani (2 parte).
O nmero de movimentos necessrios para mudar N discos igual a 2
N
1. Pelo que, o
nmero de invocaes da funo recursiva aumenta exponencialmente com o aumento do
nmero de discos. Por isso, no se deve invocar o programa para um nmero de discos
maior do que 10. A Figura 4.23 apresenta a execuo do programa para trs discos.
Figura 4.23 - Execuo do programa Torres de Hani para 3 discos.
---------------------------------
| 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
---------------------------------
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);
}
}
17 CAPTULO 4 : RECURSIVIDADE
4.9 Questes sobre a eficincia das solues recursivas
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, existem algoritmos recursivos que so
computacionalmente ineficientes, porque calculam certos valores repetidamente devido s
mltiplas invocaes recursivas. No caso dos algoritmos em que a invocao recursiva
explode rapidamente, existe ainda o perigo de a memria de tipo pilha esgotar e do
programa terminar a sua execuo abruptamente. Portanto, o programador deve ter sempre
em conta estas ineficincias e limitaes, antes de optar por uma soluo recursiva em
detrimento de uma soluo iterativa, caso ela exista.
Os algoritmos recursivos so apropriados para resolver problemas que so normalmente
definidos de forma recursiva, ou seja, problemas que so por natureza recursivos. Por
vezes, existem problemas que tm solues recursivas que so simples, concisas, elegantes
e para os quais difcil esboar solues iterativas com a mesma simplicidade e clareza.
Mas, tal como vimos nos exemplos apresentados, alguns algoritmos recursivos so menos
eficientes que os algoritmos iterativos equivalentes. Pelo que, a verdadeira importncia da
recursividade consiste em resolver esses problemas para os quais no existem solues
iterativas simples.
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.
! 2
1 ) , coseno(
2 1
0
n
x
N x
n
n
N
n
u
...
! 8
! 6
! 4
! 2
1 ) 5 , ( coseno
8 6 4 2
x x x x
x
Desenhe a tabela com o formato que a seguir se apresenta.
----------------------------------------------------------------------
| x | coseno(x,5) | coseno(x,10) | coseno(x,15) | cos(x) |
----------------------------------------------------------------------
| ##.#### | #.######### | #.######### | #.######### | #.######### |
----------------------------------------------------------------------
...
----------------------------------------------------------------------
| ##.#### | #.######### | #.######### | #.######### | #.######### |
----------------------------------------------------------------------
Faa duas verses do programa. Na primeira verso, implemente a expanso da srie de
Taylor da funo co-seno com uma funo iterativa, e na segunda verso com uma funo
recursiva.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 18
2. Pretende-se escrever um programa que escreva no monitor as permutaes da cadeia
de caracteres composta pelos caracteres numricos de 0 a 9. Altere o programa para
escrever apenas as permutaes que so compostas alternadamente por caracteres
numricos pares e mpares.
3. Pretende-se escrever um programa que l do teclado dois nmeros inteiros positivos,
que calcula e imprime no monitor o seu mximo divisor comum. O mximo divisor
comum de dois nmeros inteiros positivos pode ser calculado de forma repetitiva,
utilizando para o efeito o mtodo de Euclides, cujo algoritmo dado pela seguinte
expresso.
! !
p
e
s
q
u
i
s
a
p
o
r
d
i
s
p
e
r
s
o
CAM[N-2]
Ponteiro
.
.
.
.
.
.
CAM_In (CHAVE)
CAM_Out (CHAVE)
Figura 5.22 - Implementao da memria associativa com tabela de disperso.
Uma tabela de disperso um agregado, onde os elementos so colocados em posies
determinadas por uma funo de disperso (hashing function ). Uma funo de disperso
uma funo aritmtica que determina a posio de colocao do elemento, usando a chave
do elemento. Portanto, os elementos no so colocados em posies seguidas do agregado,
mas sim em posies que dependem da chave do elemento.
Uma vez que o nmero de posies de armazenamento, ou seja, a dimenso do agregado
tipicamente vrias ordens de grandeza menor do que o nmero total de chaves possveis,
podem ocorrer as situaes de overflow e coliso. A situao de overflow acontece quando a
tabela fica completamente preenchida e como o agregado uma estrutura esttica no tem
soluo, a no ser com um bom dimensionamento do agregado.
25 CAPTULO 5 : MEMRIAS
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.
Esta implementao semiesttica, sob o ponto de vista da estrutura de suporte, mas
dinmica sob o ponto de vista da colocao e remoo dos elementos. Permite resolver o
problema das colises e permite ainda a existncia na memria associativa de elementos
distintos com a mesma chave. Elementos com a mesma chave vo ficar na mesma posio
da tabela e podem ser colocados na lista ligada por ordem cronolgica da sua colocao na
memria associativa, se por exemplo, a lista for implementada como uma fila.
Para pesquisar a existncia de um elemento nesta implementao da memria associativa,
seja para colocar um novo elemento, seja para retirar um elemento j existente, aplica-se a
funo de disperso para a chave do elemento e obtm-se a posio da tabela onde o
elemento deve estar colocado. Depois utiliza-se a pesquisa sequencial para analisar a lista
ligada de elementos at detectar a posio de colocao ou remoo do elemento com a
chave pretendida. Como o nmero de elementos existente na lista ligada pequena, a
pesquisa sequencial aceitvel. Por outro lado, nesta implementao a leitura sucessiva de
elementos com a mesma chave aplicada facilmente.
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.
P
R
O
G
R
A
M
A
O
E
S
T
R
U
T
U
R
A
S
D
E
D
A
D
O
S
E
A
L
G
O
R
I
T
M
O
S
E
M
C
2
6
CAM dinmica hierrquica raiz
da CAM
CAM_In (CHAVE)
CAM_Out (CHAVE)
PtEle
CHAVE 4
Elemento
PtEsq PtDir
PtEle
CHAVE 6
Elemento
PtEsq PtDir
PtEle
CHAVE 2
Elemento
PtEsq PtDir
PtEle
CHAVE 8
Elemento
PtEsq PtDir
p
e
s
q
u
i
s
a
b
i
n
r
i
a
PtEle
CHAVE 3
Elemento
PtEsq PtDir
PtEle
CHAVE 1
Elemento
PtEsq PtDir
F
i
g
u
r
a
5
.
2
3
-
I
m
p
l
e
m
e
n
t
a
o
d
i
n
m
i
c
a
h
i
e
r
r
q
u
i
c
a
d
a
m
e
m
r
i
a
a
s
s
o
c
i
a
t
i
v
a
.
27 CAPTULO 5 : MEMRIAS
Uma rvore binria diz-se completa, ou completamente balanceada, quando, para um
nmero de nveis previamente fixado, contm o nmero mximo de ns, pelo que, se
verifica que N = 2
K
1, em que N o nmero de ns e K o nmero de nveis da rvore. Se
uma rvore estiver balanceada, ento tambm as suas subrvores da esquerda e da direita o
estaro. Por definio, considera-se que uma rvore vazia, ou seja, uma rvore sem
qualquer n, est balanceada.
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 log
2
N. 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.
5.10 Atribuio dinmica de memria
Para permitir a construo de estruturas de dados semiestticas e dinmicas, a linguagem C
permite a atribuio dinmica de memria durante a execuo do programa. A biblioteca
de execuo ANSI stdlib providencia as quatro funes que se apresentam na Figura 5.24.
Figura 5.24 - Funes para gesto da memria dinmica.
A Figura 5.25 apresenta um exemplo de atribuio dinmica de memria. Pretende-se
escrever um programa que leia um ficheiro de texto constitudo por nmeros inteiros, um
por linha, para os ordenar e imprimir no monitor. Vamos admitir, que o nmero de
nmeros inteiros armazenados no ficheiro depende de ficheiro para ficheiro e se encontra
armazenado na primeira linha do ficheiro, que se comporta como cabealho do ficheiro.
Uma vez que, a dimenso do agregado dependente do tamanho do ficheiro, no podemos
declarar o agregado de forma esttica, sob pena de o dimensionarmos com tamanho
insuficiente. A no ser que se dimensione o agregado por excesso, prevendo o pior caso.
Normalmente, no possvel implementar esta estratgia, uma vez que, na maioria das
aplicaes praticamente impossvel prever o pior caso. Nem to pouco desejvel,
porque gera um desperdcio de memria na maioria dos casos.
Nome da funo Significado
void *malloc (size_t sz); atribui sz bytes na
memria. Devolve um ponteiro para o incio do bloco atribudo ou
NULL no caso contrrio
void *calloc (size_t nelem, size_t sz); atribui um espao de
memria contguo de nelem objectos de sz bytes cada. Todos os bits
do espao de memria atribudo so inicializados a zero. Devolve um
ponteiro para o incio do bloco atribudo ou NULL no caso contrrio
void *realloc (void *ptr, size_t nsz); altera o tamanho do espao
de memria atribudo anteriormente e apontado por ptr para nsz
bytes. Devolve um ponteiro para o incio do bloco atribudo ou NULL
no caso contrrio
void free (void *ptr); liberta o espao de memria
previamente atribudo pelas funes anteriores e apontado por ptr
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 28
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.
Figura 5.25 - Exemplo de aplicao da funo calloc.
5.11 Leituras recomendadas
x 4 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A. Standish, da editora Addison-Wesley Publishing Company, 1995.
x 8 captulo do livro C A Software Approach, 3 edio, de Peter A. Darnell e Philip E.
Margolis, da editora Springer-Verlag, 1996.
#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;
}
Captulo 6
PESQUISA E ORDENAO
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.
Finalmente, explicamos como generalizar os algoritmos de ordenao e como contabilizar
as instrues de comparao e de atribuies de elementos, de modo a poder fazer testes
de desempenho dos algoritmos.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 2
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.
A maneira mais simples de pesquisar um agregado a pesquisa sequencial
( sequencial search ), tambm chamada de pesquisa linear. Consiste em analisar todos os
elementos do agregado de maneira metdica. A pesquisa comea no elemento inicial do
agregado e avana elemento a elemento at encontrar o valor procurado, ou at atingir o
elemento final do agregado. Este mtodo de pesquisa normalmente demorado,
dependente do tamanho do agregado, mas, no depende do arranjo interno dos elementos
no agregado. Independentemente da forma como a informao est armazenada no
agregado, o valor procurado ser encontrado, caso exista no agregado.
No entanto, se tivermos informao priori sobre os elementos do agregado possvel
acelerar a pesquisa. Se por exemplo, os elementos estiverem ordenados por ordem
crescente ou decrescente, ento possvel fazer uma pesquisa binria ( binary search ),
tambm chamada de pesquisa logartmica. Este tipo de pesquisa comea por seleccionar
o elemento central do agregado e compara-o com o valor procurado. Se o elemento
escolhido for menor ento podemos excluir a primeira metade do agregado e analisamos
apenas a segunda metade. Caso contrrio, ento podemos excluir a segunda metade do
agregado e analisamos apenas a primeira metade. Em cada passo da pesquisa, o nmero de
elementos do agregado que tm de ser analisados reduzido a metade, pelo que, este
mtodo de pesquisa mais eficiente. O processo repetido at que o elemento
seleccionado seja o valor procurado, ou at que o nmero de elementos que tm de ser
3 CAPTULO 6 : PESQUISA E ORDENAO
analisados seja reduzido a zero, o que significa, que o valor procurado no existe no
agregado.
Se os elementos do agregado, em vez de estarem colocados em posies sucessivas do
agregado, estiverem colocados em posies predeterminadas do agregado, posies essas
que so determinadas por uma funo de disperso ( hashing function ), ento possvel fazer
uma pesquisa por tabela ( table search ), tambm chamada de pesquisa por disperso
( hashing ). Uma funo de disperso uma funo aritmtica que determina a posio de
colocao do elemento no agregado, tendo em considerao o valor do elemento, ou no
caso de um elemento estruturado, usando um campo do elemento como chave de
colocao no agregado. No entanto, normalmente uma funo de disperso produz
situaes de coliso. A situao de coliso acontece quando dados dois valores diferentes, a
funo de disperso calcula a mesma a posio de colocao. Portanto, o agregado tem de
permitir colocar mais do que um elemento numa mesma posio. 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 um agregado de listas
ligadas. Este modelo de estrutura de dados, que se designa por uma tabela de disperso
com encadeamento de elementos, pesquisado em dois tempos. Para procurar um
elemento, primeiro usada a funo de disperso para, dado o valor do elemento desejado,
calcular a sua posio de colocao no agregado e aceder directamente a essa posio.
Depois utiliza-se a pesquisa sequencial para analisar a lista ligada de elementos at detectar
o elemento com o valor pretendido. Este mtodo de pesquisa ser abordado mais tarde a
propsito da implementao de memrias associativas.
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.
Tendo em considerao este agregado de elementos inteiros, vamos analisar vrios
algoritmos que utilizam a pesquisa sequencial e o algoritmo de pesquisa binria nas suas
verses iterativa e recursiva. Vamos considerar que as funes s so invocadas para
agregados que contm de facto elementos com informao, ou seja, nelem maior do que
zero, pelo que, os algoritmos podem ser simplificados, uma vez que no tm que se
preocupar com esta situao anmala.
6.1.1 Pesquisa Sequencial
Com o objectivo de tornar as funes, o mais versteis possveis, elas tm uma varivel de
entrada que indica o ndice do agregado onde deve comear a pesquisa. As funes
calculam e devolvem o ndice do elemento onde est armazenado o valor procurado.
6.1.1.1 Procurar o maior valor de um agregado
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
Figura 6.1 - Funo que determina o elemento de maior valor do agregado.
6.1.1.2 Procurar o menor valor de um agregado
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.
Figura 6.2 - Funo que determina o elemento de menor valor do agregado.
6.1.1.3 Procurar um valor no 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.
Figura 6.3 - Funo de pesquisa sequencial que procura um valor no agregado.
int ProcurarValor (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
unsigned int indactual;
for (indactual = inicio; indactual < nelem; indactual++)
if (seq[indactual] == valor) return indactual;
return -1;
}
unsigned int ProcurarMenor (int seq[], unsigned int nelem,\
unsigned int inicio)
{
unsigned int indmenor = inicio, indactual;
for (indactual = inicio+1; indactual < nelem; indactual++)
if (seq[indactual] < seq[indmenor]) indmenor = indactual;
return indmenor;
}
unsigned int ProcurarMaior (int seq[], unsigned int nelem,\
unsigned int inicio)
{
unsigned int indmaior = inicio, indactual;
for (indactual = inicio+1; indactual < nelem; indactual++)
if (seq[indactual] > seq[indmaior]) indmaior = indactual;
return indmaior;
}
5 CAPTULO 6 : PESQUISA E ORDENAO
6.1.1.4 Procurar o primeiro que serve
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.
6.1.1.5 Procurar o melhor que serve
A segunda estratgia de optimizao consiste em procurar no agregado, o valor mais
prximo do valor pretendido, ou seja, o melhor valor. Este algoritmo designa-se por o
melhor que serve ( best fit ). A Figura 6.5 apresenta a funo que determina, a partir de um
ndice inicial de pesquisa, o ndice do elemento do agregado com o melhor 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 melhor, ou
seja, um elemento com um valor que seja ainda maior do que o j encontrado e que no
exceda o valor procurado. Se pretendermos pesquisar todo o agregado, basta invocar a
funo a partir do primeiro elemento do agregado.
Figura 6.5 - Funo que determina o elemento do agregado com o melhor valor que serve.
int ProcurarMelhor (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
int indmelhor; unsigned int indactual;
indmelhor = ProcurarPrimeiro (seq, nelem, inicio, valor);
if (indmelhor == -1 ) return -1;
for (indactual = indmelhor+1; indactual < nelem; indactual++)
if (seq[indactual] > seq[indmelhor] && seq[indactual] <= valor)
indmelhor = indactual;
return indmelhor;
}
int ProcurarPrimeiro (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
unsigned int indactual;
for (indactual = inicio; indactual < nelem; indactual++)
if (seq[indactual] <= valor) return indactual;
return -1;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6
6.1.1.6 Procurar o pior que serve
A terceira estratgia de optimizao consiste em procurar no agregado, o valor menos
prximo do valor pretendido, ou seja, o pior valor. Este algoritmo designa-se por o pior
que serve ( worst fit ). Por vezes, uma estratgia pela negativa melhor do que pela positiva.
Imagine-se por exemplo, que se pretende aproveitar uma sobra de 100 cm de uma calha de
alumnio e que temos a necessidade de cortar seces de 80 cm, 50 cm e 40 cm. Uma
estratgia o melhor que serve escolher o valor 80 cm, desperdiando 20 cm, enquanto que
uma estratgia o pior que serve escolher sucessivamente os valores 40 cm e 50 cm,
desperdiando apenas 10 cm.
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.
6.1.1.7 Exemplificao dos algoritmos de pesquisa sequencial
A Figura 6.7 apresenta a aplicao destes algoritmos num agregado com vinte elementos
teis. Se pesquisarmos o agregado a comear no elemento de ndice 7, temos que, o maior
valor o elemento com ndice 19, cujo valor 250, e o menor valor o elemento com
ndice 11, cujo valor 1. Se procurarmos um elemento no agregado com valor 55, a
comear no elemento de ndice 7, encontramos o elemento com ndice 16. Mas, se
procurarmos um elemento com valor 40, no conseguimos encontrar nenhum valor, pelo
que, a funo devolve o ndice 1.
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.
int ProcurarPior (int seq[], unsigned int nelem,\
unsigned int inicio, int valor)
{
int indpior; unsigned int indactual;
indpior = ProcurarPrimeiro (seq, nelem, inicio, valor);
if (indpior == -1 ) return -1;
for (indactual = indpior+1; indactual < nelem; indactual++)
if (seq[indactual] < seq[indpior]) indpior = indactual;
return indpior;
}
7 CAPTULO 6 : PESQUISA E ORDENAO
20 3 330 25 22 24 15 32 42 2 5 1 8 7 6 39 55 145 209 250
Posio
Inicial de
Pesquisa
Maior
Valor
Menor
Valor
Valor
55
Primeiro
Valor<=40
Melhor
Valor<=40
Pior
Valor<=40
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Figura 6.7 - Resultados da utilizao dos algoritmos de pesquisa sequencial.
6.1.2 Pesquisa Binria
Uma forma de acelerar a pesquisa consiste em utilizar uma estratgia de partio sucessiva
do agregado ao meio para diminuir o nmero de elementos a analisar. Mas, este mtodo de
pesquisa s funciona se os elementos estiverem ordenados. A Figura 6.8 apresenta a funo
de pesquisa binria, na sua verso iterativa, que calcula o ndice do elemento com o valor
procurado. Como no temos a certeza que o valor procurado existe de facto no agregado, a
funo devolve o ndice 1 como sinal de pesquisa falhada. Com o objectivo de tornar a
funo o mais verstil possvel, a funo tem duas variveis de entrada que indicam os
ndices dos elementos onde deve comear e acabar a pesquisa.
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
int ProcuraBinariaIterativa (int seq[], unsigned int inicio,\
unsigned int fim, int valor)
{
unsigned int minimo = inicio, maximo = fim, medio;
while (minimo <= maximo)
{
medio = (minimo + maximo) / 2; /* clculo da posio mdia */
if (seq[medio] == valor) return medio; /* pesquisa com sucesso */
/* actualizao dos limites do intervalo de pesquisa */
if (seq[medio] > valor) maximo = medio - 1;
else minimo = medio + 1;
return -1; /* pesquisa falhada */
}
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.
3 7 20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250
MIN
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
MAX MED
3 7 20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
MIN MED MAX
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
3 7 20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250
MIN MED MAX
o valor 34 foi encontrado ao
fim de 4 tentativas
o valor 40 no foi encontrado
ao fim de 5 tentativas
3 7 20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
MED
MIN MAX
MAX MIN
3 7 20 25 26 28 30 34 42 44 50 60 68 75 86 99 125 145 209 250
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
MED
Figura 6.9 - Utilizao do algoritmo de pesquisa binria num agregado ordenado.
9 CAPTULO 6 : PESQUISA E ORDENAO
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.
int ProcuraBinariaRecursiva (int seq[], unsigned int inicio,\
unsigned int fim, int valor)
{
unsigned int medio;
/* condio de paragem no caso de pesquisa sem sucesso */
if (inicio > fim) return -1;
medio = (inicio + fim) / 2; /* clculo da posio mdia */
/* condio de paragem no caso de pesquisa com sucesso */
if (seq[medio] == valor) return medio;
if (seq[medio] > valor)
ProcuraBinariaRecursiva (seq, inicio, medio-1, valor);
/* invocao recursiva para a primeira metade do agregado */
else ProcuraBinariaRecursiva (seq, medio+1, fim, valor);
/* invocao recursiva para a segunda metade do agregado */
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10
6.1.3 Comparao entre as pesquisas sequencial e binria
Dada a dificuldade em calcular com exactido a eficincia de um algoritmo, para exprimir a
sua eficincia em funo do nmero de elementos processados, utiliza-se a notao
matemtica conhecida por ordem de magnitude ou notao O maisculo (Big O Notation).
A ordem de magnitude de uma funo igual ordem do seu termo que cresce mais
rapidamente. Por exemplo, a funo f(n) = n
2
+n de ordem de magnitude O(n
2
), uma vez
que para grandes valores de n, o termo n
2
cresce mais rapidamente que o termo n e
portanto, domina a funo.
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 log
2
(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 log
2
(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
log
2
(N+1) elementos. Portanto, a eficincia do algoritmo de pesquisa binria de
ordem O(log
2
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.
Os algoritmos de ordenao so classificados em dois tipos. A ordenao de informao
armazenada em agregados designa-se por ordenao interna. Enquanto que, a ordenao
de informao armazenada em ficheiros, designa-se por ordenao externa. Neste
captulo vamos apresentar alguns algoritmos de ordenao interna, se bem que alguns
tambm possam ser utilizados para ordenao externa.
Existem algoritmos de ordenao muito eficientes para ordenar agregados de grandes
dimenses, mas so normalmente complexos. Pelo contrrio, os algoritmos de ordenao
que vamos apresentar so simples, mas apropriados para mostrar as caractersticas dos
princpios de ordenao. So normalmente pouco eficientes, mas so suficientemente
rpidos para agregados de pequenas dimenses. Os algoritmos de ordenao que ordenam
os elementos do agregado, no prprio agregado, fazendo para o efeito um rearranjo interno
dos seus elementos, enquadram-se numa das trs seguintes categorias: ordenao por
seleco; ordenao por troca; e ordenao por insero.
11 CAPTULO 6 : PESQUISA E ORDENAO
Para implementar os algoritmos de ordenao baseados em trocas de elementos vamos
utilizar a funo Swap que se apresenta na Figura 6.11. A troca de dois elementos do
agregado exige uma varivel temporria do mesmo tipo dos elementos do agregado e custa
trs instrues de atribuio. Os elementos a trocar so parmetros de entrada-sada, pelo
que, so passados por referncia.
Figura 6.11 - Funo para trocar dois elementos de um agregado de inteiros.
6.2.1 Ordenao por seleco
Os algoritmos de ordenao por seleco utilizam uma pesquisa sequencial. Fazem
passagens sucessivas sobre todos os elementos de uma parte do agregado e em cada
passagem seleccionam o elemento de menor valor de todos os elementos analisados, no
caso de se pretender uma ordenao crescente, ou em alternativa, o elemento de maior
valor de todos os elementos analisados, no caso de se pretender uma ordenao
decrescente, colocando esse valor nos sucessivos elementos iniciais do agregado. Em cada
passagem, um elemento, de entre todos os restantes elementos do agregado ainda no
ordenados, colocado no stio certo. Mais concretamente, no caso de uma ordenao
crescente, na primeira passagem, o menor valor de todos fica colocado no primeiro
elemento do agregado, na segunda passagem, o segundo menor valor fica colocado no
segundo elemento do agregado, e assim sucessivamente at que na ltima passagem o
penltimo menor valor, ou seja, o segundo maior valor, fica colocado no penltimo
elemento do agregado, e, consequentemente, o maior valor fica automaticamente colocado
no ltimo elemento do agregado. Vamos agora apresentar dois algoritmos que aplicam este
mtodo de ordenao.
A Figura 6.12 apresenta o algoritmo de Ordenao Sequencial ( Sequencial Sort ) para a
ordenao crescente do agregado. Cada elemento do agregado comparado com os
restantes elementos do agregado abaixo dele. Se o elemento de cima, cujo ndice indi for
menor que o elemento de baixo, cujo ndice indj, ento trocam-se os elementos. Para
efectuar a troca dos elementos do agregado utilizada a funo Swap. S so analisados os
primeiros nelem1 elementos, porque, quando se ordena o penltimo elemento do
agregado, o ltimo elemento fica automaticamente ordenado.
Figura 6.12 - Algoritmo de ordenao Sequencial.
A Figura 6.13 apresenta a execuo do algoritmo para um agregado com 10 elementos. Em
cada passagem o elemento ordenado fica a sombreado para melhor se observar a parte
ordenada do agregado. Na nona passagem para alm do nono elemento, tambm o dcimo
void Swap (int *x, int *y)
{
int temp;
temp = *x; *x = *y; *y = temp;
}
void Sequential_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indj;
for (indi = 0; indi < nelem-1; indi++)
for (indj = indi+1; indj < nelem; indj++)
if (seq[indi] > seq[indj])
Swap (&seq[indi], &seq[indj]); /* trocar os elementos */
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12
elemento fica ordenado. O algoritmo executa nove passagens e faz um total de 45
comparaes e de 25 trocas para ordenar o agregado.
2 209 25 32 42 15 8 55 145 330
2 330 209 32 42 25 15 55 145 8
2 15 330 32 209 42 25 55 145 8
2 15 25 42 330 209 32 55 145 8
2 15 25 209 32 330 42 55 145 8
2 15 25 330 32 42 209 55 145 8
2 15 25 55 32 42 330 209 145 8
2 15 25 55 32 42 145 330 209 8
2 15 25 55 32 42 145 209 330 8
209 25 15 32 42 2 8 55 145 330
depois
da
primeira
passagem
NC=9 NT=3
I
J J
depois
da
segunda
passagem
NC=8 NT=4
depois
da
terceira
passagem
NC=7 NT=4
I
J J
J J
I depois
da
quarta
passagem
NC=6 NT=4
depois
da
quinta
passagem
NC=5 NT=3
I
J J
I
J J
depois
da
sexta
passagem
NC=4 NT=2
depois
da
stima
passagem
NC=3 NT=2
I
J J
J
depois
da
oitava
passagem
NC=2 NT=2 J
depois
da
nona
passagem
NC=1 NT=1
I
TOTAL
NC = 45
NT = 25
2 15 25 55 32 42 145 209 330 8
I
J J
I
J
Figura 6.13 - Execuo do algoritmo de ordenao Sequencial.
13 CAPTULO 6 : PESQUISA E ORDENAO
Para um agregado com N elementos, todos os elementos do agregado so comparados
com todos os outros elementos, pelo que, este algoritmo faz (N
2
N)/2 comparaes e o
nmero de trocas se bem que dependente do grau de desordenao dos elementos, no pior
caso pode atingir tambm as (N
2
N)/2 trocas. Portanto, este algoritmo tem uma eficincia
de comparao de ordem O(N
2
) e uma eficincia de trocas de ordem O(N
2
), pelo que,
pertence classe O(N
2
).
Este algoritmo tem a desvantagem de eventualmente fazer trocas desnecessrias de
elementos. Pelo que, para reduzir o nmero de trocas, em cada passagem deve-se detectar
o elemento de menor valor, memorizando-se para o efeito o ndice onde ele se encontra
armazenado e s depois de comparar o elemento com os restantes elementos do agregado,
que se faz a troca do elemento analisado com o elemento de menor valor.
A Figura 6.14 apresenta o algoritmo de Ordenao Seleco ( Selection Sort ), que
implementa esta optimizao das trocas, para a ordenao crescente do agregado. Existe a
varivel auxiliar indmin que no incio de cada passagem inicializada a indi e que representa
o ndice do elemento de menor valor detectado durante a passagem. Cada elemento do
agregado comparado com os restantes elementos do agregado abaixo dele. Se o elemento
de menor valor, cujo ndice indmin for menor que o elemento, cujo ndice indj, ento
este elemento com ndice indj o novo menor valor at ento detectado, pelo que, o ndice
indmin actualizado a indj. No final da passagem pelo agregado, se indmin for diferente de
indi, ento sinal que o elemento de menor valor no est na posio inicial indi e,
portanto, preciso trocar os dois elementos.
Figura 6.14 - Algoritmo de ordenao Seleco.
A Figura 6.15 apresenta a execuo do algoritmo para um agregado com 10 elementos e o
algoritmo executa nove passagens para ordenar o agregado, sendo o nmero total de
comparaes igual ao do algoritmo anterior. Na quarta e na nona passagens no
efectuada qualquer troca e nas restantes passagens efectuada apenas uma troca, pelo que,
o nmero de trocas de apenas 7 contra as 25 do algoritmo anterior.
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(N
2
) 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(N
2
).
void Selection_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indj, indmin;
for (indi = 0; indi < nelem-1; indi++)
{
indmin = indi; /* o menor valor est na posio i */
for (indj = indi+1; indj < nelem; indj++)
if (seq[indmin] > seq[indj])
indmin = indj; /* o menor valor est na posio j */
if (indmin != indi)
Swap (&seq[indi], &seq[indmin]); /* trocar os elementos */
}
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
I
2 25 15 32 42 209 8 55 145 330
2 25 15 32 42 209 330 55 145 8
2 15 25 32 42 209 330 55 145 8
2 15 25 32 42 209 330 55 145 8
2 15 25 42 32 209 330 55 145 8
2 15 25 209 32 42 330 55 145 8
2 15 25 55 32 42 330 209 145 8
2 15 25 55 32 42 145 209 330 8
2 15 25 55 32 42 145 209 330 8
209 25 15 32 42 2 8 55 145 330
depois
da
primeira
passagem
NC=9 NT=1 J J
I
J J
depois
da
segunda
passagem
NC=8 NT=1
depois
da
terceira
passagem
NC=7 NT=1
I
J J
J J
I depois
da
quarta
passagem
NC=6 NT=0
depois
da
quinta
passagem
NC=5 NT=1
I
J J
I
J J
depois
da
sexta
passagem
NC=4 NT=1
depois
da
stima
passagem
NC=3 NT=1
I
J J
J
depois
da
oitava
passagem
NC=2 NT=1 J
depois
da
nona
passagem
NC=1 NT=0
I
I
J
TOTAL
NC = 45
NT = 7
2 15 25 55 32 42 145 209 330 8
MIN
MIN
MIN
MIN
MIN
MIN
MIN
MIN
MIN
Figura 6.15 - Execuo do algoritmo de ordenao Seleco.
15 CAPTULO 6 : PESQUISA E ORDENAO
6.2.2 Ordenao por troca
Os algoritmos de ordenao por troca, fazem passagens sucessivas sobre o agregado,
comparando elementos que se encontram a uma distncia fixa, que caso estejam fora de
ordem so trocados. Quando durante uma passagem no se tiver efectuado qualquer troca,
ento sinal de que os elementos que se encontram distncia de comparao j esto
ordenados, pelo que, a ordenao termina. Vamos agora apresentar trs algoritmos que
aplicam este mtodo de ordenao.
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.
Figura 6.16 - Algoritmo de ordenao Bolha.
A Figura 6.17 apresenta a execuo do algoritmo para um agregado com 10 elementos.
Uma vez que o agregado est bastante desordenado, o algoritmo executa nove passagens,
se bem que na ltima no efectua qualquer troca. No entanto mesmo que efectuasse trocas
na nona passagem, o algoritmo terminaria na mesma, uma vez que, o indicador de posio
inicial excedeu o fim do agregado. O algoritmo faz um total de 45 comparaes e de 25
trocas para ordenar o agregado.
void Bubble_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indinicial, ntrocas;
indinicial = 1; /* inicializar o limite superior de ordenao */
do
{
ntrocas = 0; /* inicializar o contador de trocas */
for (indi = nelem-1; indi >= indinicial; indi--)
if (seq[indi-1] > seq[indi])
{
Swap (&seq[indi], &seq[indi-1]); /* trocar os elementos */
ntrocas++; /* actualizar o nmero de trocas efectuadas */
}
indinicial++; /* actualizar o limite superior de ordenao */
} while (ntrocas && indinicial < nelem);
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 16
2 330 25 8 15 42 32 55 145 209
2 209 330 42 25 15 32 55 145 8
2 15 209 32 330 25 42 55 145 8
2 15 25 32 209 330 42 55 145 8
2 15 25 330 32 209 42 55 145 8
2 15 25 209 32 42 330 55 145 8
2 15 25 55 32 42 209 330 145 8
2 15 25 55 32 42 145 209 330 8
2 15 25 55 32 42 145 209 330 8
209 25 15 32 42 2 8 55 145 330
depois
da
primeira
passagem
NC=9 NT=6 I I
I I
depois
da
segunda
passagem
NC=8 NT=5
depois
da
terceira
passagem
NC=7 NT=4 I I
I I
depois
da
quarta
passagem
NC=6 NT=2
depois
da
quinta
passagem
NC=5 NT=2 I I
I I
depois
da
sexta
passagem
NC=4 NT=2
depois
da
stima
passagem
NC=3 NT=2 I I
I
depois
da
oitava
passagem
NC=2 NT=2 I
depois
da
nona
passagem
NC=1 NT=0 I
TOTAL
NC = 45
NT = 25
2 15 25 55 32 42 145 209 330 8
INICIAL
INICIAL
INICIAL
INICIAL
INICIAL
INICIAL
INICIAL
INICIAL
INICIAL
INICIAL
Figura 6.17 - Execuo do algoritmo de ordenao Bolha.
17 CAPTULO 6 : PESQUISA E ORDENAO
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 (N
2
N)/2
comparaes. Em mdia faz aproximadamente N
2
/3 comparaes. O nmero de trocas se
bem que dependente do grau de desordenao dos elementos, no pior caso pode atingir as
(N
2
N)/2 trocas. Portanto, este algoritmo tem uma eficincia de comparao de ordem
O(N
2
), uma eficincia de trocas de ordem O(N
2
), pelo que, pertence classe O(N
2
).
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.
Figura 6.18 - Algoritmo de ordenao Concha (1 verso).
A Figura 6.19 apresenta a execuo do algoritmo para um agregado com 10 elementos. O
algoritmo executa 7 passagens. Nas primeiras duas passagens, o incremento de 5
elementos, nas trs passagens seguintes o incremento de 2 elementos e nas duas
void Shell_Sort (int seq[], unsigned int nelem) /* 1 verso */
{
unsigned int indi, ntrocas, incremento;
for (incremento = nelem/2; incremento > 0; incremento /= 2)
do
{
ntrocas = 0; /* inicializar o contador de trocas */
for (indi = incremento; indi < nelem; indi++)
if (seq[indi-incremento] > seq[indi])
{ /* trocar os elementos */
Swap (&seq[indi], &seq[indi-incremento]);
ntrocas++; /* actualizar o nmero de trocas efectuadas */
}
} while (ntrocas);
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 18
passagens finais o incremento de 1 elemento. Faz um total de 52 comparaes e de 11
trocas para ordenar o agregado. Comparando com a ordenao Bolha, temos menos 2
passagens, mais algumas comparaes, mas, menos de metade das trocas.
2 8 15 330 42 209 25 55 145 32
2 8 15 330 42 209 25 55 145 32
2 8 32 55 42 25 145 330 209 15
2 8 25 55 42 32 145 330 209 15
2 15 25 55 32 42 145 209 330 8
2 15 25 55 32 42 145 209 330 8
209 25 15 32 42 2 8 55 145 330
depois
da
primeira
passagem
NC=5 NT=3 I I
depois
da
segunda
passagem
NC=5 NT=0
depois
da
terceira
passagem
NC=8 NT=4
depois
da
quarta
passagem
NC=8 NT=1
depois
da
quinta
passagem
NC=8 NT=0
depois
da
sexta
passagem
NC=9 NT=3
depois
da
stima
passagem
NC=9 NT=0
TOTAL
NC = 52
NT = 11
2 15 25 55 32 42 145 209 330 8
INCREMENTO=5
INCREMENTO=5
I I
INCREMENTO=2
I I
I I
INCREMENTO=2
2 8 25 55 42 32 145 330 209 15
I I
INCREMENTO=2
INCREMENTO=1
I I
I I
INCREMENTO=1
Figura 6.19 - Execuo do algoritmo de ordenao Concha.
Normalmente, o algoritmo Concha mais eficiente do que o algoritmo Bolha, uma vez
que, as primeiras passagens analisam apenas parte dos elementos do agregado e portanto,
fazem poucas trocas, mas trocam elementos que esto muito fora de stio. Quando as
ltimas passagens, que analisam os elementos adjacentes, so efectuadas, ento o agregado
j se encontra parcialmente ordenado, pelo que, so necessrias poucas passagens e poucas
trocas para acabar a ordenao. No entanto, para passar de incremento em incremento esta
verso do algoritmo exige uma passagem sem trocas.
19 CAPTULO 6 : PESQUISA E ORDENAO
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.
Figura 6.20 - Algoritmo de ordenao Concha (2 verso).
O desempenho deste algoritmo depende da srie de incrementos. Nesta verso e para um
agregado com N elementos, em relao ao nmero de comparaes, este algoritmo no pior
caso e usando a srie de incrementos propostos por Donald Shell, pertence classe O(N
2
).
Com a srie de incrementos propostos por Hibbard pertence classe O(N
3/2
) e com a srie
de incrementos propostos por Sedgewick pertence classe O(N
4/3
).
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
void Shell_Sort (int seq[], unsigned int nelem) /* 2 verso */
{
unsigned int indi, indj, incremento; int temp;
for (incremento = nelem/2; incremento > 0; incremento /= 2)
for (indi = incremento; indi < nelem; indi++)
{
temp = seq[indi]; /* copiar o elemento a ordenar */
for (indj = indi; indj >= incremento; indj -= incremento)
if (temp < seq[indj-incremento])
seq[indj] = seq[indj-incremento]; /* deslocar elementos */
else break;
seq[indj] = temp; /* inserir o elemento a ordenar na posio */
}
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 20
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 ascendente. Existe
tambm a varivel auxiliar indfinal que inicializada com o ndice do ltimo elemento do
agregado e que representa o ndice do elemento da parte final do agregado onde termina a
comparao de elementos em cada passagem descendente. Na passagem ascendente, cada
elemento final do agregado, desde o elemento de ndice indfinal at ao elemento de ndice
indinicial, 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. O ndice do elemento onde foi feita a ltima troca armazenado e aps ter sido
completada a passagem ascendente, o ndice indinicial colocado na posio seguinte
posio da ltima troca. Deste modo os elementos de menor valor vo sendo deslocados
em direco parte inicial do agregado. Depois na passagem descendente, cada elemento
inicial do agregado, desde o elemento de ndice indinicial at ao elemento de ndice indfinal,
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. O ndice do
elemento onde foi feita a ltima troca armazenado e aps ter sido completada a passagem
descendente, o ndice indfinal colocado na posio anterior posio da ltima troca.
Deste modo os elementos de maior valor vo sendo deslocados em direco parte final
do agregado.
Em cada passagem ascendente e descendente, ordena-se pelo menos dois elementos, um na
parte inicial do agregado e outro na parte final do agregado. Quando no feita qualquer
troca durante uma passagem ascendente, o ndice indinicial ser colocado depois do ndice
indfinal. Por outro lado, quando no feita qualquer troca durante uma passagem
descendente, o ndice indfinal ser colocado antes do ndice indinicial. Qualquer destas
situaes sinal de que os elementos entre o ndice indinicial e o ndice indfinal tambm
esto ordenados e a ordenao termina, porque o agregado est todo ordenado.
Figura 6.21 - Algoritmo de ordenao Crivo.
void Shaker_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indinicial = 1, indfinal = nelem-1, utroca;
do
{
/* passagem ascendente */
for (indi = indfinal; indi >= indinicial; indi--)
if (seq[indi-1] > seq[indi])
{
Swap (&seq[indi], &seq[indi-1]); /* trocar os elementos */
utroca = indi; /* actualizar a posio da ltima troca */
}
indinicial = utroca+1; /* actualizar o limite superior */
/* passagem descendente */
for (indi = indinicial; indi <= indfinal; indi++)
if (seq[indi-1] > seq[indi])
{
Swap (&seq[indi], &seq[indi-1]); /* trocar os elementos */
utroca = indi; /* actualizar a posio da ltima troca */
}
indfinal = utroca-1; /* actualizar o limite inferior */
} while (indinicial < indfinal);
}
21 CAPTULO 6 : PESQUISA E ORDENAO
A Figura 6.22 apresenta a execuo do algoritmo para um agregado com 10 elementos. O
algoritmo executa 6 passagens, 3 em cada sentido. Na terceira passagem descendente no
efectuada qualquer troca, pelo que, o ndice de posio final passa para trs do ndice de
posio inicial parando o processo de ordenao. O algoritmo faz um total de 39
comparaes e de 25 trocas para ordenar o agregado. Comparando com a ordenao
Bolha, temos menos 3 passagens e menos 6 comparaes, e o mesmo nmero de trocas.
Os elementos do agregado que vo ficando ordenados, esto a sombreado para melhor se
observar as duas extremidades ordenadas do agregado.
2 330 25 8 15 42 32 55 145 209
I I
2 25 15 32 42 8 55 145 330 209
2 209 25 32 15 42 55 145 330 8
2 25 15 55 42 32 145 209 330 8
2 15 25 55 32 42 145 209 330 8
209 25 15 32 42 2 8 55 145 330
depois
da
primeira
passagem
INICIAL
2 15 25 55 32 42 145 209 330 8
UT
UT
UT
UT
UT
UT
2 15 25 55 32 42 145 209 330 8
depois
da
terceira
passagem
depois
da
quarta
passagem
depois
da
quinta
passagem
depois
da
sexta
passagem
TOTAL
NC = 39
NT = 25
INICIAL
INICIAL
FINAL
FINAL
depois
da
segunda
passagem
NC=9 NT=6
NC=8 NT=7 I I
NC=7 NT=4
FINAL
I I
NC=6 NT=6
INICIAL FINAL
I I
NC=5 NT=2
INICIAL FINAL
FINAL
FINAL
INICIAL
INICIAL
I I
I I NC=4 NT=0
Figura 6.22 - Execuo do algoritmo de ordenao Crivo.
O desempenho deste algoritmo complexo de analisar. Em comparao com o algoritmo
Bolha, tem o mesmo desempenho em relao ao nmero de trocas e mais eficiente em
relao ao nmero de comparaes. No entanto, tambm pertence classe O(N
2
).
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 22
6.2.3 Ordenao por insero
A ordenao por insero a tcnica de ordenao que normalmente usamos,
nomeadamente, para ordenar uma mo de cartas. Consiste em determinar para cada
elemento do agregado, a posio de insero para que ele fique ordenado e inseri-lo nessa
posio, deslocando para o efeito os restantes elementos.
A Figura 6.23 apresenta o algoritmo de Ordenao Insero ( Insertion Sort ) para a
ordenao crescente do agregado. Durante o processo de ordenao, o agregado est
dividido em duas partes. A parte inicial do agregado est ordenada e a parte final est
desordenada. O algoritmo comea por considerar que o primeiro elemento do agregado j
est ordenado e depois ordena os restantes elementos do agregado, ou seja, desde o
segundo ao ltimo elemento. Cada elemento a ordenar previamente copiado para uma
varivel temporria, de forma a abrir uma posio livre que permita o deslocamento dos
elementos que esto atrs dele e que so maiores do que ele. Enquanto houver elementos
na parte ordenada do agregado, ou seja, elementos que esto atrs do elemento a ordenar
que sejam maiores do que ele, estes elementos so deslocados uma posio para a frente.
Quando o deslocamento dos elementos terminar, o elemento a ordenar colocado na
posio que foi aberta pelo deslocamento dos elementos. Se o elemento j estiver na
posio correcta, porque maior do que todos os que esto atrs de si, ento no h
deslocamento de elementos e o elemento colocado sobre ele prprio. Nesta situao, so
feitas duas instrues de cpia inutilmente. Esta ineficincia caracterstica desta
implementao do algoritmo de insero. O algoritmo faz praticamente tantas
comparaes como deslocamentos. Mais concretamente, para ordenar cada elemento, faz
mais uma comparao, para poder detectar o fim dos deslocamentos.
Figura 6.23 - Algoritmo de ordenao Insero.
Para comparar este algoritmo com os anteriores, temos que ter em considerao, que para
ordenar cada elemento, so precisas duas instrues de atribuio. Uma para copiar o
elemento para uma varivel temporria e outra para o inserir na posio definitiva. E o
deslocamento de cada elemento custa ainda uma instruo de atribuio. Pelo que, o
nmero de instrues de atribuio NA = ND + 2 * (N1). Como cada instruo de
atribuio equivalente, a um tero das instrues de atribuio que so necessrias para
efectuar a troca de dois elementos do agregado, nos algoritmos anteriores baseados em
trocas, ento temos que NT = ( ND + 2 * (N1) )/3.
A Figura 6.24 apresenta a execuo do algoritmo para um agregado com 10 elementos. O
algoritmo faz um total de 34 comparaes e de 43 instrues de atribuio para efectuar os
void Insertion_Sort (int seq[], unsigned int nelem)
{
unsigned int indi, indd; int temp;
for (indi = 1; indi < nelem; indi++)
{
temp = seq[indi]; /* copiar o elemento a ordenar */
/* deslocar os elementos atrs dele que lhe so maiores */
for (indd = indi; indd > 0 && seq[indd-1] > temp; indd--)
seq[indd] = seq[indd-1];
seq[indd] = temp; /* inserir o elemento a ordenar na posio */
}
}
23 CAPTULO 6 : PESQUISA E ORDENAO
25 deslocamentos de elementos que so necessrios para ordenar o agregado, o que
equivalente a aproximadamente 14 trocas.
209 25 15 32 42 2 8 55 145 330
PI
209 25 15 32 42 2 8 55 145 330
209 25 15 32 42 2 8 55 145 330
25 330 15 32 42 2 8 55 145 209
15 209 330 32 42 2 8 55 145 25
25 330 15 32 42 2 8 55 145 209
15 42 209 32 330 2 8 55 145 25
15 209 330 32 42 2 8 55 145 25
2 25 42 32 209 330 8 55 145 15
15 42 209 32 330 2 8 55 145 25
2 25 32 330 42 209 8 55 145 15
2 25 42 32 209 330 8 55 145 15
ordenar
o
segundo
elemento
NC=1 ND=0
I
I
ordenar
o
terceiro
elemento
NC=3 ND=2
PI
ordenar
o
quarto
elemento
NC=4 ND=3
I PI
ordenar
o
quinto
elemento
NC=3 ND=2
I PI
I
ordenar
o
sexto
elemento
NC=6 ND=5
PI
ordenar
o
stimo
elemento
NC=4 ND=3
I PI
2 25 32 330 42 209 8 55 145 15
ordenar
o
oitavo
elemento
NC=7 ND=6
ordenar
o
nono
elemento
NC=3 ND=2
ordenar
o
dcimo
elemento
NC=3 ND=2
I PI
2 15 25 209 32 42 330 55 145 8
2 15 25 55 32 42 209 330 145 8
2 15 25 209 32 42 330 55 145 8
I PI
2 15 25 55 32 42 145 209 330 8
2 15 25 55 32 42 209 330 145 8
I PI
2 15 25 55 32 42 145 209 330 8
TOTAL
NC = 34
NA = 43
o segundo elemento j est no stio
Figura 6.24 - Execuo do algoritmo de ordenao Insero.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 24
Para um agregado com N elementos, este algoritmo faz no pior caso (N
2
N)/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.
6.2.4 Comparao dos algoritmos de ordenao
A Tabela 6.1 apresenta os resultados dos algoritmos de ordenao apresentados. O pior
algoritmo o Sequencial, j que faz o nmero mximo de comparaes necessrias para
ordenar um agregado, e faz trocas s cegas, fazendo por vezes trocas que so feitas e
refeitas vezes sem conta. O algoritmo Seleco permite minimizar o nmero de trocas e
neste aspecto o melhor de todos, compensando desta forma o excesso de comparaes.
O algoritmo Bolha tem um desempenho mau, a no ser quando o agregado est
parcialmente desordenado, o que no o caso do exemplo apresentado. A primeira verso
do algoritmo Concha tem, para o exemplo utilizado, um nmero de comparaes maior do
que os outros algoritmos, se bem que com um nmero de trocas baixo. Mas, no caso da
segunda verso, ele o melhor algoritmo em nmero de comparaes. Como o resultado
deste algoritmo depende muito da srie de incrementos utilizado, estes valores podem
eventualmente ser melhorados com outra srie de incrementos. O nmero de comparaes
s desce abaixo de N
2
/2, para os algoritmos Concha (2 verso), Crivo e Insero. O
primeiro tem um nmero de trocas aceitvel, o segundo tem um nmero de trocas elevado,
enquanto que o terceiro tem um nmero de trocas pior do que o Concha (1 verso), mas
que aceitvel se tivermos em conta a reduo do nmero de comparaes. Portanto, os
algoritmos Insero e Concha (2 verso) so os melhores algoritmos analisados.
Algoritmo N de Comparaes N de Trocas
Sequencial 45 25
Seleco 45 7
Bolha 45 25
Concha (1 verso) 52 11
Concha (2 verso) 29 18
Crivo 39 25
Insero 34 14
Tabela 6.1 - Comparao dos algoritmos de ordenao.
Para a escolha da verso do algoritmo Concha, devemos optar pela primeira verso se a
funo de comparao dos elementos do agregado for simples, por exemplo uma
comparao numrica e se os elementos do agregado forem estruturas pesadas, ou seja,
com muitos bytes, com vista a minorar o nmero de trocas. Se pelo contrrio, a funo de
comparao dos elementos do agregado for pesada, por exemplo uma comparao
alfanumrica com muitos caracteres e se os elementos do agregado forem tipos simples,
estruturas pequenas, ou ponteiros, ento devemos optar pela segunda verso com vista a
minorar o nmero de comparaes. Estas consideraes aplicam-se igualmente escolha
de qualquer algoritmo de ordenao.
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
6.3 Ordenao por fuso
Uma forma de optimizar a ordenao de um agregado com muitos elementos, consiste em
partir o agregado em vrios agregados mais pequenos, orden-los separadamente e depois
fundi-los num agregado nico. Para fundir dois ou mais agregados j ordenados, usa-se um
algoritmo de ordenao por fuso. A Figura 6.25 apresenta o algoritmo de Ordenao
Fuso de Listas ( Merge List Sort ) para a fuso de dois agregados ordenados por ordem
crescente, considerando que eles no tm elementos repetidos. O agregado de sada tem
que ter capacidade suficiente para armazenar os elementos dos dois agregados de entrada.
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.
Figura 6.25 - Algoritmo de ordenao Fuso de Listas.
Este algoritmo muito verstil e eficiente, uma vez que, pode ser generalizado para fazer a
fuso de mais do que dois agregados. No caso da existncia de elementos repetidos nos
agregados de entrada, podemos considerar solues alternativas, em que a ocorrncia de
elementos repetidos eliminada ou mantida. Tambm pode ser utilizado como algoritmo
de ordenao externa para fundir dois ou mais ficheiros. Para executar a fuso de dois
agregados j ordenados, com N/2 elementos cada, este algoritmo faz no melhor caso N/2
comparaes e no pior caso N1 comparaes.
void Merge_List_Sort (int seqa[], unsigned int nelema,\
int seqb[], unsigned int nelemb,\
int seqc[], unsigned int *nelemc)
{
unsigned int indi = 0, indj = 0, indk = 0, indc;
/* copiar o elemento do agregado A ou o elemento do agregado B */
while (indi < nelema && indj < nelemb)
if (seqa[indi] < seqb[indj])
seqc[indk++] = seqa[indi++];
else seqc[indk++] = seqb[indj++];
if (indi < nelema)
for (indc = indi; indc < nelema; indc++)
seqc[indk++] = seqa[indc];
/* copiar os restantes elementos do agregado A */
else for (indc = indj; indc < nelemb; indc++)
seqc[indk++] = seqb[indc];
/* copiar os restantes elementos do agregado B */
*nelemc = indk; /* armazenar o nmero de elementos do agregado C */
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 26
A Figura 6.26 apresenta a execuo do algoritmo na fuso de dois agregados previamente
ordenados, com 5 elementos cada. O algoritmo faz 8 comparaes, at que o agregado B
fica esgotado, aps o qual, os restantes elementos do agregado A so copiados para o
agregado C.
K
15 2 25 55 145
B
I
C
J
antes
da
ordenao
comear
2
15 2 25 55 145
B
8 42 209 330 32
A
J I
K
depois
de copiar
o primeiro
elemento
do
agregado B
8 2
15 2 25 55 145 B
C
C
I
K
J
15 8 2
15 2 25 55 145 B 8 42 209 330 32 A
25
I J
K
8 42 209 330 32
A
8 42 209 330 32
A
depois
de copiar
o primeiro
elemento
do
agregado A
depois
de copiar
mais dois
elementos
do
agregado B
depois
de copiar
mais dois
elementos
do
agregado A
15 2 25 55 145
B
8 42 209 330 32
A
J I
K
C 15 8 2 25 32 42
15 2 25 55 145
B
C
I
K
J
8 42 209 330 32 A
15 8 2 25 55 32 42 145
depois
de copiar
os
restantes
elementos
do
agregado A
15 2 25 55 145 B 8 42 209 330 32 A
I J
K
15 8 2 25 55 32 42 145 209 330 C
depois
de esgotar
os
elementos
do
agregado B
Figura 6.26 - Execuo do algoritmo de ordenao Fuso de Listas.
27 CAPTULO 6 : PESQUISA E ORDENAO
6.4 Algoritmos de ordenao recursivos
Os algoritmos de ordenao recursivos so algoritmos de ordenao que aplicam o
princpio do dividir para conquistar. So algoritmos complexos, mas muito eficientes e
pertencem classe O(N log
2
N). Vamos apresentar dois algoritmos.
6.4.1 Algoritmo de ordenao Fuso
O algoritmo de Ordenao Fuso ( Merge Sort ) pode ser utilizado de forma recursiva para
ordenar um agregado, partindo-o sucessivamente ao meio e fazendo a fuso dos agregados
parcelares j ordenados. Este princpio quando aplicado recursivamente, at que os
agregados obtidos so apenas compostos por um nico elemento, acaba por ordenar o
agregado. Esta verso recursiva, para a ordenao crescente do agregado, apresentada na
Figura 6.27.
Figura 6.27 - Algoritmo de ordenao Fuso.
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 */
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 28
A invocao inicial da funo de ordenao Merge_Sort (seq, 0, nelem-1);. Cada
invocao da funo Merge_Sort precisa de saber o primeiro e o ltimo elementos do
agregado parcelar que est a ser ordenado, pelo que, o procedimento tem como parmetros
de entrada o incio e o fim do agregado. A funo Merge_List_Sort que constitui a parte
no recursiva do algoritmo faz a fuso das duas partes j ordenadas para um agregado local
e no fim copia-o de volta para o agregado a ordenar. uma verso ligeiramente diferente
do algoritmo apresentado na Figura 6.25, uma vez que faz a fuso de duas partes do
mesmo agregado em vez de dois agregados distintos. Da que tem como parmetros de
entrada os limites das duas partes do agregado, definidos pelos elementos inicial, mdio e
final do agregado. O agregado local atribudo dinamicamente com o tamanho necessrio
para fazer a fuso das duas metades do agregado e libertado quando no mais necessrio.
Para simplificar no feita a validao da atribuio de memria.
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.
A Figura 6.28 apresenta a execuo do algoritmo para um agregado com 10 elementos. O
algoritmo faz um total de 21 comparaes para fundir os agregados parcelares e executa 68
instrues de atribuio, ou seja, cpias de valores entre o agregado a ordenar e o agregado
local da funo de fuso de listas, metade das quais se devem ao facto da necessidade de
usar um agregado extra para fazer a fuso. Este nmero de cpias equivalente a
aproximadamente 23 trocas.
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.
Para executar a ordenao de um agregado com N elementos, sendo N mltiplo de 2, este
algoritmo faz no melhor caso (N log
2
N)/2 comparaes e no pior caso N log
2
N N + 1
comparaes, pelo que, pertence classe O(N log
2
N).
6.4.2 Algoritmo de ordenao Rpido
O algoritmo de Ordenao Rpido ( Quick Sort ) foi criado em 1960 por C. A. R. Hoare e
utiliza um principio muito simples que quando aplicado recursivamente, acaba por ordenar
o agregado. O princpio o seguinte. Vamos escolher um elemento do agregado, que
vamos designar por pivot e dividir o agregado em duas partes. Na parte da esquerda
colocam-se os valores menores do que o pivot e na parte direita colocam-se os valores
maiores do que o pivot. Se cada parte do agregado for sucessivamente dividida ao meio e
for aplicado este princpio de separao dos elementos, ento quando o processo recursivo
terminar ao se atingir agregados com trs ou menos elementos, o agregado est ordenado
por ordem crescente.
A Figura 6.29 apresenta o algoritmo para a ordenao crescente do agregado.
29 CAPTULO 6 : PESQUISA E ORDENAO
2 15 25 55 32 42 145 209 330 8
209 25 15 32 42 2 8 55 145 330
209 25 15 42 330
209 25 15 42 330
209 25 15 42 330
209 330
25 330 209 15 42
15 42 209 330 25
2 8 55 145 32
2 8 55 145 32
2 8 55 145 32
2 32
2 32 8 55 145
2 32 55 145 8
i r
n e
v c
o u
c r
a s
i
v
e a
s s
f a
u g
s r
e
o g
a
d d
o o
s s
209 330 2 32
TOTAL
NC = 21
NA = 68
Figura 6.28 - Execuo do algoritmo de ordenao Fuso.
Se o nmero de elementos do agregado for menor do que dois, ento o agregado est
automaticamente ordenado. Se existirem apenas dois elementos, eles so ordenados atravs
de uma simples comparao e eventual troca dos dois elementos. Quando o agregado tem
mais do que dois elementos, escolhido o elemento de ndice mdio para servir de pivot.
Os elementos das extremidades e o pivot so trocados para ficarem por ordem crescente.
Se s existirem trs elementos, ento esta operao constituda por trs comparaes e
eventuais trs trocas, ordena o agregado. Seno, h que assegurar que todos os elementos
esquerda do pivot so menores do que ele e que todos os elementos sua direita so
maiores do que ele. Os elementos que se encontrem fora do stio so trocados da parte
direita para a parte esquerda e vice-versa, ou ento so trocados com o pivot provocando
que ele se desloque mais para a esquerda ou mais para a direita do que a posio mdia
inicialmente calculada. Pelo que, quando o processamento se separao terminar no h
garantia que o agregado est partido em duas metades com um nmero semelhante de
elementos. Aps este processamento, o algoritmo invocado recursivamente para o
agregado constitudo pelos elementos esquerda do pivot e para o agregado constitudo
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 30
pelos elementos direita do pivot. A invocao inicial da funo de ordenao
Quick_Sort (seq, 0, nelem-1);.
Figura 6.29 - Algoritmo de ordenao Rpido (1 verso).
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++;
/* procurar elementos na parte direita menores do que pivot */
while (indj > medio)
if (seq[indj] < seq[medio]) break; else indj--;
if (indi != medio && indj != medio)
Swap (&seq[indi], &seq[indj]); /* trocar os elementos */
else if (indi == medio && indj != medio)
{ /* trocar o elemento de ndice indj com o pivot */
/* e deslocar o pivot uma posio para a direita */
Swap (&seq[medio++], &seq[indj]);
Swap (&seq[medio], &seq[indj]);
indi = medio;
}
else if (indi != medio && indj == medio)
{ /* trocar o elemento de ndice indi com o pivot */
/* e deslocar o pivot uma posio para a esquerda */
Swap (&seq[medio--], &seq[indi]);
Swap (&seq[medio], &seq[indi]);
indj = medio;
}
}
/* 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);
}
31 CAPTULO 6 : PESQUISA E ORDENAO
A Figura 6.30 apresenta a execuo do algoritmo para um agregado com 10 elementos. Os
elementos que servem de pivot, aparecem a cheio para se distinguirem dos restantes
elementos e as invocaes recursivas so identificadas pela sigla IR. O algoritmo faz um
total de 28 comparaes e de 19 trocas de elementos. Como exemplo, vamos ver como se
comporta o algoritmo quando a funo invocada. Uma vez que o agregado inicial tem
dez elementos, de ndices 0 a 9, o elemento de ndice 4, cujo valor 42, escolhido para
pivot. Ao ordenar os valores 209, 42 e 145, o valor 145 passa a ser o novo pivot, porque
o valor mediano. Todos os valores menores do que 145 so colocados sua esquerda,
enquanto que, todos os valores maiores do que 145 so colocados sua direita. Pelo que, o
pivot vai ser deslocado para a posio de ndice 7. Depois so feitas as invocaes
recursivas dos agregados formados pelos elementos 0 a 6 e pelos elementos 8 a 9.
Quando comparado com os algoritmos anteriores, constatamos que ele tem um
desempenho praticamente semelhante ao Concha (2 verso) e globalmente equivalente ao
Fuso, com a vantagem de no necessitar de um agregado auxiliar. Quando comparado
com o algoritmo de Insero, ele tem um maior nmero de trocas compensado com um
menor nmero de comparaes, tendo no entanto uma implementao mais complexa.
15 25
2 15 25 55 32 42 145 209 330 8
42 25 15 32 8 2 145 330 209 55
42 25 15 8 55
25 8
2 32
42 55
32
TOTAL
NC = 28
NT = 19
209 25 15 32 42 2 8 55 145 330
15 25 8 32 2 55 42
2 15 8
aps processamento
25
25 8 15
2
2
25 8 15 2 42 55
209 330
aps processamento
aps processamento
209 25 15 32 42 2 8 55 145 330
I R I R
I R I R
I R I R
2 15
Figura 6.30 - Execuo do algoritmo de ordenao Rpido.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 32
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.
Figura 6.31 - Algoritmo de ordenao Rpido (2 verso).
Para executar a ordenao de um agregado com N elementos, este algoritmo faz no pior
caso (N
2
N)/2 comparaes. No entanto, no caso mdio faz aproximadamente
1.4(N+1) log
2
N comparaes, pelo que, considera-se que pertence classe O(N log
2
N).
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);
}
33 CAPTULO 6 : PESQUISA E ORDENAO
6.5 Generalizao dos algoritmos de ordenao
Os algoritmos de ordenao foram apresentados e aplicados sobre agregados de nmeros
inteiros, com vista a melhor visualizar o seu funcionamento. No entanto, em muitas
aplicaes h a necessidade de armazenar bases de dados em agregados e de as ordenar de
acordo com as diferentes caractersticas dos seus elementos. Por vezes existe mesmo a
necessidade de ordenar sucessivamente um agregado de registos por diversas chaves de
ordenao diferente, como por exemplo, fazer ordenaes alfabticas, cronolgicas e
numricas e fazer ordenaes crescentes ou ascendentes e decrescentes ou descendentes.
Como podemos ento implementar estas ordenaes recorrendo ao mesmo algoritmo de
ordenao? E de preferncia com o mnimo de esforo. Ou seja, como podemos
generalizar um algoritmo de ordenao para que ele possa ordenar um agregado com
elementos de um qualquer tipo de dados, ordenar por diferentes critrios e ordenar por
ordem crescente ou decrescente. Para poder generalizar um algoritmo de ordenao e de
modo a estruturar melhor a soluo, precisamos de um ficheiro de interface que defina o
tipo de elementos constituintes do agregado e que providencie as funes de comparao
de acordo com os diferentes critrios de ordenao que se pretendem efectuar.
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.
Para implementar no mesmo algoritmo de ordenao, a ordenao crescente ou ascendente
e decrescente ou descendente, vamos passar funo de ordenao o parmetro de entrada
adicional inteiro tord que representa o tipo de ordenao a efectuar. Vamos definir que este
parmetro indica que se pretende a ordenao crescente com o valor 1 e indica que se
pretende a ordenao decrescente com o valor 1.
Pelo que, as funes de comparao tm a particularidade de devolver um valor inteiro que
um dos seguintes trs valores: o valor 0 no caso dos elementos serem iguais conforme o
item comparado; o valor 1 no caso do primeiro elemento ser maior do que segundo
elemento conforme o item comparado, ou seja, um maior nmero de registo ou um nome
alfabeticamente posterior ou uma data maior, o que significa uma data mais recente; e o
valor 1 no caso do primeiro elemento ser menor do que segundo elemento conforme o
item comparado, ou seja, um menor nmero de registo ou um nome alfabeticamente
anterior ou uma data menor o que significa uma data mais antiga.
Finalmente, para poder generalizar os algoritmos de ordenao precisamos de um
mecanismo que permita utilizar uma qualquer funo de comparao dentro da funo de
ordenao, de acordo com a ordenao pretendida num dado momento do programa. Para
tal, a linguagem C providencia os ponteiros para funes. Um ponteiro para uma funo
permite invocar de uma forma simples e elegante diferentes funes. A seguinte instruo
define um ponteiro para uma funo inteira com dois parmetros de entrada do tipo
TElem, que reconhecido pelo identificador PtFComp.
typedef int (*PtFComp) (TElem, TElem);
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 34
Figura 6.32 - Ficheiro de interface do elemento constituinte do agregado a ordenar.
/********** Interface da Estrutura de Dados do Agregado **********/
/* Nome: elemento.h */
/* Definio do tipo dos elementos do agregado e das funes de
comparao necessrias. Este ficheiro deve ser modificado para
adequar a definio e as funes a cada implementao especfica. */
#include <string.h>
#ifndef _ELEMENTO
#define _ELEMENTO
/************* Definio do Tipo de Dados do Elemento *************/
typedef struct
{
unsigned int dia;
unsigned int mes;
unsigned int ano;
} TData;
typedef struct
{
unsigned int nreg;
char nome[60];
TData data;
} TElem;
/***** Definio do Tipo Ponteiro para a Funo de Comparao *****/
typedef int (*PtFComp) (TElem, TElem);
/******* Definio das Funes de Comparao dos Elementos *******/
int CompNRegisto (TElem a, TElem b)
{
if ( a.nreg > b.nreg ) return 1;
else if ( a.nreg < b.nreg ) return -1;
else return 0;
}
int CompNome (TElem a, TElem b)
{
int comp = strcmp (a.nome, b.nome);
if ( comp > 0 ) return 1;
else if ( comp < 0 ) return -1;
else return 0;
}
int CompData (TElem a, TElem b)
{
if ( a.data.ano > b.data.ano ) return 1;
else if ( a.data.ano < b.data.ano) return -1;
else if ( a.data.mes > b.data.mes ) return 1;
else if (a.data.mes < b.data.mes ) return -1;
else if ( a.data.dia > b.data.dia ) return 1;
else if ( a.data.dia < b.data.dia ) return -1;
else return 0;
}
#endif
35 CAPTULO 6 : PESQUISA E ORDENAO
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.
Figura 6.34 - Algoritmo de ordenao Sequencial generalizado.
A Figura 6.35 apresenta a utilizao sucessiva deste algoritmo de ordenao, para ordenar
um agregado de elementos do tipo TElem. Antes de cada invocao, atribudo ao
ponteiro fcomp a funo de comparao necessria para obter a ordenao desejada e
depois a funo de ordenao invocada indicando tambm o tipo de ordenao
pretendido. Para aumentar a legibilidade do programa, definimos as constantes simblicas
CRESCENTE e DECRESCENTE.
Como exerccio de treino escreva a funo Display, cuja funcionalidade imprimir no
monitor a informao relativa a um agregado de estruturas do tipo TElem, com o
prottipo Display (TElem seq[], unsigned int nelem); e teste o programa.
void SwapElementos (TElem *x, TElem *y)
{
TElem temp;
temp = *x; *x = *y; *y = temp;
}
void Sequential_Sort (TElem seq[], unsigned int nelem,\
PtFComp fcomp, int tord)
{
unsigned int indi, indj;
for (indi = 0; indi < nelem-1; indi++)
for (indj = indi+1; indj < nelem; indj++)
if ( fcomp (seq[indi], seq[indj]) == tord )
SwapElementos (&seq[indi], &seq[indj]);
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 36
Figura 6.35 - Exemplo da utilizao sucessiva do algoritmo de ordenao Sequencial.
6.6 Avaliao do desempenho dos algoritmos
Para podermos avaliar o desempenho dos algoritmos de ordenao necessrio
contabilizar o nmero de operaes de comparao e o nmero de operaes de trocas de
elementos. Ou, uma vez que existem algoritmos de ordenao que em vez de trocas fazem
cpias de elementos, contabilizar em alternativa o nmero de instrues de atribuio, de
maneira a ter uma mtrica uniforme.
Para calcular o nmero de operaes efectuadas em invocaes sucessivas das funes de
troca e de comparao de elementos, utilizam-se variveis contadoras de durao
permanente, ou seja, variveis que so declaradas com o qualificativo static. Como a
funo de ordenao pode ser repetidamente invocada, ento conveniente poder reiniciar
a contagem, assim como necessrio reportar o resultado final da contagem. Pelo que, as
#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);
int main (void)
{
TElem pintores[] = {
{ 1, "Vincent Van Gogh", {30, 3, 1853} },
{ 2, "Vieira da Silva", {13, 6, 1908} },
{ 3, "Amedeo Modigliani", {12, 7, 1884} },
{ 4, "Claude Monet", {14, 11, 1840} },
{ 5, "Georgia O'Keeffe", {15, 11, 1887} }
};
int nelem = sizeof (pintores) / sizeof (pintores[0]);
/* ponteiro para a funo de comparao inicializado a NULL */
PtFComp fcomp = NULL;
fcomp = CompNome; /* ordenao alfabtica ascendente */
Sequential_Sort (pintores, nelem, fcomp, CRESCENTE);
printf ("Ordenao Alfabtica Ascendente\n");
Display (pintores, nelem);
fcomp = CompData; /* ordenao cronolgica decrescente */
Sequential_Sort (pintores, nelem, fcomp, DECRESCENTE);
printf ("Ordenao Cronolgica Decrescente\n");
Display (pintores, nelem);
fcomp = CompNRegisto; /* ordenao crescente por registo */
Sequential_Sort (pintores, nelem, fcomp, CRESCENTE);
printf ("Ordenao Numrica Crescente\n");
Display (pintores, nelem);
return EXIT_SUCCESS;
}
... /* Definio das funes */
37 CAPTULO 6 : PESQUISA E ORDENAO
funes tm o parmetro de entrada modo de tipo inteiro, que indica o modo de actuao
sobre a varivel contadora.
De maneira a aumentar a legibilidade das funes aconselhvel utilizar constantes
simblicas que representam o modo de actuao sobre a varivel contadora. Temos as trs
constantes simblicas seguintes: REP para reportar o valor da varivel contadora; INIC
para inicializar a varivel contadora; e NORM para realizar a operao da funo e para
incrementar o valor da varivel contadora.
Para calcular o nmero de instrues de atribuio utiliza-se uma varivel de durao
permanente na funo SwapCount, tal como se mostra na Figura 6.36. No modo NORM a
funo troca os dois elementos e incrementa o valor da varivel contadora de trs unidades,
para contabilizar as trs instrues de atribuio.
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.
Figura 6.37 - Funo para comparar elementos do agregado com contabilizao de comparaes.
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 */
}
}
unsigned int SwapCount (TElem *x, TElem *y, int modo)
{
static unsigned int cont; /* varivel contadora */
TElem temp;
if (modo == REP) return cont;
else if (modo == INIC) cont = 0;
else if (modo == NORM)
{
temp = *x; *x = *y; *y = temp; /* efectuar a troca */
cont += 3; /* contagem das 3 instrues de atribuio */
}
return 0;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 38
A Figura 6.38 apresenta a verso generalizada e com contabilizao de instrues do
algoritmo de ordenao Sequencial. Antes da ordenao comear, as funes SwapCount e
CCount so invocadas para inicializar as variveis contadoras. Durante a ordenao as
funes so invocadas no modo normal para desempenharem a sua tarefa. Depois da
ordenao terminar, as funes so invocadas para reportar o nmero de comparaes e de
instrues de atribuio efectuadas durante a ordenao, que so armazenados nos
parmetros de sada nc e na respectivamente. Quando as funes so invocadas para
inicializar ou reportar a varivel contadora, os restantes parmetros so passados como
sendo ponteiros nulos.
Figura 6.38 - Algoritmo de ordenao Sequencial generalizado e com contabilizao de operaes.
Como exerccio de treino altere o programa apresentado na Figura 6.35 de modo a utilizar
estas novas verses das funes SwapCount, CCount e Sequential_Sort. Acrescente ainda
ao programa a impresso no monitor do nmero de comparaes e do nmero de trocas,
que so um tero das instrues de atribuio, depois de cada ordenao.
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.
6.8 Leituras recomendadas
x 13 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A: Standish, da editora Addison-Wesley Publishing Company, 1995.
x 7 captulo do livro Data Structures and Algorithm Analysis in C, 2 edio, de Mark
Allen Weiss, da editora Addison-Wesley Publishing Company, 1997.
void Sequential_Sort (TElem seq[], unsigned int nelem,\
PtFComp fcomp, int tord, int *nc, int *na)
{
unsigned int indi, indj;
/* inicializao das variveis contadoras */
CCount ((TElem *) NULL, (TElem *) NULL, (PtFComp) NULL, INIC);
SwapCount ((TElem *) NULL, (TElem *) NULL, INIC);
/* execuo da ordenao com contabilizao das operaes */
for (indi = 0; indi < nelem-1; indi++)
for (indj = indi+1; indj < nelem; indj++)
if (CCount (&seq[indi], &seq[indj], fcomp, NORM) == tord )
SwapCount (&seq[indi], &seq[indj], NORM);
/* relatrio das variveis contadoras */
*nc = CCount ((TElem *) NULL, (TElem *) NULL, (PtFComp) NULL, REP);
*na = SwapCount ((TElem *) NULL, (TElem *) NULL, REP);
}
Captulo 7
FILAS E PILHAS
Sumrio
Este captulo dedicado s estruturas de dados lineares que so as filas e as pilhas.
Apresentamos as implementaes esttica e semiesttica baseadas em agregados e a
implementao dinmica baseada em listas ligadas. Mostramos exemplos de aplicao que
utilizam filas e pilhas como elementos de armazenamento e que tiram partido da sua
organizao interna para a resoluo de problemas. Finalmente apresentamos uma
implementao abstracta, dinmica e com capacidade de mltipla instanciao.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 2
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.
Vamos apresentar as implementaes esttica, semiesttica e dinmica de filas e de pilhas,
considerando que estamos perante estruturas de dados concretas, cujo tipo dos elementos
de armazenamento concretizado pelo utilizador atravs de um ficheiro de interface, que
vamos designar por elemento.h.
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
Figura 7.1 - Ficheiro de interface do elemento constituinte da memria.
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 ).
7.2.1 Implementao esttica
A Figura 7.2 apresenta o ficheiro de interface da implementao esttica de uma fila, que
baseada num agregado de elementos usado de forma circular. Os indicadores de cabea e
cauda da fila so variveis de tipo inteiro positivo.
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.
De seguida vamos apresentar graficamente o comportamento das operaes de colocao
de um elemento na fila e de remoo de um elemento da fila.
/*********** Interface da Estrutura de Dados do Mdulo ***********/
/* Nome: elemento.h */
/* Definio da dimenso da estrutura de dados para o caso das
implementaes esttica e semiesttica e definio do tipo de dados
dos seus elementos. Este ficheiro deve ser modificado para adequar a
definio a cada implementao especfica. */
#ifndef _ELEMENTO
#define _ELEMENTO
/****** Constantes de Parametrizao das Estruturas de Dados ******/
#define N_ELEMENTOS 100 /* nmero de elementos do agregado */
/************* Definio do Tipo de Dados do Elemento *************/
typedef ... TElem; /* tipo de dados dos elementos */
#endif
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 4
Figura 7.2 - Ficheiro de interface da fila esttica.
A Figura 7.3 mostra o estado inicial da fila. Por uma questo de implementao, vamos
considerar que a cauda da fila indica sempre a primeira posio livre para a prxima
operao de colocao de um elemento na fila, pelo que, inicialmente aponta para a posio
0 do agregado, enquanto que a cabea da fila indica sempre a posio da prxima operao
de remoo de um elemento da fila, pelo que, inicialmente aponta para a posio N do
agregado, ou seja, a posio depois do fim do agregado, como indicao de fila vazia. A
Figura 7.3 mostra tambm a colocao do primeiro elemento na fila e o estado aps a
operao. A cabea da fila vai ficar a apontar para o elemento acabado de colocar, que a
posio 0 do agregado, e, fica nesta posio enquanto este elemento no for retirado da fila.
A cauda da fila deslocada para a posio 1 do agregado, que a primeira posio livre
para colocar o prximo elemento.
estado inicial
cabea
da fila
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cauda
da fila
cabea
da fila
colocao do primeiro elemento
Figura 7.3 - Situao inicial da fila e aps a colocao do primeiro elemento.
/******************* Interface da FILA Esttica *******************/
/* Nome : fila_est.h */
#ifndef _FILA_ESTATICA
#define _FILA_ESTATICA
#include "elemento.h" /* caracterizao do tipo elemento da fila */
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define FIFO_EMPTY 4 /* fila vazia */
#define FIFO_FULL 5 /* fila cheia */
/* Aluso s Funes Exportadas pelo Mdulo */
int Fifo_In (TElem elemento);
/* Coloca o elemento elemento na cauda da fila. Valores de retorno:
OK ou FIFO_FULL. */
int Fifo_Out (TElem *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou FIFO_EMPTY. */
#endif
5 CAPTULO 7 : FILAS E PILHAS
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
da fila
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cauda
da fila
cabea
da fila
colocao de um elemento
Figura 7.4 - Colocao de um elemento na fila.
Na segunda situao, que se apresenta na Figura 7.5, a posio seguinte da fila est fora da
dimenso do agregado, pelo que, a cauda da fila passa para o incio do agregado e fica
frente da cabea. O que parece uma situao anmala quando comparada com o
funcionamento de uma fila de espera no dia a dia, em que a cabea est sempre frente da
cauda. Mas, preciso ter em considerao que estamos perante uma fila circular. Para
conseguir este efeito circular da cauda da fila utiliza-se o operador mdulo, para calcular
sempre um valor que est entre 0 e N_ELEMENTOS1. Em alternativa utilizao
circular do agregado, a fila poderia ser implementada de forma linear, o que implicaria
deslocar todos os elementos da fila, para o incio do agregado, sempre que se retirasse um
elemento da fila. Mas, tal implementao seria muito ineficiente para filas grandes.
cabea
da fila
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cauda
da fila
cabea
da fila
colocao de um elemento
Figura 7.5 - Colocao de um elemento na fila com movimentao circular.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6
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.
FILA
CHEIA
cabea da fila
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cabea
da fila
cauda da fila
colocao do ltimo elemento
Figura 7.6 - Colocao do ltimo elemento na fila.
Sempre que um elemento retirado da fila, o indicador de cabea da fila deslocado para o
elemento seguinte. Como j foi referido, esta implementao no a forma habitual de
funcionamento de um fila, onde sempre que o elemento da cabea da fila sai da fila, toda a
fila deslocada para a frente. Existem duas situaes distintas quando se retira um
elemento 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.
cabea
da fila
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cauda
da fila
cabea
da fila
remoo de um elemento
Figura 7.7 - Remoo de um elemento da fila.
7 CAPTULO 7 : FILAS E PILHAS
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.
cabea
da fila
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cauda
da fila
cabea
da fila
remoo de um elemento
Figura 7.8 - Remoo de um elemento da fila com movimentao circular.
A Figura 7.9 apresenta a situao de remoo do ltimo elemento da fila. Nesta situao a
cabea da fila fica a apontar para o mesmo elemento que a cauda da fila, o que significa que
a fila ficou vazia. Enquanto este estado durar, no possvel retirar mais elementos da fila.
Nesta situao, a cabea da fila deve ficar a apontar para a posio N do agregado, ou seja,
a posio depois do fim do agregado, como indicao de fila vazia. Assim, fica reposta o
estado inicial da fila, com a diferena que neste caso, a cauda da fila no est a apontar para
a posio 0 do agregado. Quando se colocar um elemento na fila, na posio apontada pela
cauda da fila, a cabea da fila ser colocada de novo a apontar para o elemento acabado de
colocar.
cauda
da fila
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
Elemento Elemento Elemento Elemento Elemento
FIFO[N-1] FIFO[0]
Elemento
...
Elemento
cabea
da fila
cauda da fila
cabea da fila
cabea
da fila
remoo do ltimo elemento
FILA
VAZIA
Figura 7.9 - Remoo do ltimo elemento da fila.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 8
A Figura 7.10 apresenta o ficheiro de implementao da fila esttica. A estrutura de dados
do mdulo constituda pelo agregado FILA, que um agregado de elementos do tipo
TElem definido no ficheiro de interface elemento.h, e pelas variveis inteiras positivas
head para a cabea da fila e tail para a cauda da fila. A estrutura de dados declarada com
o qualificativo static, de modo a estar protegida do exterior.
Figura 7.10 - Ficheiro de implementao da fila esttica.
7.2.2 Implementao semiesttica
A Figura 7.11 e a Figura 7.12 apresentam respectivamente o ficheiro de interface e o
ficheiro de implementao da fila semiesttica. A implementao semiesttica da fila
baseada no agregado FILA, que um agregado de ponteiros para elementos do tipo de
dados TElem, que est definido no ficheiro de interface elemento.h, e pelas variveis
inteiras positivas head para a cabea da fila e tail para a cauda da fila. Tal como na
implementao esttica, o agregado usado de forma circular.
A implementao em tudo semelhante da implementao esttica. A nica diferena
que quando se coloca um elemento na fila necessrio atribuir memria para o
armazenamento do elemento. A atribuio da memria feita recorrendo funo malloc,
uma vez que, a atribuio feita elemento a elemento. Se no existir memria para essa
atribuio, o elemento no pode ser colocado na fila, pelo que, assinalada a situao de
inexistncia de memria, usando o cdigo de erro NO_MEM. Por outro lado, quando se
retira um elemento da fila necessrio libertar a memria ocupada com o armazenamento
do elemento, recorrendo funo free e colocar o elemento do agregado a apontar para
NULL.
/***************** Implementao da FILA Esttica *****************/
/* Nome : fila_est.c */
#include <stdio.h>
#include "fila_est.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da FILA */
static TElem FILA[N_ELEMENTOS]; /* rea de armazenamento */
static unsigned int head = N_ELEMENTOS; /* cabea da fila */
static unsigned int tail = 0; /* cauda da fila */
/* Definio das Funes */
int Fifo_In (TElem elemento)
{
if (head == tail) return FIFO_FULL;
FILA[tail] = elemento;
if (head == N_ELEMENTOS) head = tail;
tail = ++tail % N_ELEMENTOS;
return OK;
}
int Fifo_Out (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (head == N_ELEMENTOS) return FIFO_EMPTY;
*pelemento = FILA[head];
head = ++head % N_ELEMENTOS;
if (head == tail) head = N_ELEMENTOS;
return OK;
}
9 CAPTULO 7 : FILAS E PILHAS
Figura 7.11 - Ficheiro de interface da fila semiesttica.
Figura 7.12 - Ficheiro de implementao da fila semiesttica.
/*************** Implementao da FILA Semiesttica ***************/
/* Nome : fila_semiest.c */
#include <stdio.h>
#include <stdlib.h>
#include "fila_semiest.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da FILA */
static TElem *FILA[N_ELEMENTOS]; /* rea de armazenamento */
static unsigned int head = N_ELEMENTOS; /* cabea da fila */
static unsigned int tail = 0; /* cauda da fila */
/* Definio das Funes */
int Fifo_In (TElem elemento)
{
if (head == tail) return FIFO_FULL;
if ((FILA[tail] = (TElem *) malloc (sizeof (TElem))) == NULL)
return NO_MEM;
*FILA[tail] = elemento;
if (head == N_ELEMENTOS) head = tail;
tail = ++tail % N_ELEMENTOS;
return OK;
}
int Fifo_Out (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (head == N_ELEMENTOS) return FIFO_EMPTY;
*pelemento = *FILA[head];
free (FILA[head]);
FILA[head] = NULL;
head = ++head % N_ELEMENTOS;
if (head == tail) head = N_ELEMENTOS;
return OK;
}
/***************** Interface da FILA Semiesttica *****************/
/* Nome : fila_semiest.h */
#ifndef _FILA_SEMIESTATICA
#define _FILA_SEMIESTATICA
#include "elemento.h" /* caracterizao do tipo elemento da fila */
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define NO_MEM 3 /* memria esgotada */
#define FIFO_EMPTY 4 /* fila vazia */
#define FIFO_FULL 5 /* fila cheia */
/* Aluso s Funes Exportadas pelo Mdulo */
int Fifo_In (TElem elemento);
/* Coloca o elemento elemento na cauda da fila. Valores de retorno:
OK, FIFO_FULL ou NO_MEM. */
int Fifo_Out (TElem *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou FIFO_EMPTY. */
#endif
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10
7.2.3 Implementao dinmica
A Figura 7.13 apresenta o ficheiro de interface da implementao dinmica de uma fila, que
baseada numa lista ligada de elementos. Uma lista ligada uma estrutura constituda por
elementos, a que vamos chamar ns, ligados atravs de ponteiros. Cada n da lista ligada
constitudo por dois ponteiros, um para o elemento que armazena a informao e outro
para o n seguinte da lista. O ltimo n da lista aponta para NULL, para servir de
indicador de finalizao da fila. A memria para os ns e para os elementos atribuda,
quando um elemento colocado na fila e libertada quando um elemento retirado da fila.
Os indicadores de cabea e cauda da fila so ponteiros. A cabea da fila aponta para o
elemento mais antigo que se encontra na fila e que o primeiro a ser retirado. A cauda da
fila aponta sempre para o elemento mais recente que se encontra na fila e frente do qual
se insere um novo elemento. Quando so ambos ponteiros nulos, sinal que a fila est
vazia. Uma fila dinmica nunca est cheia. Quando muito, pode no existir memria para
continuar a acrescentar-lhe mais elementos. Portanto, as situaes de erro so as mesmas
da implementao semiesttica, com excepo que no existe o cdigo de erro
FIFO_FULL.
Figura 7.13 - Ficheiro de interface da fila dinmica.
Vamos apresentar de forma grfica as operaes de colocao e remoo de elementos
numa fila dinmica. A Figura 7.14 apresenta o estado inicial da fila e o estado aps a
colocao do primeiro elemento. Para colocar um elemento na fila primeiro necessrio
atribuir memria para o armazenamento do n da lista e depois para o armazenamento do
elemento. Se no existir memria para essas atribuies, ento o elemento no pode ser
colocado na fila e assinalada esta situao de erro. Aps a atribuio da memria, o
elemento ligado ao n e o n ligado fila. O n fica a apontar para NULL, uma vez que
o ltimo n da fila, ou seja, ele o elemento finalizador da fila. A cauda da fila posta a
apontar para ele e finalmente, a informao a armazenar copiada para o elemento.
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.
/******************* Interface da FILA Dinmica *******************/
/* Nome : fila_din.h */
#ifndef _FILA_DINAMICA
#define _FILA_DINAMICA
#include "elemento.h" /* caracterizao do tipo elemento da fila */
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define NO_MEM 3 /* memria esgotada */
#define FIFO_EMPTY 4 /* fila vazia */
/* Aluso s Funes Exportadas pelo Mdulo */
int Fifo_In (TElem elemento);
/* Coloca o elemento apontado por elemento na cauda da fila. Valores
de retorno: OK ou NO_MEM. */
int Fifo_Out (TElem *pelemento);
/* Retira o elemento da cabea da fila para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou FIFO_EMPTY. */
#endif
11 CAPTULO 7 : FILAS E PILHAS
PtEle
Elemento
1
PtSeg
cauda
da fila
cabea
da fila
estado inicial
colocao
do primeiro
elemento
cabea
da fila
cauda
da fila
Figura 7.14 - Situao inicial da fila e aps a colocao do primeiro elemento.
A Figura 7.15 apresenta a colocao de mais um elemento na fila. Neste caso, este
elemento tem de ser ligado fila, pelo que, o n do elemento que est apontado pela cauda
e que aponta para NULL, posto a apontar para o novo n, que passa agora a ser o ltimo
elemento da fila. A cauda da fila actualizada e a cabea da fila continua inalterada.
PtEle
Elemento
2
PtSeg
cabea
da fila
cauda
da fila
PtEle
Elemento
1
PtSeg
colocao
de um
elemento
cabea
da fila
PtEle
Elemento
3
PtSeg
cauda
da fila
PtEle
Elemento
1
PtSeg
PtEle
Elemento
2
PtSeg
Figura 7.15 - Colocao de mais um elemento na fila.
A Figura 7.16 apresenta a remoo de um elemento da fila. A informao armazenada no
elemento copiada e depois a cabea da fila colocada a apontar para o elemento seguinte
que vai passar agora a ser a nova cabea da fila. Esta operao feita atribuindo cabea da
fila o valor apontado pelo ponteiro PtSeg, que aponta para o n seguinte da fila. Toda a
memria ocupada pelo elemento e pelo n libertada.
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
remoo
de um
elemento
cabea
da fila
PtEle
Elemento
3
PtSeg
cauda
da fila
PtEle
Elemento
1
PtSeg
PtEle
Elemento
2
PtSeg
PtEle
Elemento
3
PtSeg
cabea
da fila
cauda
da fila
PtEle
Elemento
2
PtSeg
Figura 7.16 - Remoo de um elemento da fila.
PtEle
Elemento
3
PtSeg
cauda
da fila
cabea
da fila
remoo
do ltimo
elemento
cabea
da fila
cauda
da fila
FILA
VAZIA
Figura 7.17 - Remoo do ltimo elemento da fila.
A Figura 7.18 apresenta o ficheiro de implementao da fila dinmica. A estrutura de dados
do mdulo constituda por uma lista ligada de ns do tipo struct no, sendo cada n
constitudo pelo ponteiro pelemento para um elemento do tipo TElem, que est definido
no ficheiro de interface elemento.h, e pelo ponteiro pseg para fazer a ligao do n ao n
seguinte da fila, caso ele exista. Para controlar a fila existem os ponteiros, head e tail
respectivamente para a cabea e para a cauda da fila, que so declarados com o qualificativo
static, de modo a estarem protegidas do exterior. Como a fila inicialmente est vazia, estes
ponteiros so inicializadas a NULL. Quando um elemento colocado na fila, a atribuio
de memria para o seu crescimento, comea pela atribuio de memria para o n, que
caso seja bem sucedida, prossegue com a atribuio de memria para o elemento. Caso no
exista memria para o elemento, a colocao de mais um elemento na fila cancelada e a
memria anteriormente atribuda para o n libertada, antes da funo terminar a sua
execuo com o cdigo de erro NO_MEM. No faz sentido ter um n na fila, seno
podemos colocar tambm o elemento que vai armazenar a informao. O n do novo
elemento aponta para NULL, uma vez que o ltimo elemento da fila. A funo prossegue
com a ligao do ltimo elemento que estava na fila ao novo elemento e com a actualizao
do ponteiro cauda da fila que fica a apontar para o novo elemento. Finalmente feita a
13 CAPTULO 7 : FILAS E PILHAS
cpia da informao para o elemento. Quando um elemento retirado da fila, primeiro
verificado se o ponteiro passado no nulo e s depois que a informao armazenada no
elemento retirada da fila. Caso a fila no esteja vazia, o ponteiro cabea da fila colocado
a apontar para o elemento seguinte. Quando o ltimo elemento retirado da fila, ento a
cabea da fila fica a apontar para NULL, o que significa que a fila ficou vazia, pelo que, a
cauda da fila tambm colocada a apontar para NULL. Depois a memria ocupada pelo
elemento libertada e s depois que memria ocupada pelo n libertada.
Figura 7.18 - Ficheiro de implementao da fila dinmica.
/***************** Implementao da FILA Dinmica *****************/
/* Nome : fila_din.c */
#include <stdio.h>
#include <stdlib.h>
#include "fila_din.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da FILA */
typedef struct no *PtNo;
struct no
{
TElem *pelemento; /* ponteiro para o elemento */
PtNo pseg; /* ponteiro para o n seguinte */
};
static PtNo head = NULL; /* cabea da fila */
static PtNo tail = NULL; /* cauda da fila */
/* Definio das Funes */
int Fifo_In (TElem elemento)
{
PtNo tmp;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (TElem *) malloc (sizeof(TElem))) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pseg = NULL;
if (tail == NULL) head = tmp;
else tail->pseg = tmp;
tail = tmp;
*tail->pelemento = elemento;
return OK;
}
int Fifo_Out (TElem *pelemento)
{
PtNo tmp;
if (pelemento == NULL) return NULL_PTR;
if (head == NULL) return FIFO_EMPTY;
*pelemento = *head->pelemento;
tmp = head;
head = head->pseg;
if (head == NULL) tail = NULL;
free (tmp->pelemento);
free (tmp);
return OK;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14
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).
7.3.1 Implementao esttica
A Figura 7.19 apresenta o ficheiro de interface da implementao esttica de uma pilha, que
baseada num agregado de elementos. O indicador de topo da pilha uma varivel de tipo
inteiro positivo.
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.
Figura 7.19 - Ficheiro de interface da pilha esttica.
/****************** Interface do PILHA Esttica ******************/
/* Nome : pilha_est.h */
#ifndef _PILHA_ESTATICA
#define _PILHA_ESTATICA
#include "elemento.h" /* caracterizao do tipo elemento da pilha */
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define STACK_EMPTY 4 /* pilha vazia */
#define STACK_FULL 5 /* pilha cheia */
/* Aluso s Funes Exportadas pelo Mdulo */
int Stack_Push (TElem elemento);
/* Coloca o elemento apontado por elemento no topo da pilha. Valores
de retorno: OK ou STACK_FULL. */
int Stack_Pop (TElem *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou STACK_EMPTY. */
#endif
15 CAPTULO 7 : FILAS E PILHAS
De seguida, vamos apresentar graficamente o comportamento das operaes de colocao
de um elemento na pilha e de remoo de um elemento da pilha.
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
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
estado inicial colocao do
primeiro elemento
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
Figura 7.20 - Situao inicial da pilha e aps a colocao do primeiro elemento.
Sempre que se coloca um elemento na pilha, o topo da pilha deslocada para a posio
seguinte da pilha, depois da cpia do elemento para a pilha. A Figura 7.21 apresenta a
situao limite de utilizao da pilha, quando se coloca o ltimo elemento na pilha. Nesta
situao o topo da pilha fica a apontar para a posio do agregado de ndice N, o que
significa que a pilha ficou cheia. Enquanto este estado durar, no possvel colocar mais
elementos na pilha.
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
colocao do ltimo elemento
PILHA
CHEIA
Figura 7.21 - Colocao do ltimo elemento na pilha.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 16
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.
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
. topo da
pilha
STACK[1]
remoo de um elemento
Figura 7.22 - Remoo de um elemento da pilha.
A Figura 7.23 apresenta a situao de remoo do ltimo elemento da pilha. Aps a
operao, o topo da pilha fica a apontar para a posio do agregado de ndice 0, o que
significa que ficou reposta o estado inicial da pilha, ou seja, a pilha ficou vazia. Enquanto
este estado durar, no possvel retirar mais elementos da pilha.
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
Elemento
Elemento
Elemento
Elemento
STACK[N-1]
STACK[0]
Elemento
.
.
.
topo da
pilha
STACK[1]
remoo do ltimo elemento
PILHA
VAZIA
Figura 7.23 - Remoo do ltimo elemento da fila.
A Figura 7.24 apresenta o ficheiro de implementao da pilha esttica. A estrutura de dados
do mdulo constituda pelo agregado PILHA, que um agregado de elementos do tipo
TElem, que est definido no ficheiro de interface elemento.h, e pela varivel inteira positiva
top para o topo da pilha. A estrutura de dados declarada com o qualificativo static, de
modo a estar protegida do exterior.
17 CAPTULO 7 : FILAS E PILHAS
Figura 7.24 - Ficheiro de implementao da pilha esttica.
7.3.2 Implementao semiesttica
A Figura 7.25 e a Figura 7.26 apresentam respectivamente o ficheiro de interface e o
ficheiro de implementao da pilha semiesttica. A implementao semiesttica da pilha
baseada no agregado PILHA, que um agregado de ponteiros para elementos do tipo de
dados TElem, que est definido no ficheiro de interface elemento.h, e pela varivel inteira
positiva top para o topo da pilha. O agregado usado tal como na implementao esttica.
A implementao em tudo semelhante da implementao esttica. A nica diferena
que quando se coloca um elemento na pilha necessrio atribuir memria para o
armazenamento do elemento. A atribuio da memria feita recorrendo funo malloc,
uma vez que, a atribuio feita elemento a elemento. Se no existir memria para essa
atribuio, o elemento no pode ser colocado na pilha e assinalada a situao de
inexistncia de memria, usando o cdigo de erro NO_MEM. Por outro lado, quando se
retira um elemento da pilha necessrio libertar a memria ocupada com o
armazenamento do elemento, recorrendo funo free e colocar o elemento do agregado a
apontar para NULL.
/**************** Implementao da PILHA Esttica ****************/
/* Nome : pilha_est.c */
#include <stdio.h>
#include "pilha_est.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da PILHA */
static TElem PILHA[N_ELEMENTOS]; /* rea de armazenamento */
static unsigned int top = 0; /* topo da pilha */
/* Definio das Funes */
int Stack_Push (TElem elemento)
{
if (top == N_ELEMENTOS) return STACK_FULL;
PILHA[top++] = elemento;
return OK;
}
int Stack_Pop (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (top == 0) return STACK_EMPTY;
*pelemento = PILHA[--top];
return OK;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 18
Figura 7.25 - Ficheiro de interface da pilha semiesttica.
Figura 7.26 - Ficheiro de implementao da pilha semiesttica.
/************** Implementao da PILHA Semiesttica **************/
/* Nome : pilha_semiest.c */
#include <stdio.h>
#include <stdlib.h>
#include "pilha_semiest.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da PILHA */
static TElem *PILHA[N_ELEMENTOS]; /* rea de armazenamento */
static unsigned int top = 0; /* topo da pilha */
/* Definio das Funes */
int Stack_Push (TElem elemento)
{
if (top == N_ELEMENTOS) return STACK_FULL;
if ((PILHA [top] = (TElem *) malloc (sizeof (TElem))) == NULL)
return NO_MEM;
*PILHA[top++] = elemento;
return OK;
}
int Stack_Pop (TElem *pelemento)
{
if (pelemento == NULL) return NULL_PTR;
if (top == 0) return STACK_EMPTY;
*pelemento = *PILHA[--top];
free (PILHA[top]);
PILHA[top] = NULL;
return OK;
}
/**************** Interface do PILHA Semiesttica ****************/
/* Nome : pilha_semiest.h */
#ifndef _PILHA_SEMIESTATICA
#define _PILHA_SEMIESTATICA
#include "elemento.h" /* caracterizao do tipo elemento da pilha */
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define NO_MEM 3 /* memria esgotada */
#define STACK_EMPTY 4 /* pilha vazia */
#define STACK_FULL 5 /* pilha cheia */
/* Aluso s Funes Exportadas pelo Mdulo */
int Stack_Push (TElem elemento);
/* Coloca o elemento apontado por elemento no topo da pilha. Valores
de retorno: OK, STACK_FULL ou NO_MEM. */
int Stack_Pop (TElem *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou STACK_EMPTY. */
#endif
19 CAPTULO 7 : FILAS E PILHAS
7.3.3 Implementao dinmica
A Figura 7.27 apresenta o ficheiro de interface da implementao dinmica, que baseada
numa lista ligada de elementos. Uma lista ligada uma estrutura constituda por elementos,
a que vamos chamar ns, ligados atravs de ponteiros. Cada n da lista ligada constitudo
por dois ponteiros, um para o elemento que armazena a informao e outro para o n
anterior da lista. O primeiro n da lista aponta para NULL, para servir de indicador de
finalizao da pilha. A memria para os ns e para os elementos da pilha atribuda,
quando um elemento colocado na pilha e libertada quando um elemento retirado da
pilha. O indicador de topo da pilha um ponteiro, que aponta para o elemento mais
recentemente colocado na pilha e que o primeiro a ser retirado. Quando o topo da pilha
um ponteiro nulo, sinal que a pilha est vazia. Uma pilha dinmica nunca est cheia.
Quando muito, pode no existir memria para continuar a acrescentar-lhe mais elementos.
Portanto, as situaes de erro so as mesmas da implementao semiesttica, com excepo
que no existe o cdigo de erro STACK_FULL.
Figura 7.27 - Ficheiro de interface da pilha dinmica.
Vamos apresentar de forma grfica as operaes de colocao e remoo de elementos
numa pilha dinmica. A Figura 7.28 apresenta o estado inicial da pilha e o estado aps a
colocao do primeiro elemento.
Para colocar um elemento na pilha necessrio atribuir memria, primeiro para o
armazenamento do n da lista e depois para o armazenamento do elemento. Se no existir
memria para essas atribuies, ento o elemento no pode ser colocado na pilha e
assinalada esta situao de erro. Aps a atribuio da memria, o elemento ligado ao n e
o n ligado pilha. O n fica a apontar para o n anterior que o topo da pilha, que
depois actualizado, sendo colocado a apontar para este novo n. Finalmente, a
informao a armazenar copiada para o elemento.
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.
/****************** Interface da PILHA Dinmica ******************/
/* Nome : pilha_din.h */
#ifndef _PILHA_DINAMICA
#define _PILHA_DINAMICA
#include "elemento.h" /* caracterizao do tipo elemento da pilha */
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define NO_MEM 3 /* memria esgotada */
#define STACK_EMPTY 4 /* pilha vazia */
/* Aluso s Funes Exportadas pelo Mdulo */
int Stack_Push (TElem elemento);
/* Coloca o elemento apontado por elemento no topo da pilha. Valores
de retorno: OK ou NO_MEM. */
int Stack_Pop (TElem *pelemento);
/* Retira o elemento do topo da pilha para o elemento apontado por
pelemento. Valores de retorno: OK, NULL_PTR ou STACK_EMPTY. */
#endif
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 20
PtAnt
Elemento
1
PtEle
topo da
pilha
topo da
pilha
estado inicial colocao do
primeiro elemento
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.
PtAnt
Elemento
1
PtEle
topo da
pilha
colocao de um elemento
PtAnt
Elemento
2
PtEle
PtAnt
Elemento
1
PtEle
topo da
pilha
Figura 7.29 - Colocao de mais um elemento na pilha.
A Figura 7.30 apresenta a remoo de um elemento da pilha. A informao armazenada no
elemento copiada. Depois o topo da pilha colocada a apontar para o elemento anterior
que vai passar agora a ser o novo topo da pilha. Esta operao feita atribuindo ao topo da
pilha o valor apontado pelo ponteiro PtAnt, que aponta para o n anterior da pilha. Depois
toda a memria ocupada pelo elemento e pelo n libertada.
PtAnt
Elemento
1
PtEle
topo da
pilha
remoo de um elemento
PtAnt
Elemento
2
PtEle
PtAnt
Elemento
1
PtEle
topo da
pilha
Figura 7.30 - Remoo de um elemento da pilha.
21 CAPTULO 7 : FILAS E PILHAS
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.
PtAnt
Elemento
1
PtEle
topo da
pilha
topo da
pilha
remoo do ltimo elemento
PILHA
VAZIA
Figura 7.31 - Remoo do ltimo elemento da pilha.
A Figura 7.32 apresenta o ficheiro de implementao da pilha dinmica. A estrutura de
dados do mdulo constituda por uma lista ligada de ns do tipo struct no, sendo cada
n constitudo pelo ponteiro pelemento para um elemento do tipo TElem, que est
definido no ficheiro de interface elemento.h, e pelo ponteiro pant para fazer a ligao do
n ao n anterior da pilha, caso ele exista. Para controlar a pilha existe o ponteiro top para
o topo da pilha, que declarado com o qualificativo static, de modo a estar protegido do
exterior. Como a pilha inicialmente est vazia, este ponteiro inicializado a NULL.
Quando um elemento colocado na pilha, a atribuio de memria para o seu crescimento,
comea pela atribuio de memria para o n, que caso seja bem sucedida, prossegue com
a atribuio de memria para o elemento. Caso no exista memria para o elemento, a
colocao de mais um elemento na pilha cancelada e a memria anteriormente atribuda
para o n libertada, antes da funo terminar a sua execuo com o cdigo de erro
NO_MEM. No faz sentido ter um n na pilha, seno podemos colocar tambm o
elemento que vai armazenar a informao. A funo continua com a ligao do novo
elemento ao ltimo elemento que estava na pilha, pelo que, o n do primeiro elemento da
pilha fica a apontar para NULL. Depois feita a actualizao do ponteiro topo da pilha que
fica a apontar para o novo elemento e finalmente feita a cpia da informao para o
elemento.
Quando um elemento retirado da pilha, primeiro verificado se o ponteiro passado no
nulo e s depois que a informao armazenada no elemento retirada da pilha. O
ponteiro topo da pilha colocado a apontar para o elemento anterior. Quando o ltimo
elemento retirado da pilha, ento o topo da pilha fica a apontar para NULL, o que
significa que a pilha ficou vazia. Depois a memria ocupada pelo elemento libertada e s
depois que a memria ocupada pelo n da pilha libertada.
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 22
Figura 7.32 - Ficheiro de implementao da pilha dinmica.
7.4 Exemplos de aplicao de filas e pilhas
Uma das aplicaes das pilhas a anlise e processamento de estruturas imbricadas.
Estruturas imbricadas so estruturas que so compostas internamente por outras estruturas
do mesmo tipo, onde existe a necessidade de garantir que uma estrutura interna finalizada
antes da estrutura externa. Um exemplo tpico de uma estrutura imbricada uma expresso
aritmtica que normalmente composta por subexpresses aritmticas entre parnteses
/**************** Implementao da PILHA Dinmica ****************/
/* Nome : pilha_din.c */
#include <stdio.h>
#include <stdlib.h>
#include "pilha_din.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da PILHA */
typedef struct no *PtNo;
struct no
{
TElem *pelemento; /* ponteiro para o elemento */
PtNo pant; /* ponteiro para o n anterior */
};
static PtNo top = NULL; /* topo da pilha */
/* Definio das Funes */
int Stack_Push (TElem elemento)
{
PtNo tmp;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (TElem *) malloc (sizeof(TElem))) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pant = top;
top = tmp;
*top->pelemento = elemento;
return OK;
}
int Stack_Pop (TElem *pelemento)
{
PtNo tmp;
if (pelemento == NULL) return NULL_PTR;
if (top == NULL) return STACK_EMPTY;
*pelemento = *top->pelemento;
tmp = top;
top = top->pant;
free (tmp->pelemento);
free (tmp);
return OK;
}
23 CAPTULO 7 : FILAS E PILHAS
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.
A Figura 7.34 apresenta o programa que implementa este algoritmo. De maneira a
estruturar melhor o programa, foi implementada uma funo inteira que compara o
parntese esquerdo com o parntese direito e que devolve 1, caso eles sejam equivalentes e
0 no caso contrrio. Neste caso a pilha utilizada a implementao esttica, mas podia ser
qualquer outra implementao. Para utilizar a pilha, primeiro preciso editar o ficheiro de
interface elemento.h, e definir o nmero de elementos da estrutura de dados da pilha com
um valor suficiente para armazenar os parnteses da expresso, que no pior caso podem ser
tantos quantos o nmero de caracteres da expresso e definir tambm o tipo dos elementos
da pilha como sendo do tipo char. Depois o mdulo compilado, com a opo c, para
ficar concretizado para uma pilha de caracteres. Finalmente, o programa compilado,
indicando no comando de compilao o ficheiro objecto do mdulo pilha_est.o. A Figura
7.33 apresenta o resultado de execuo do programa para vrias expresses.
Figura 7.33 - Exemplos de utilizao do programa.
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)]}/[(A+B)*C]
Expresso com parnteses balanceados
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)}]/[(A+B)*C]
Parnteses [ e } discordantes
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)]}/[(A+B)*C]}
Mais parnteses direitos do que esquerdos
Expresso algbrica -> {A+B*[C/(F+A)+(C+D)*(EF)]}/[(A+B)*C
Mais parnteses esquerdos do que direitos
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 24
Figura 7.34 - Programa que verifica o balanceamento dos parnteses numa expresso.
#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);
int main (void)
{
char exp[81], /* expresso lida do teclado para analisar */
cpilha; /* parntese esquerdo armazenado na pilha */
int nc, /* nmero de smbolos da expresso */
c, /* contador do ciclo for */
st; /* estado de realizao da operao da pilha */
printf ("Expresso algbrica -> ");
scanf ("%80s", exp);
nc = strlen (exp);
for (c = 0; c < nc; c++)
if ( exp[c] == '(' || exp[c] == '[' || exp[c] == '{' )
Stack_Push (exp[c]);
else if ( exp[c] == ')' || exp[c] == ']' || exp[c] == '}' )
{
st = Stack_Pop (&cpilha);
if (st == STACK_EMPTY)
{
printf ("Mais parnteses direitos do que esquerdos\n");
return EXIT_SUCCESS;
}
else if ( !parenteses_equivalentes (cpilha, exp[c]) )
{
printf ("Parnteses %c e %c discordantes\n",\
cpilha, exp[c]);
return EXIT_SUCCESS;
}
}
st = Stack_Pop (&cpilha);
if (st == STACK_EMPTY)
printf ("Expresso com parnteses balanceados\n");
else printf ("Mais parnteses esquerdos do que direitos\n");
return EXIT_SUCCESS;
}
int parenteses_equivalentes (char pesquerdo, char pdireito)
{
switch (pesquerdo)
{
case '(' : return pdireito == ')'; break;
case '[' : return pdireito == ']'; break;
case '{' : return pdireito == '}'; break;
default : return 0;
}
}
25 CAPTULO 7 : FILAS E PILHAS
Pretende-se determinar se uma palavra um palndromo, que uma palavra que se l da
mesma maneira da esquerda para a direita e da direita para esquerda. Ou seja, aquilo que
normalmente se designa por capicua. Para que uma palavra seja uma capicua, os seus
caracteres esto reflectidos em relao ao carcter central. Uma forma de detectar esta
caracterstica, consiste em comparar cada carcter com o seu simtrico. Por outras palavras,
se a palavra for comparada com a palavra invertida, carcter a carcter, temos que os
caracteres so coincidentes. Podemos fazer esta deteco utilizando uma fila e uma pilha
conjuntamente. O algoritmo o seguinte. Coloca-se todos os caracteres da palavra na fila e
na pilha. Depois retira-se o carcter da cabea da fila e o carcter do topo da pilha e
comparam-se. Desta maneira, compara-se o primeiro carcter da palavra, ou seja, o carcter
que se encontra na fila com o ltimo carcter da palavra, ou seja, o carcter que se encontra
na pilha. Se os caracteres forem iguais at se esgotar os caracteres da fila e da pilha, ento a
palavra uma capicua. Caso contrrio, a palavra no uma capicua.
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.
Figura 7.35 - Programa que verifica se uma palavra uma capicua.
#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 */
int main (void)
{
char palavra[81], /* palavra lida do teclado */
cfila, /* carcter armazenado na fila */
cpilha; /* carcter armazenado na pilha */
int nc, c;
printf ("Palavra -> "); scanf ("%80s", palavra);
nc = strlen (palavra);
for (c = 0; c < nc; c++)
{
Fifo_In (palavra[c]); Stack_Push (palavra[c]);
}
for (c = 0; c < nc; c++)
{
Fifo_Out (&cfila); Stack_Pop (&cpilha);
if (cfila != cpilha)
{
printf ("A palavra no uma capicua\n"); return EXIT_SUCCESS;
}
}
printf ("A palavra uma capicua\n");
return EXIT_SUCCESS;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 26
7.5 Implementaes abstractas
Uma alternativa para poder reutilizar um mdulo de memria sem a necessidade de o
recompilar sempre que o utilizador precisa de mudar o tipo dos elementos da memria,
consiste na criao de um mdulo abstracto. Um mdulo abstracto pode ser concretizado
durante a execuo do programa, permitindo assim a sua reutilizao para diferentes tipos
de dados. Para que um mdulo de memria seja reutilizvel sem limitaes, ele deve
tambm ter uma implementao dinmica para que a sua capacidade de armazenamento
seja ilimitada. Por outro lado, para implementar certos algoritmos, por vezes existe a
necessidade de utilizar mais do que uma memria do mesmo tipo, pelo que, o mdulo de
memria tambm deve ter a capacidade de mltipla instanciao. Portanto, um mdulo de
memria abstracto, com uma implementao dinmica e com capacidade de mltipla
instanciao um mdulo muito verstil e com uma reutilizao praticamente ilimitada.
Com a linguagem C possvel criar um mdulo de memria usando ponteiros de tipo
void, uma vez que ele pode ser colocado a apontar para qualquer tipo de dados. Portanto,
estamos perante um ponteiro genrico que possibilita a construo de um mdulo de
memria, em que o seu elemento de armazenamento pode ser de qualquer tipo de dados.
Aquando da utilizao do mdulo preciso concretiz-lo para o tipo de dados necessrio
aplicao. Esta operao feita atravs de uma funo de criao do mdulo, que
responsvel pela caracterizao do tamanho em bytes do elemento de armazenamento da
memria. Para permitir manipular estruturas de dados, sem conhecimento do seu tipo
concreto, a biblioteca de execuo ANSI string providencia a funo memcpy, que copia
blocos de memria, sem atribuir qualquer interpretao ao contedo dos bytes copiados.
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.
A funo de criao da fila Fifo_Create, cria a estrutura de suporte da fila, coloca os
ponteiros para a cabea e a cauda da fila a apontar para NULL, ou seja, cria uma fila sem
elementos, concretiza o tipo de elementos atravs da especificao do seu tamanho em bytes
e devolve a referncia da estrutura de suporte da fila criada.
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.36 - Ficheiro de interface da fila abstracta com mltipla instanciao.
/****************** Interface da FILA Abstracta ******************/
/* Nome : fila_abs.h */
#ifndef _FILA_ABSTRACTA
#define _FILA_ABSTRACTA
typedef struct fifo *PtFifo;
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define NULL_SIZE 2 /* tamanho nulo */
#define NO_MEM 3 /* memria esgotada */
#define FIFO_EMPTY 4 /* fila vazia */
#define NO_FIFO 7 /* no foi instanciada qualquer fila */
/* Aluso s Funes Exportadas pelo Mdulo */
PtFifo Fifo_Create (unsigned int sz);
/* Concretiza a fila para elementos de sz bytes. Devolve a
referncia da fila criada ou NULL em caso de erro. */
int Fifo_Destroy (PtFifo *fila);
/* Destri a fila referenciada por fila e coloca a referncia a
NULL. Valores de retorno: OK ou NO_FIFO. */
int Fifo_In (PtFifo fila, void *pelemento);
/* Coloca o elemento apontado por pelemento na cauda da fila
referenciada por fila. Valores de retorno: OK, NO_FIFO, NULL_PTR ou
NO_MEM. */
int Fifo_Out (PtFifo fila, void *pelemento);
/* Retira o elemento da cabea da fila referenciada por fila, para o
elemento apontado por pelemento. Valores de retorno: OK, NO_FIFO,
NULL_PTR ou FIFO_EMPTY. */
#endif
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 28
Figura 7.37 - Ficheiro de implementao da fila abstracta com mltipla instanciao (1 parte).
/**************** Implementao da FILA Abstracta ****************/
/* Nome : fila_abs.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fila_abs.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da FILA */
typedef struct no *PtNo;
struct no
{
void *pelemento; /* ponteiro para o elemento */
PtNo pseg; /* ponteiro para o n seguinte */
};
struct fifo
{
unsigned int size;/* tamanho em nmero de bytes de cada elemento */
PtNo head; /* cabea da fila */
PtNo tail; /* cauda da fila */
};
/* Definio das Funes */
PtFifo Fifo_Create (unsigned int sz)
{
PtFifo fifo;
if (sz == 0) return NULL;
if ((fifo = (PtFifo) malloc (sizeof (struct fifo))) == NULL)
return NULL;
fifo->size = sz;
fifo->head = NULL;
fifo->tail = NULL;
return fifo;
}
int Fifo_Destroy (PtFifo *fila)
{
PtFifo fifo = *fila; PtNo tmp;
if (fifo == NULL) return NO_FIFO;
while ( fifo->head != NULL )
{
tmp = fifo->head;
fifo->head = fifo->head->pseg;
free (tmp->pelemento);
free (tmp);
}
free (fifo);
*fila = NULL;
return OK;
}
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.
int Fifo_In (PtFifo fila, void *pelemento)
{
PtFifo fifo = fila; PtNo tmp;
if (fifo == NULL) return NO_FIFO;
if (pelemento == NULL) return NULL_PTR;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (void *) malloc (fifo->size)) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pseg = NULL;
if (fifo->tail == NULL) fifo->head = tmp;
else fifo->tail->pseg = tmp;
fifo->tail = tmp;
memcpy (fifo->tail->pelemento, pelemento, fifo->size);
return OK;
}
int Fifo_Out (PtFifo fila, void *pelemento)
{
PtFifo fifo = fila; PtNo tmp;
if (fifo == NULL) return NO_FIFO;
if (pelemento == NULL) return NULL_PTR;
if (fifo->head == NULL) return FIFO_EMPTY;
memcpy (pelemento, fifo->head->pelemento, fifo->size);
tmp = fifo->head;
fifo->head = fifo->head->pseg;
if (fifo->head == NULL) fifo->tail = NULL;
free (tmp->pelemento);
free (tmp);
return OK;
}
PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 30
A funo de criao da pilha Stack_Create, cria a estrutura de suporte da pilha, coloca o
ponteiro para o topo da pilha a apontar para NULL, ou seja, cria uma pilha sem elementos,
concretiza o tipo de elementos atravs da especificao do seu tamanho em bytes e devolve
a referncia da estrutura de suporte da pilha criada.
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 colocao de elementos na pilha Stack_Push comea por assegurar que a
pilha existe e depois comporta-se tal como na implementao dinmica. Mas, a cpia do
elemento para a pilha feita pela funo memcpy indicando o ponteiro para o elemento a
colocar na pilha, o ponteiro para o topo da pilha e o nmero de bytes a copiar, que est
armazenado no campo que especifica o tamanho dos elementos da pilha. Desta maneira
possvel manipular os elementos da pilha sem que se saiba de que tipo eles so.
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.39 - Ficheiro de interface da pilha abstracta com mltipla instanciao.
/****************** Interface da PILHA Abstracta ******************/
/* Nome : pilha_abs.h */
#ifndef _PILHA_ABSTRACTA
#define _PILHA_ABSTRACTA
typedef struct stack *PtStack;
/* Definio de Constantes */
#define OK 0 /* operao realizada com sucesso */
#define NULL_PTR 1 /* ponteiro nulo */
#define NULL_SIZE 2 /* tamanho nulo */
#define NO_MEM 3 /* memria esgotada */
#define STACK_EMPTY 4 /* pilha vazia */
#define NO_STACK 7 /* no foi instanciada qualquer pilha */
/* Aluso s Funes Exportadas pelo Mdulo */
PtStack Stack_Create (unsigned int sz);
/* Concretiza a pilha para elementos de sz bytes. Devolve a
referncia da pilha criada ou NULL em caso de erro. */
int Stack_Destroy (PtStack *pilha);
/* Destri a pilha referenciada por pilha e coloca a referncia a
NULL. Valores de retorno: OK ou NO_STACK. */
int Stack_Push (PtStack pilha, void *pelemento);
/* Coloca o elemento apontado por pelemento no topo da pilha
referenciada por pilha. Valores de retorno: OK, NO_STACK, NULL_PTR
ou NO_MEM. */
int Stack_Pop (PtStack pilha, void *pelemento);
/* Retira o elemento do topo da pilha referenciada por pilha, para o
elemento apontado por pelemento. Valores de retorno: OK, NO_STACK,
NULL_PTR ou STACK_EMPTY. */
#endif
31 CAPTULO 7 : FILAS E PILHAS
Figura 7.40 - Ficheiro de implementao da pilha abstracta com mltipla instanciao (1 parte).
/**************** Implementao da PILHA Abstracta ****************/
/* Nome : pilha_abs.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pilha_abs.h" /* Ficheiro de interface do mdulo */
/* Definio da Estrutura de Dados Interna da PILHA */
typedef struct no *PtNo;
struct no
{
void *pelemento; /* ponteiro para o elemento */
PtNo pant; /* ponteiro para o n anterior */
};
struct stack
{
unsigned int size;/* tamanho em nmero de bytes de cada elemento */
PtNo top; /* topo da pilha */
};
/* Definio das Funes */
PtStack Stack_Create (unsigned int sz)
{
PtStack stack;
if (sz == 0) return NULL;
if ((stack = (PtStack) malloc (sizeof (struct stack))) == NULL)
return NULL;
stack->size = sz;
stack->top = NULL;
return stack;
}
int Stack_Destroy (PtStack *pilha)
{
PtStack stack = *pilha; PtNo tmp;
if (stack == NULL) return NO_STACK;
while (stack->top != NULL)
{
tmp = stack->top;
stack->top = stack->top->pant;
free (tmp->pelemento);
free (tmp);
}
free (stack);
*pilha = NULL;
return OK;
}
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.
int Stack_Push (PtStack pilha, void *pelemento)
{
PtStack stack = pilha; PtNo tmp;
if (stack == NULL) return NO_STACK;
if (pelemento == NULL) return NULL_PTR;
if ((tmp = (PtNo) malloc (sizeof (struct no))) == NULL)
return NO_MEM;
if ((tmp->pelemento = (void *) malloc (stack->size)) == NULL)
{
free (tmp); return NO_MEM;
}
tmp->pant = stack->top;
stack->top = tmp;
memcpy (stack->top->pelemento, pelemento, stack->size);
return OK;
}
int Stack_Pop (PtStack pilha, void *pelemento)
{
PtStack stack = pilha; PtNo tmp;
if (stack == NULL) return NO_STACK;
if (pelemento == NULL) return NULL_PTR;
if (stack->top == NULL) return STACK_EMPTY;
memcpy (pelemento, stack->top->pelemento, stack->size);
tmp = stack->top;
stack->top = stack->top->pant;
free (tmp->pelemento);
free (tmp);
return OK;
}
33 CAPTULO 7 : FILAS E PILHAS
Figura 7.42 - Exemplo de utilizao da pilha abstracta com mltipla instanciao.
7.6 Leituras recomendadas
x 7 captulo do livro Data Structures, Algorithms and Software Principles in C, de
Thomas A. Standish, da editora Addison-Wesley Publishing Company, 1995.
#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;
}