Escolar Documentos
Profissional Documentos
Cultura Documentos
Apii Martinez
Apii Martinez
2011
S UMRIO
1 Recurso
1.1
Definio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.1
Algoritmos e programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2
10
2.2.1
13
2.3
15
2.4
Moral da histria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
21
3.1
21
3.2
22
3.2.1
Definio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
3.2.2
Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
4 Busca
29
4.1
Busca seqencial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
4.2
30
37
5.1
Problema da ordenao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
5.2
37
5.3
Mtodo da seleo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
5.4
Mtodo da insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
41
i
6.1
41
6.2
Problema da intercalao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
6.3
43
47
7.1
Problema da separao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
7.2
49
8 Listas de prioridades
8.1
53
Heaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
8.1.1
55
8.1.2
Construo de um max-heap . . . . . . . . . . . . . . . . . . . . . . . . . .
57
8.1.3
59
8.2
Listas de prioridades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
8.3
63
65
9.1
Variveis ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
9.2
67
9.3
Ponteiros em expresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
10 Programas extensos
75
10.1 Arquivos-fontes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
10.2 Arquivos-cabealhos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
80
11 Ponteiros e funes
83
83
86
12 Ponteiros e vetores
89
89
93
94
13 Ponteiros e matrizes
101
107
119
125
137
151
169
177
185
193
201
215
223
AULA
R ECURSO
Recurso um conceito fundamental em computao. Sua compreenso nos permite construir programas elegantes, curtos e poderosos. Muitas vezes, o equivalente no-recursivo
muito mais difcil de escrever, ler e compreender que a soluo recursiva, devido em especial
estrutura recursiva intrnseca do problema. Por outro lado, uma soluo recursiva tem como
principal desvantagem maior consumo de memria, na maioria das vezes muito maior que
uma funo no-recursiva equivalente.
Na linguagem C, uma funo recursiva aquela que contm em seu corpo uma ou mais
chamadas a si mesma. Naturalmente, uma funo deve possuir pelo menos uma chamada proveniente de uma outra funo externa. Uma funo no-recursiva, em contrapartida, aquela
para qual todas as suas chamadas so externas. Nesta aula construiremos funes recursivas
para solues de problemas.
Este texto baseado nas referncias [2, 7].
1.1 Definio
De acordo com [2], em muitos problemas computacionais encontramos a seguinte propriedade: cada entrada do problema contm uma entrada menor do mesmo problema. Dessa
forma, dizemos que esses problemas tm uma estrutura recursiva. Para resolver um problema
como esse, usamos em geral a seguinte estratgia:
se a entrada do problema pequena ento
resolva-a diretamente;
seno,
reduza-a a uma entrada menor do mesmo problema,
aplique este mtodo entrada menor
e volte entrada original.
A aplicao dessa estratgia produz um algoritmo recursivo, que implementado na linguagem C torna-se um programa recursivo ou um programa que contm uma ou mais funes
recursivas.
Uma funo recursiva aquela que possui, em seu corpo, uma ou mais chamadas a si
mesma. Uma chamada de uma funo a si mesma dita uma chamada recursiva. Como
sabemos, uma funo deve possuir ao menos uma chamada proveniente de uma outra funo,
externa a ela. Se a funo s possui chamadas externas a si, ento chamada de funo norecursiva. No semestre anterior, construimos apenas funes no-recursivas.
1
R ECURSO
Em geral, a toda funo recursiva corresponde uma outra no-recursiva que executa exatamente a mesma computao. Como alguns problemas possuem estrutura recursiva natural,
funes recursivas so facilmente construdas a partir de suas definies. Alm disso, a demonstrao da correo de um algoritmo recursivo facilitada pela relao direta entre sua
estrutura e a induo matemtica. Outras vezes, no entanto, a implementao de um algoritmo
recursivo demanda um gasto maior de memria, j que durante seu processo de execuo muitas informaes devem ser guardadas na sua pilha de execuo.
1.2 Exemplos
Um exemplo clssico de uma funo recursiva aquela que computa o fatorial de um nmero inteiro n > 0. A idia da soluo atravs de uma funo recursiva baseada na frmula
mostrada a seguir:
1,
se n 6 1 ,
n! =
n (n 1)! , caso contrrio .
A funo recursiva fat , que calcula o fatorial de um dado nmero inteiro no negativo n,
mostrada a seguir:
A sentena return pode aparecer em qualquer ponto do corpo de uma funo e por isso
podemos escrever a funo fat como a seguir:
Geralmente, preferimos a primeira verso implementada acima, onde existe apenas uma
sentena com a palavra reservada return posicionada no final do corpo da funo fat . Essa
maneira de escrever funes recursivas evita confuso e nos permite seguir o fluxo de execuo
FACOM
UFMS
1.2 E XEMPLOS
dessas funes mais naturalmente. No entanto, a segunda verso da funo fat apresentada
acima equivalente primeira e isso significa que tambm vlida. Alm disso, essa segunda
soluo mais compacta e usa menos memria, j que evita o uso de uma varivel. Por tudo
isso, essa segunda forma de escrever funes recursivas muito usada por programadores(as)
mais experientes.
A execuo da funo fat se d da seguinte forma. Imagine que uma chamada fat(3)
foi realizada. Ento, temos ilustrativamente a seguinte situao:
fat(3)
p
fat(2)
p
fat(1)
p
devolve 1
x
devolve 2 1 = 2 fat(1)
x
devolve 3 2 = 3 fat(2)
/* Recebe um nmero inteiro n > 0 e um vetor v de nmeros inteiros com n elementos e devolve um elemento mximo de v */
int maximo(int n, int v[MAX])
{
int aux;
if (n == 1)
return v[0];
else {
aux = maximo(n-1, v);
if (aux > v[n-1])
return aux;
else
return v[n-1];
}
}
Segundo P. Feofiloff [2], para verificar que uma funo recursiva est correta, devemos seguir o seguinte roteiro, que significa mostrar por induo a correo de um algoritmo ou programa:
FACOM
UFMS
R ECURSO
Ento, por hiptese de induo, sabemos que a funo maximo devolve um maior elemento no
vetor v contendo n 1 elementos. Esse elemento, por conta da sentena acima, armazenado
ento na varivel aux . A estrutura condicional que se segue na funo maximo compara o
valor armazenado em aux com o valor armazenado em v[n-1] , o ltimo elemento do vetor
v. Um maior valor entre esses dois valores ser ento devolvido pela funo. Dessa forma,
a funo maximo devolve corretamente o valor de um maior elemento em um vetor com n
nmeros inteiros.
Exerccios
1.1 A n-sima potncia de um nmero x, denotada por xn , pode ser computada recursivamente observando a seguinte a frmula:
1,
se n = 0 ,
n
x =
n1
xx
, se n > 1 .
Considere neste exerccio que x e n so nmeros inteiros.
FACOM
UFMS
1.2 E XEMPLOS
FACOM
UFMS
R ECURSO
1.2 O que faz a funo abaixo?
void imprime_alguma_coisa(int n)
{
if (n != 0) {
imprime_alguma_coisa(n / 2);
printf("%c", 0 + n % 2);
}
}
(a) Escreva uma funo recursiva que receba dois nmeros inteiros positivos e devolva
o mximo divisor comum entre eles usando o algoritmo de Euclides.
(b) Escreva um programa que receba dois nmeros inteiros e calcule o mximo divisor
comum entre eles. Use a funo do item (a).
1.4
que receba um nmero inteiro n > 0 e um vetor v de nmeros com ponto flutuante
com n elementos, e calcule e devolva a soma desses nmeros.
(b) Usando a funo do item anterior, escreva um programa que receba um nmero
inteiro n, com n > 1, e mais n nmeros reais e calcule a soma desses nmeros.
1.5
F1 = 1
F = 1
2
Fi = Fi1 + Fi2 , para i > 3.
(a) Escreva uma funo recursiva com a seguinte interface:
int Fib(int i)
UFMS
1.2 E XEMPLOS
(b) Escreva um programa que receba um nmero inteiro i > 1 e imprima o termo Fi da
seqncia de Fibonacci. Use a funo do item (a).
1.7 O piso de um nmero inteiro positivo x o nico inteiro i tal que i 6 x < i + 1. O piso
de x denotado por x.
Segue uma amostra de valores da funo log2 n:
n
log2 n
15
3
16
4
31
4
32
5
63
5
64
6
127
6
128
7
255
7
256
8
511
8
512
9
(b) Escreva um programa que receba um nmero inteiro n > 1 e imprima log2 n. Use
a funo do item (a).
1.8 Considere o seguinte processo para gerar uma seqncia de nmeros. Comece com um
inteiro n. Se n par, divida por 2. Se n mpar, multiplique por 3 e some 1. Repita esse
processo com o novo valor de n, terminando quando n = 1. Por exemplo, a seqncia de
nmeros a seguir gerada para n = 22:
22
11
34
17
52
26
13
40
20
10
16
conjecturado que esse processo termina com n = 1 para todo inteiro n > 0. Para uma
entrada n, o comprimento do ciclo de n o nmero de elementos gerados na seqncia.
No exemplo acima, o comprimento do ciclo de 22 16.
(a) Escreva uma funo no-recursiva com a seguinte interface:
int ciclo(int n)
que receba um nmero inteiro positivo n, mostre a seqncia gerada pelo processo
descrito acima na sada e devolva o comprimento do ciclo de n.
(b) Escreva uma verso recursiva da funo do item (a) com a seguinte interface:
int cicloR(int n)
que receba um nmero inteiro positivo n, mostre a seqncia gerada pelo processo
descrito acima na sada e devolva o comprimento do ciclo de n.
(c) Escreva um programa que receba um nmero inteiro n > 1 e determine a seqncia
gerada por esse processo e tambm o comprimento do ciclo de n. Use as funes em
(a) e (b) para testar.
FACOM
UFMS
R ECURSO
1.9 Podemos calcular a potncia xn de uma maneira mais eficiente. Observe primeiro que se
n uma potncia de 2 ento xn pode ser computada usando seqncias de quadrados.
Por exemplo, x4 o quadrado de x2 e assim x4 pode ser computado usando somente duas
multiplicaes ao invs de trs. Esta tcnica pode ser usada mesmo quando n no uma
potncia de 2, usando a seguinte frmula:
se n = 0,
1,
n
n/2
2
x =
(1.1)
(x ) , se n par,
x xn1 , se n mpar.
(a) Escreva uma funo com interface
int potencia(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva xn usando a frmula (1.1).
(b) Escreva um programa que receba dois nmeros inteiros a e b e imprima o valor de
ab .
FACOM
UFMS
AULA
E FICINCIA
DE ALGORITMOS E
PROGRAMAS
Medir a eficincia de um algoritmo ou programa significa tentar predizer os recursos necessrios para seu funcionamento. O recurso que temos mais interesse neste momento o tempo
de execuo embora a memria, a comunicao e o uso de portas lgicas tambm podem ser de
interesse. Na anlise de algoritmos e/ou programas alternativos para soluo de um mesmo
problema, aqueles mais eficientes de acordo com algum desses critrios so em geral escolhidos como melhores. Nesta aula faremos uma discusso inicial sobre eficincia de algoritmos e
programas tendo como base as referncias [1, 2, 8].
10
E FICINCIA
DE ALGORITMOS E PROGRAMAS
UFMS
2.2 A NLISE
DE ALGORITMOS E PROGRAMAS
11
A anlise de um programa pode ser uma tarefa desafiadora envolvendo diversas ferramentas matemticas tais como combinatria discreta, teoria das probabilidades, lgebra e etc.
Como o comportamento de um programa pode ser diferente para cada entrada possvel, precisamos de uma maneira que nos possibilite resumir esse comportamento em frmulas matemticas simples e de fcil compreenso.
Mesmo com a conveno de um modelo fixo de computao para analisar um programa,
ainda precisamos fazer muitas escolhas para decidir como expressar nossa anlise. Um dos
principais objetivos encontrar uma forma de expresso abstrata que simples de escrever
e manipular, que mostra as caractersticas mais importantes das necessidades do programa e
exclui os detalhes mais tediosos.
No difcil notar que a anlise do programa 2.1 depende do nmero de elementos fornecidos na entrada. Isso significa que procurar um elemento x em um conjunto C com milhares
de elementos certamente gasta mais tempo que procur-lo em um conjunto C com apenas trs
elementos. Alm disso, importante tambm notar que o programa 2.1 gasta diferentes quantidades de tempo para buscar um elemento em conjuntos de mesmo tamanho, dependendo de
como os elementos nesses conjuntos esto dispostos, isto , da ordem como so fornecidos na
entrada. Como fcil perceber tambm, em geral o tempo gasto por um programa cresce com
o tamanho da entrada e assim comum descrever o tempo de execuo de um programa como
uma funo do tamanho de sua entrada.
Por tamanho da entrada queremos dizer, quase sempre, o nmero de itens na entrada.
Por exemplo, o vetor C com n nmeros inteiros onde a busca de um elemento ser realizada
tem n itens ou elementos. Em outros casos, como na multiplicao de dois nmeros inteiros, a
melhor medida para o tamanho da entrada o nmero de bits necessrios para representar essa
entrada na base binria. O tempo de execuo de um programa sobre uma entrada particular
o nmero de operaes primitivas, ou passos, executados por ele. Quanto mais independente
da mquina a definio de um passo, mais conveniente para anlise de tempo dos algoritmos
e programas.
Considere ento que uma certa quantidade constante de tempo necessria para executar
cada linha de um programa. Uma linha pode gastar uma quantidade de tempo diferente de
outra linha, mas consideramos que cada execuo da i-sima linha gasta tempo ci , onde ci
uma constante positiva.
Iniciamos a anlise do programa 2.1 destacando que o aspecto mais importante para sua
anlise o tempo gasto com a busca do elemento x no vetor C contendo n nmeros inteiros,
descrita entre a entrada de dados (leitura) e a sada (impresso dos resultados). As linhas
restantes contm diretivas de pr-processador, cabealho da funo main , a prpria entrada
de dados, a prpria sada e tambm a sentena return que finaliza a funo main . fato
que a entrada de dados e a sada so, em geral, inerentes aos problemas computacionais e,
alm disso, sem elas no h sentido em se falar de processamento. Isso quer dizer que, como a
entrada e a sada so inerentes ao programa, o que de fato damos mais importncia na anlise
de um programa no seu tempo gasto no processamento, isto , na transformao dos dados
de entrada nos dados de sada. Isto posto, vamos verificar a seguir o custo de cada linha no
programa 2.1 e tambm o nmero de vezes que cada linha executada. A tabela a seguir ilustra
esses elementos.
FACOM
UFMS
12
E FICINCIA
DE ALGORITMOS E PROGRAMAS
#include <stdio.h>
#define MAX 100
int main(void)
{
int n, i, C[MAX], x;
printf("Informe n: ");
scanf("%d", &n);
for (i = 0; i < n; i++) {
printf("Informe um elemento: ");
scanf("%d", &C[i]);
}
printf("Informe x: ");
scanf("%d", &x);
for (i = 0; i < n && C[i] != x; i++)
;
if (i < n)
printf("%d est na posio %d de C\n", x, i);
else
printf("%d no pertence ao conjunto C\n", x);
return 0;
}
Custo
c1
c2
c3
0
c4
c5
c6
c7
c8
c9
0
c10
c11
c12
0
c13
c14
c15
c16
c17
0
Vezes
1
1
1
1
1
1
1
n+1
n
n
n
1
1
ti
ti 1
1
1
1
1
1
1
O tempo de execuo do programa 2.1 dado pela soma dos tempos para cada sentena
executada. Uma sentena que gasta ci passos e executada n vezes contribui com ci n no tempo
de execuo do programa. Para computar T (n), o tempo de execuo do programa 2.1, devemos somar os produtos das colunas Custo e Vezes , obtendo:
T (n) = c1 + c2 + c3 + c4 + c5 + c6 + c7 (n + 1) + c8 n + c9 n+
c10 + c11 + c12 ti + c13 + c14 + c15 + c16 + c17 .
Observe que, mesmo para entradas de um mesmo tamanho, o tempo de execuo de um
programa pode depender de qual entrada desse tamanho fornecida. Por exemplo, no programa 2.1, o melhor caso ocorre se o elemento x encontra-se na primeira posio do vetor C.
Assim, ti = 1 e o tempo de execuo do melhor caso dado por:
T (n) = c1 + c2 + c3 + c4 + c5 + c6 + c7 (n + 1) + c8 n + c9 n+
c10 + c11 + c12 + c13 + c14 + c15 + c16 + c17
= (c7 + c8 + c9 )n+
(c1 + c2 + c3 + c4 + c5 + c6 + c7 + c10 + c11 + c12 + c13 + c14 + c15 + c16 + c17 ) .
Esse tempo de execuo pode ser expresso como uma funo linear an + b para constantes
a e b, que dependem dos custos ci das sentenas do programa. Assim, o tempo de execuo
dado por uma funo linear em n. No entanto, observe que as constantes c7 , c8 e c9 que
multiplicam n na frmula de T (n) so aquelas que tratam somente da entrada de dados. Evidentemente que para armazenar n nmeros inteiros em um vetor devemos gastar tempo an,
para alguma constante positiva a. Dessa forma, se consideramos apenas a constante c12 na frmula, que trata propriamente da busca, o tempo de execuo de melhor caso, quando ti = 1,
FACOM
UFMS
2.2 A NLISE
DE ALGORITMOS E PROGRAMAS
13
dado por:
T (n) = c12 ti = c12 .
Por outro lado, se o elemento x no se encontra no conjunto C, temos ento o pior caso do
programa. Alm de comparar i com n, comparamos tambm o elemento x com o elemento C[i]
para cada i, 0 6 i 6 n 1. Uma ltima comparao ainda realizada quando i atinge o valor
n. Assim, ti = n + 1 e o tempo de execuo de pior caso dado por:
T (n) = c12 ti = c12 (n + 1) = c12 n + c12 .
Esse tempo de execuo de pior caso pode ser expresso como uma funo linear an + b para
constantes a e b que dependem somente da constante c12 , responsvel pelo trecho do programa
que realiza o processamento.
Na anlise do programa 2.1, estabelecemos os tempos de execuo do melhor caso, quando
encontramos o elemento procurado logo na primeira posio do vetor que representa o conjunto, e do pior caso, quando no encontramos o elemento no vetor. No entanto, estamos em
geral interessados no tempo de execuo de pior caso de um programa, isto , o maior tempo
de execuo para qualquer entrada de tamanho n.
Como o tempo de execuo de pior caso de um programa um limitante superior para seu
tempo de execuo para qualquer entrada, temos ento uma garantia que o programa nunca
vai gastar mais tempo que esse estabelecido. Alm disso, o pior caso ocorre muito freqentemente nos programas em geral, como no caso do problema da busca.
UFMS
14
E FICINCIA
DE ALGORITMOS E PROGRAMAS
seja relevante, estamos estudando na verdade a eficincia assinttica de um algoritmo ou programa. Isto , concentramo-nos em saber como o tempo de execuo de um programa cresce
com o tamanho da entrada no limite, quando o tamanho da entrada cresce ilimitadamente.
Usualmente, um programa que assintoticamente mais eficiente ser a melhor escolha para
todas as entradas, excluindo talvez algumas entradas pequenas.
As notaes que usamos para descrever o tempo de execuo assinttico de um programa
so definidas em termos de funes matemticas cujos domnios so o conjunto dos nmeros
naturais N = {0, 1, 2, . . .}. Essas notaes so convenientes para descrever o tempo de execuo
de pior caso T (n) que usualmente definido sobre entradas de tamanhos inteiros.
No incio desta seo estabelecemos que o tempo de execuo de pior caso do programa 2.1
T (n) = O(n). Vamos definir formalmente o que essa notao significa. Para uma dada funo
g(n), denotamos por O(g(n)) o conjunto de funes
O(g(n)) = {f (n) : existem constantes positivas c e n0 tais que
0 6 f (n) 6 cg(n) para todo n > n0 } .
A funo f (n) pertence ao conjunto O(g(n)) se existe uma constante positiva c tal que f (n)
no seja maior que cg(n), para n suficientemente grande. Dessa forma, usamos a notao
O para fornecer um limitante assinttico superior sobre uma funo, dentro de um fator constante. Apesar de O(g(n)) ser um conjunto, escrevemos f (n) = O(g(n)) para indicar que f (n)
um elemento de O(g(n)), isto , que f (n) O(g(n)).
A figura 2.1 mostra a intuio por trs da notao O. Para todos os valores de n direita de
n0 , o valor da funo f (n) est sobre ou abaixo do valor da funo g(n).
cg(n)
f (n)
n
n0
Figura 2.1: f (n) = O(g(n)).
Dessa forma, podemos dizer, por exemplo, que 4n + 1 = O(n). Isso porque existem constantes positivas c e n0 tais que
4n + 1 6 cn
FACOM
UFMS
2.3 A NLISE
15
Conforme j fizemos antes, a anlise linha a linha da funo trocas_sucessivas descrita abaixo.
FACOM
UFMS
16
E FICINCIA
DE ALGORITMOS E PROGRAMAS
Custo
c1
0
c2
c3
c4
c5
c6
c7
c8
0
0
Vezes
1
1
1
n
Pn1
(i + 1)
i=1
Pn1
i=1 i
Pn1
i=1 ti
Pn1
i=1 ti
Pn1
ti
Pi=1
n1
i=1 i
1
n1
X
(i + 1) +
i=1
n1
X
=n+2
=n+2
i=1
n1
X
i=1
n1
X
i+
i=1
i+
i+
n1
X
i=1
n1
X
1+3
1+3
i=1
n1
X
i=1
n1
X
ti +
n1
X
i=1
ti +
n1
X
ti
i=1
ti
i=1
n1
X
i=1
n(n 1)
+n1
2
= n2 + n 1 .
=n+2
Ento, sabemos que o tempo de execuo da funo trocas_sucessivas dado pela expresso T (n) = n2 + n 1. Para mostrar que T (n) = O(n2 ) devemos encontrar duas constantes
positivas c e n0 e mostrar que
n2 + n 1 6 cn2 ,
n2 + n 1 6 2n2
n 1 6 n2
FACOM
UFMS
2.3 A NLISE
17
ou seja,
n2 n + 1 > 0 .
Observe ento que a inequao n2 n + 1 > 0 sempre vlida para todo n > 1. Assim,
escolhendo c = 2 e n0 = 1, temos que
n2 + n 1 6 cn2
para todo n > n0 , onde c = 2 e n0 = 1. Portanto, T (n) = O(n2 ), ou seja, o tempo de execuo
do melhor caso da funo trocas_sucessivas quadrtico no tamanho da entrada.
Para definir a entrada que determina o pior caso para a funo trocas_sucessivas devemos notar que quando o vetor contm os n elementos em ordem decrescente, o maior nmero
possvel de trocas realizado. Assim, ti = i para todo i e o tempo de execuo de pior caso
dado pela seguinte expresso:
T (n) = n +
=n+
n1
X
(i + 1) +
i=1
n1
X
(i + 1) +
i=1
n1
X
=n+5
i=1
n1
X
i=1
n1
X
i+3
i+3
i=1
i+
n1
X
n1
X
i=1
n1
X
ti
i
i=1
i=1
n(n 1)
+n1
2
5
n2 n + 2n 1
2
1
n2 n 1 .
2
=n+5
5
2
5
=
2
=
Agora, para mostrar que o tempo de execuo de pior caso T (n) = O(n2 ), escolhemos
c = 5/2 e temos ento que
5
5 2 1
n n 1 6 n2
2
2
2
1
n160
2
ou seja,
1
n+1 > 0
2
e, assim, a inequao (1/2)n + 1 > 0 para todo n > 1. Logo, escolhendo c = 5/2 e n0 = 1 temos
que
5 2 1
n n 1 6 cn2
2
2
para todo n > n0 , onde c = 5/2 e n0 = 1. Assim, T (n) = O(n2 ), ou seja, o tempo de execuo do
pior caso da funo trocas_sucessivas quadrtico no tamanho da entrada. Note tambm
que ambos os tempos de execuo de melhor e de pior caso da funo trocas_sucessivas
tm o mesmo desempenho assinttico.
FACOM
UFMS
18
E FICINCIA
DE ALGORITMOS E PROGRAMAS
Usando um programa cujo tempo de execuo tem menor taxa de crescimento, mesmo com
um compilador pior, o computador pessoal 20 vezes mais rpido que o supercomputador!
Esse exemplo mostra que os algoritmos e os programas, assim como os computadores, so
uma tecnologia. O desempenho total do sistema depende da escolha de algoritmos e programas eficientes tanto quanto da escolha de computadores rpidos. Assim como rpidos avanos
esto sendo feitos em outras tecnologias computacionais, eles esto sendo feitos em algoritmos
e programas tambm.
FACOM
UFMS
2.4 M ORAL
19
DA HISTRIA
Exerccios
2.1 Qual o menor valor de n tal que um programa com tempo de execuo 100n2 mais
rpido que um programa cujo tempo de execuo 2n , supondo que os programas foram
implementados no mesmo computador?
2.2 Suponha que estamos comparando as implementaes dos mtodos de ordenao por
trocas sucessivas e por intercalao em um mesmo computador. Para entradas de tamanho n, o mtodo das trocas sucessivas gasta 8n2 passos enquanto que o mtodo da intercalao gasta 64n log n passos. Para quais valores de n o mtodo das trocas sucessivas
melhor que o mtodo da intercalao?
2.3 Expresse a funo n3 /1000 100n2 100n + 3 na notao O.
2.4 Para cada funo f (n) e tempo t na tabela abaixo determine o maior tamanho n de um
problema que pode ser resolvido em tempo t, considerando que o programa soluciona o
problema em f (n) microssegundos.
1
segundo
1
minuto
1
hora
1
dia
1
ms
1
ano
1
sculo
log n
n
n
n log n
n2
n3
2n
n!
2.5 verdade que 2n+1 = O(2n )? E verdade que 22n = O(2n )?
2.6 Suponha que voc tenha algoritmos com os cinco tempos de execuo listados abaixo.
Quo mais lento cada um dos algoritmos fica quando voc (i) duplica o tamanho da
entrada, ou (ii) incrementa em uma unidade o tamanho da entrada?
(a) n2
(b) n3
(c) 100n2
(d) n log2 n
(e) 2n
2.7 Suponha que voc tenha algoritmos com os seis tempos de execuo listados abaixo. Suponha que voc tenha um computador capaz de executar 1010 operaes por segundo
e voc precisa computar um resultado em no mximo uma hora de computao. Para
cada um dos algoritmos, qual o maior tamanho da entrada n para o qual voc poderia
receber um resultado em uma hora?
(a) n2
FACOM
UFMS
20
E FICINCIA
DE ALGORITMOS E PROGRAMAS
(b) n3
(c) 100n2
(d) n log2 n
(e) 2n
(f) 22
2.8 Rearranje a seguinte lista de funes em ordem crescente de taxa de crescimento. Isto ,
se a funo g(n) sucede imediatamente a funo f (n) na sua lista, ento verdade que
f (n) = O(g(n)).
f1 (n) = n2.5
f2 (n) = 2n
f3 (n) = n + 10
f4 (n) = 10n
f5 (n) = 100n
f6 (n) = n2 log2 n
2.9 Considere o problema de computar o valor de um polinmio em um
ponto. Dados n
Pn1
coeficientes a0 , a1 , . . . , an1 e um nmero real x, queremos computar i=0 ai xi .
(a) Escreva um programa simples com tempo de execuo de pior caso O(n2 ) para solucionar este problema.
(b) Escreva um programa com tempo de execuo de pior caso O(n) para solucionar
este problema usando o mtodo chamado de regra de Horner para reescrever o polinmio:
n1
X
ai xi = ( (an1 x + an2 )x + + a1 )x + a0 .
i=1
2.10 Seja A[0..n 1] um vetor de n nmeros inteiros distintos dois a dois. Se i < j e A[i] > A[j]
ento o par (i, j) chamado uma inverso de A.
(a) Liste as cinco inverses do vetor A = h2, 3, 8, 6, 1i.
(b) Qual vetor com elementos no conjunto {1, 2, . . . , n} tem a maior quantidade de inverses? Quantas so?
(c) Escreva um programa que determine o nmero de inverses em qualquer permutao de n elementos em tempo de execuo de pior caso O(n log n).
FACOM
UFMS
AULA
C ORREO
DE ALGORITMOS E
PROGRAMAS
fato que estamos muito interessados em construir algoritmos e programas eficientes, conforme vimos na aula 2. No entanto, de nada vale um algoritmo eficiente mas incorreto. Por
correto, queremos dizer que o algoritmo sempre pra com a resposta correta para todo entrada.
Devemos, assim, ser capazes de mostrar que nossos algoritmos so eficientes e corretos.
H duas estratgias bsicas para mostrar que um algoritmo, programa ou funo est correto, dependendo de como foi descrito. Se recursivo, ento a induo matemtica usada
imediatamente para mostrar sua correo. Por outro lado, se no-recursivo, ento contm um
ou mais processos iterativos, que so controlados por estruturas de repetio. Processos iterativos podem ser ento documentados com invariantes, que nos ajudam a entender os motivos
pelos quais o algoritmo, programa ou funo programa est correto. Nesta aula veremos como
mostrar que uma funo, recursiva ou no, est correta.
Esta aula inspirada em [1, 2].
21
22
C ORREO
DE ALGORITMOS E PROGRAMAS
Proposio 3.1. A funo potR recebe dois nmeros inteiros x e n e devolve corretamente o valor de
xn .
Demonstrao.
Vamos mostrar a proposio por induo em n.
Se n = 0 ento a funo devolve 1 = x0 = xn .
Suponha que a funo esteja correta para todo valor k, com 0 < k < n. Ou seja, a
funo potR com parmetros x e k devolve corretamente o valor xk para todo k,
com 0 < k < n.
Agora, vamos mostrar que a funo est correta para n > 0. Como n > 0 ento a
ltima linha do corpo da funo executada:
return x * potR(x, n-1);
Ento, como n1 < n, por hiptese de induo, a chamada potR(x, n-1) nesta linha devolve corretamente o valor xn1 . Logo, a chamada de potR(x, n) devolve
x xn1 = xn .
3.2.1 Definio
Um invariante de um processo iterativo uma relao entre os valores das variveis envolvidas neste processo que vale no incio de cada iterao do mesmo. Os invariantes explicam
o funcionamento do processo iterativo e permitem provar por induo que ele tem o efeito
desejado.
Devemos provar trs elementos sobre um invariante de um processo iterativo:
Inicializao: verdadeiro antes da primeira iterao da estrutura de repetio;
Manuteno: se verdadeiro antes do incio de uma iterao da estrutura de repetio, ento
permanece verdadeiro antes da prxima iterao;
Trmino: quando a estrutura de repetio termina, o invariante nos d uma propriedade til
que nos ajuda a mostrar que o algoritmo ou programa est correto.
FACOM
UFMS
3.2 C ORREO
23
3.2.2 Exemplos
Nesta seo veremos exemplos do uso dos invariantes para mostrar a correo de programas. O primeiro exemplo, dado no programa 3.1, bem simples e o programa contm apenas
variveis do tipo inteiro e, obviamente, uma estrutura de repetio. O segundo exemplo, apresentado no programa 3.2, um programa que usa um vetor no processo iterativo para soluo
do problema.
Considere ento o programa 3.1, que recebe um nmero inteiro n > 0 e uma seqncia de n
nmeros inteiros, e mostra a soma desses n nmeros inteiros. O programa 3.1 simples e no
usa um vetor para solucionar esse problema.
Programa 3.1: Soma n inteiros fornecidos pelo(a) usurio(a).
#include <stdio.h>
/* Recebe um nmero inteiro n > 0 e uma seqncia de n nmeros
inteiros e mostra o resultado da soma desses nmeros */
int main(void)
{
int n, i, num, soma;
printf("Informe n: ");
scanf("%d", &n);
soma = 0;
for (i = 1; i <= n; i++) {
/* varivel soma contm o somatrio dos
primeiros i-1 nmeros fornecidos */
printf("Informe um nmero: ");
scanf("%d", &num);
soma = soma + num;
}
printf("Soma dos %d nmeros %d\n", n, soma);
return 0;
}
importante destacar o comentrio descrito nas duas linhas seguintes estrutura de repetio for do programa 3.1: este o invariante desse processo iterativo. E como Feofiloff
destaca em [2], o enunciado de um invariante , provavelmente, o nico tipo de comentrio
que vale a pena inserir no corpo de um algoritmo, programa ou funo.
Ento, podemos provar a seguinte proposio.
FACOM
UFMS
24
C ORREO
DE ALGORITMOS E PROGRAMAS
Proposio 3.2. O programa 3.1 computa corretamente a soma de n > 0 nmeros inteiros fornecidos
pelo(a) usurio(a).
Demonstrao.
Por convenincia na demonstrao, usaremos o modo matemtico para expressar as
variveis do programa. Dessa forma, denotaremos i no lugar de i , soma no lugar
de soma e num ao invs de num . Quando nos referirmos ao i-simo nmero inteiro
da seqncia de nmeros inteiros fornecida pelo(a) usurio(a), que armazenado
na varivel num , usaremos por convenincia a notao numi .
Provar que o programa 3.1 est correto significa mostrar que para qualquer valor de
n e qualquer seqncia de n nmeros, a varivel soma conter, ao final do processo
iterativo, o valor
n
X
numi .
soma =
i=1
Vamos agora mostrar que o invariante vale no incio da primeira iterao do processo iterativo. Como a varivel soma contm o valor 0 (zero) e i contm 1, verdade
que a varivel soma contm a soma dos i 1 primeiros nmeros fornecidos pelo(a)
usurio(a).
Suponha agora que o invariante valha no incio da i-sima iterao, com 1 < i < n.
Vamos mostrar que o invariante vale no incio da ltima iterao, quando i contm
o valor n. Por hiptese de induo, a varivel soma contm o valor
=
n1
X
numi .
i=1
n1
X
numi
i=1
n
X
+ numn
numi .
i=1
Portanto, isso mostra que o programa 3.1 de fato realiza a soma dos n nmeros
inteiros fornecidos pelo(a) usurio(a).
O prximo exemplo dado pelo seguinte problema: dado um vetor com n nmeros inteiros fornecidos pelo(a) usurio(a), encontrar um valor mximo armazenado nesse vetor. O
programa 3.2 bem simples e se prope a solucionar esse problema.
FACOM
UFMS
3.2 C ORREO
25
Vamos mostrar que o invariante vale no incio da ltima iterao, quando i contm o
valor n 1. Por hiptese de induo, no incio desta iterao a varivel max contm
o valor o elemento mximo de vet[0..n 2]. Ento, no decorrer dessa iterao, o
valor vet[n 1] comparado com max e dois casos devem ser avaliados:
FACOM
UFMS
26
C ORREO
DE ALGORITMOS E PROGRAMAS
Exerccios
3.1 O programa 3.3 recebe um nmero inteiro n > 0, uma seqncia de n nmeros inteiros,
um nmero inteiro x e verifica se x pertence seqncia de nmeros.
Programa 3.3: Verifica se x pertence uma seqncia de n nmeros.
#include <stdio.h>
#define MAX 100
int main(void)
{
int n, C[MAX], i, x;
scanf("%d", &n);
for (i = 0; i < n; i++)
scanf("%d", &C[i]);
scanf("%d", &x);
i = 0;
while (i < n && C[i] != x)
/* x no pertence C[0..i] */
i++;
if (i < n)
printf("%d o %d-simo elemento do vetor\n", x, i);
else
printf("%d no se encontra no vetor\n", x);
return 0;
}
UFMS
3.2 C ORREO
27
que receba um nmero inteiro n > 0 e uma seqncia de n nmeros inteiros armazenados
no vetor, e devolva o vetor a seqncia de nmeros invertida. Mostre que sua funo est
correta.
3.3 Mostre que sua soluo para o exerccio 1.6 est correta.
3.4 Mostre que sua soluo para o exerccio 1.7 est correta.
3.5 Mostre que sua soluo para o exerccio 1.8 est correta.
FACOM
UFMS
28
FACOM
C ORREO
DE ALGORITMOS E PROGRAMAS
UFMS
AULA
B USCA
Como temos visto ao longo de muitas das aulas anteriores, a busca de um elemento em
um conjunto uma operao bsica em Computao. A maneira como esse conjunto armazenado na memria do computador permite que algumas estratgias possam ser usadas para
realizar a tarefa da busca. Na aula de hoje revemos os mtodos de busca que vimos usando
corriqueiramente at o momento como uma subtarefa realizada na soluo de diversos problemas prticos. A busca ser fixada, como antes, com os dados envolvidos como sendo nmeros
inteiros e o conjunto de nmeros inteiros onde a busca se dar armazenado em um vetor.
Alm de rever essa estratgia, chamada de busca seqencial, vemos ainda uma estratgia nova
e muito eficiente de busca em um vetor ordenado, chamada de busca binria. Esta aula est
completamente baseada no livro de P. Feofiloff [2], captulos 3 e 7.
/* Recebe um nmero inteiro n >= 0, um vetor v[0..n-1] com n nmeros inteiros e um nmero inteiro x e devolve k no intervalo
[0, n-1] tal que v[k] == x. Se tal k no existe, devolve -1. */
int busca_sequencial(int n, int v[MAX], int x)
{
int k;
for (k = n - 1; k >= 0 && v[k] != x; k--)
;
return k;
}
29
30
B USCA
i = busca_sequencial(v, n, x);
if (i >= 0)
printf("Encontrei %d!\n", v[i]);
A correo da funo busca_sequencial mostrada de forma semelhante quela do exerccio 3.1. Seu tempo de execuo linear no tamanho da entrada, isto , proporcional a n, ou
ainda, O(n), como mostrado na aula 2. A funo busca_sequencial_R tem correo e anlise de eficincia equivalentes.
Vale a pena consultar o captulo 3, pginas 12 e 13, do livro de P. Feofiloff [2], para verificar uma srie de maus exemplos para soluo do problema da busca, dentre deselegantes,
ineficientes e at incorretos.
No caso de vetores ordenados, uma pergunta equivalente mas ligeiramente diferente formulada: em qual posio do vetor v o elemento x deveria estar? Dessa forma, o problema da
busca pode ser reformulado da seguinte maneira: dado um nmero inteiro n > 0, um vetor de
FACOM
UFMS
4.2 B USCA
31
EM UM VETOR ORDENADO
nmeros inteiros crescente v[0..n 1] e um nmero inteiro x, encontrar um ndice k tal que
v[k 1] < x 6 v[k] .
(4.1)
Encontrar um ndice k como o da equao (4.1) significa ento praticamente resolver o problema da busca, bastando comparar x com v[k].
Observe ainda que qualquer valor de k no intervalo [0, n] pode ser uma soluo do problema da busca. Nos dois extremos do intervalo, a condio (4.1) deve ser interpretada adequadamente. Isto , se k = 0, a condio se reduz apenas a x 6 v[0], pois v[1] no faz sentido.
Se k = n, a condio se reduz somente a v[n 1] < x, pois v[n] no faz sentido. Tudo se passa
como se o vetor v tivesse um componente imaginrio v[1] com valor e um componente
imaginrio v[n] com valor +. Tambm, para simplificar um pouco o raciocnio, suporemos
que n > 1.
Isso posto, observe ento que uma busca seqencial, recursiva ou no, como as funes
busca_sequencial e busca_sequencial_R da seo 4.1, pode ser executada para resolver
o problema. Vejamos ento uma soluo um pouco diferente na funo busca_ordenada .
/* Recebe um nmero inteiro n > 0, um vetor de nmeros inteiros crescente v[0..n-1] e um nmero inteiro x e devolve um ndice k em [0, n] tal que v[k-1] < x <= v[k] */
int busca_ordenada(int n, int v[MAX], int x)
{
int k;
for (k = 0; k < n && v[k] < x; k++)
;
return k;
}
i = busca_ordenada(n, v, x);
Se aplicamos a estratgia de busca seqencial em um vetor ordenado, ento certamente obtemos uma resposta correta, porm ineficiente do ponto de vista de seu consumo de tempo.
Isso porque, no pior caso, a busca seqencial realiza a comparao do elemento x com cada
um dos elementos do vetor v de entrada. Ou seja, o problema da busca resolvido em tempo
proporcional ao nmero de elementos do vetor de entrada v, deixando de explorar sua propriedade especial de se encontrar ordenado. O tempo de execuo de pior caso da funo
busca_ordenada permanece proporcional a n, o mesmo que das funes da seo 4.1.
Com uma busca binria, podemos fazer o mesmo trabalho de forma bem mais eficiente. A
busca binria se baseia no mtodo que usamos de modo automtico para encontrar uma palavra no dicionrio: abrimos o dicionrio ao meio e comparamos a primeira palavra desta pgina
com a palavra buscada. Se a primeira palavra menor que a palavra buscada, jogamos fora a
primeira metade do dicionrio e repetimos a mesma estratgia considerando apenas a metade
FACOM
UFMS
32
B USCA
restante. Se, ao contrrio, a primeira palavra maior que a palavra buscada, jogamos fora
a segunda metade do dicionrio e repetimos o processo. A funo busca_binaria abaixo
implementa essa idia.
/* Recebe um nmero inteiro n > 0, um vetor de nmeros inteiros crescente v[0..n-1] e um nmero inteiro x e devolve um ndice k em [0, n] tal que v[k-1] < x <= v[k] */
int busca_binaria(int n, int v[MAX], int x)
{
int esq, dir, meio;
esq = -1;
dir = n;
while (esq < dir - 1) {
meio = (esq + dir) / 2;
if (v[meio] < x)
esq = meio;
else
dir = meio;
}
return dir;
}
k = busca_binaria(n, v, x);
UFMS
4.2 B USCA
EM UM VETOR ORDENADO
33
k = busca_binaria_R(-1, n, v, x);
Quando a funo busca_binaria_R chamada com argumentos (1, n, v, x), ela chama a
si mesma cerca de log2 n vezes. Este nmero de chamadas a profundidade da recurso e
determina o tempo de execuo da funo.
Exerccios
4.1 Tome uma deciso de projeto diferente daquela da seo 4.1: se x no estiver em v[0..n1],
a funo deve devolver n. Escreva a verso correspondente da funo busca . Para evitar
o grande nmero de comparaes de k com n, coloque uma sentinela em v[n].
/* Recebe um nmero inteiro n >= 0, um vetor de nmeros inteiros v[0..n-1] e um nmero inteiro x e devolve k no intervalo
[0, n-1] tal que v[k] == x. Se tal k no existe, devolve n */
int busca_sequencial_sentinela(int n, int v[MAX+1], int x)
{
int k;
v[n] = x;
for (k = 0; v[k] != x; k++)
;
return k;
}
FACOM
UFMS
34
B USCA
4.4 A operao de remoo consiste de retirar do vetor v[0..n 1] o elemento que tem ndice
k e fazer com que o vetor resultante tenha ndices 0, 1, . . . , n 2. Essa operao s faz
sentido se 0 6 k < n.
(a) Escreva uma funo no-recursiva com a seguinte interface:
int remove(int n, int v[MAX], int k)
FACOM
UFMS
4.2 B USCA
EM UM VETOR ORDENADO
35
UFMS
36
B USCA
4.12 Suponha que cada elemento do vetor v[0..n 1] uma cadeia de caracteres (ou seja,
temos uma matriz de caracteres). Suponha tambm que o vetor est em ordem lexicogrfica. Escreva uma funo eficiente, baseada na busca binria, que receba uma cadeia de
caracteres x e devolva um ndice k tal que x igual a v[k]. Se tal k no existe, a funo
deve devolver 1.
4.13 Suponha que cada elemento do vetor v[0..n 1] um registro com dois campos: o nome
do(a) estudante e o nmero do(a) estudante. Suponha que o vetor est em ordem crescente de nmeros. Escreva uma funo de busca binria que receba o nmero de um(a)
estudante e devolva seu nome. Se o nmero no estiver no vetor, a funo deve devolver
a cadeia de caracteres vazia.
4.14 Escreva uma funo que receba um vetor crescente v[0..n 1] de nmeros inteiros e devolva um ndice i entre 0 e n 1 tal que v[i] = i. Se tal i no existe, a funo deve devolver
1. A sua funo no deve fazer mais que log2 n comparaes envolvendo os elementos
de v.
FACOM
UFMS
AULA
O RDENAO :
MTODOS ELEMENTARES
Alm da busca, a ordenao outra operao elementar em computao. Nesta aula revisaremos os mtodos de ordenao elementares que j tivemos contato em aulas anteriores:
o mtodo das trocas sucessivas, da seleo e da insero. Esses mtodos foram projetados a
partir de idias simples e tm, como caracterstica comum, tempo de execuo de pior caso
quadrtico no tamanho da entrada. A aula baseada no livro de P. Feofiloff [2].
38
O RDENAO :
MTODOS ELEMENTARES
trocas_sucessivas(n, v);
Para entender a funo trocas_sucessivas basta observar que no incio de cada repetio do for externo vale que:
o vetor v[0..n 1] uma permutao do vetor original,
o vetor v[i + 1..n 1] crescente e
v[j] 6 v[i + 1] para j = 0, 1, . . . , i.
Alm disso, o consumo de tempo da funo trocas_sucesivas proporcional ao nmero
de execues da comparao v[j] > v[j + 1], que proporcional a n2 no pior caso.
selecao(n, v);
Para entender como e por que o a funo selecao funciona, basta observar que no incio
de cada repetio do for externo valem os seguintes invariantes:
o vetor v[0..n 1] uma permutao do vetor original,
o vetor v[0..i 1] est em ordem crescente e
FACOM
UFMS
5.4 M TODO
DA INSERO
39
insercao(n, v);
Para entender a funo insercao basta observar que no incio de cada repetio do for
externo, valem os seguintes invariantes:
o vetor v[0..n 1] uma permutao do vetor original e
o vetor v[0..i 1] crescente.
O consumo de tempo da funo insercao proporcional ao nmero de execues da
comparao v[j] > x, que proporcional a n2 .
FACOM
UFMS
40
O RDENAO :
MTODOS ELEMENTARES
Exerccios
5.1 Escreva uma funo que verifique se um dado vetor v[0..n 1] crescente.
int verifica_ordem(int n, int v[MAX])
{
for (i = 0; i < n - 1; i++)
if (v[i] > v[i+1])
return 0;
return 1;
}
5.2 Que acontece se trocarmos a realao i > 0 pela relao i >= 0 no cdigo da funo
trocas_sucessivas ? Que acontece se trocarmos j < i por j <= i ?
5.3 Troque a relao v[j] > v[j + 1] pela relao v[j] >= v[j + 1] no cdigo da funo trocas_sucessivas . A nova funo continua produzindo uma ordenao crescente de v[0..n 1]?
5.4 Escreva uma verso recursiva do mtodo de ordenao por trocas sucessivas.
5.5 Que acontece se trocarmos i = 0 por i = 1 no cdigo da funo selecao ? Que
acontece se trocarmos i < n-1 por i < n ?
5.6 Troque v[j] < v[min] por v[j] <= v[min] no cdigo da funo selecao . A nova
funo continua produzindo uma ordenao crescente de v[0..n 1]?
5.7 Escreva uma verso recursiva do mtodo de ordenao por seleo.
5.8 No cdigo da funo insercao , troque v[j] > x por v[j] >= x . A nova funo
continua produzindo uma ordenao crescente de v[0..n 1]?
5.9 No cdigo da funo insercao , que acontece se trocarmos i = 1 por i = 0 ? Que
acontece se trocarmos v[j+1] = x por v[j] = x ?
5.10 Escreva uma verso recursiva do mtodo de ordenao por insero.
5.11 Escreva uma funo que rearranje um vetor v[0..n 1] de modo que ele fique em ordem
estritamente crescente.
5.12 Escreva uma funo que permute os elementos de um vetor v[0..n 1] de modo que eles
fiquem em ordem decrescente.
FACOM
UFMS
AULA
O RDENAO
POR INTERCALAO
Na aula 5 revimos os mtodos de ordenao mais bsicos, que so todos iterativos, simples
e tm tempo de execuo de pior caso proporcional a n2 , onde n o tamanho da entrada.
Mtodos mais eficientes de ordenao so baseados em recurso, tcnica introduzida na aula 1.
Nesta aula, estudamos o mtodo da ordenao por intercalao, conhecido como mergesort.
A ordenao por intercalao, que veremos nesta aula, e a ordenao por separao, que
veremos na aula 7, so mtodos eficientes baseados na tcnica recursiva chamada dividir para
conquistar, onde quebramos o problema em vrios subproblemas de menor tamanho que so
similares ao problema original, resolvemos esses subproblemas recursivamente e ento combinamos essas solues para produzir uma soluo para o problema original. Esa aula baseada
no livro de P. Feofiloff [2] e no livro de Cormen et. al [1].
42
O RDENAO
POR INTERCALAO
de A e B. Variantes sutis desse problema geral podem ser descritas como no caso em que se
permite ou no elementos iguais nos dois conjuntos de entrada, isto , conjuntos de entrada A
e B tais que A B 6= ou A B = .
O problema da intercalao que queremos resolver aqui mais especfico e pode ser assim
descrito: dados dois vetores crescentes v[p..q 1] e v[q..r 1], rearranjar v[p..r 1] em ordem
crescente. Isso significa que queremos de alguma forma intercalar os vetores v[0..q 1] e v[q..r
1]. Nesse caso, primeira vista parece que os vetores de entrada podem ter elementos em
comum. Entretanto, este no o caso, j que estamos considerando o mesmo conjunto inicial de
elementos armazenados no vetor v. Uma maneira fcil de resolver o problema da intercalao
usar um dos mtodos de ordenao da aula 5 tendo como entrada o vetor v[p..r 1]. Essa
soluo, no entanto, tem consumo de tempo de pior caso proporcional ao quadrado do nmero
de elementos do vetor e ineficiente por desconsiderar as caractersticas dos vetores v[p..q 1]
e v[q..r 1]. Uma soluo mais eficiente, que usa um vetor auxiliar, mostrada a seguir.
/* Recebe os vetores crescentes v[p..q-1] e v[q..r-1]
e rearranja v[p..r-1] em ordem crescente */
void intercala(int p, int q, int r, int v[MAX])
{
int i, j, k, w[MAX];
i = p;
j = q;
k = 0;
while (i < q && j < r) {
if (v[i] < v[j]) {
w[k] = v[i];
i++;
}
else {
w[k] = v[j];
j++;
}
k++;
}
while (i < q) {
w[k] = v[i];
i++;
k++;
}
while (j < r) {
w[k] = v[j];
j++;
k++;
}
for (i = p; i < r; i++)
v[i] = w[i-p];
}
UFMS
6.3 O RDENAO
POR INTERCALAO
43
Como a expresso (p + q)/2 da funo mergesort do tipo inteiro, observe que seu resultado , na verdade, avaliado como p+q
2 .
Observe tambm que para ordenar um vetor v[0..n 1] basta chamar a funo mergesort
com os seguintes argumentos:
mergesort(0, n, v);
Observe que as chamadas recursivas so realizadas at a linha divisria imaginria ilustrada na figura, quando p > r 1. A partir desse ponto, a cada volta de um nvel de recurso,
uma intercalao realizada. No final, uma ltima intercalao realizada e o vetor original
torna-se ento um vetor crescente com os mesmos elementos de entrada.
Qual o desempenho da funo mergesort quando queremos ordenar um vetor v[0..n1]?
Suponha, para efeito de simplificao, que n uma potncia de 2. Se esse no o caso, podemos examinar duas potncias de 2 consecutivas, justamente aquelas tais que 2k1 < n 6 2k ,
para algum k > 0. Observe ento que o nmero de elementos do vetor diminudo a aproximadamente metade a cada chamada da funo mergesort . Ou seja, o nmero aproximado de
chamadas proporcional a log2 n. Na primeira vez, o problema original reduzido a dois subproblemas onde necessrio ordenar os vetores v[0.. n2 1] e v[ n2 ..n 1]. Na segunda vez, cada
FACOM
UFMS
44
O RDENAO
POR INTERCALAO
1].
E
assim
por
diante.
Alm
disso,
como
j
vimos,
o
tempo
total
que
a
funo
e v[ 3n
4
intercala gasta proporcional ao nmero de elementos do vetor v, isto , r p. Portanto, a
funo mergesort consome tempo proporcional a n log2 n.
Exerccios
6.1 Simule detalhadamente a execuo da funo mergesort sobre o vetor de entrada
v[0..7] = {3, 41, 52, 26, 38, 57, 9, 49}.
6.2 A funo intercala est correta nos casos extremos p = q e q = r?
6.3 Um algoritmo de intercalao estvel se no altera a posio relativa dos elementos que
tm um mesmo valor. Por exemplo, se o vetor tiver dois elementos de valor 222, um
algoritmo de intercalao estvel manter o primeiro 222 antes do segundo. A funo
FACOM
UFMS
6.3 O RDENAO
POR INTERCALAO
45
intercala estvel? Se a comparao v[i] < v[j] for trocada por v[i] <= v[j] a
funo fica estvel?
(b) Qual vetor com elementos do conjunto {1, 2, . . . , n} tem o maior nmero de inverses? Quantas so?
(c) Qual a relao entre o tempo de execuo da ordenao por insero e o nmero de
inverses em um vetor de entrada? Justifique sua resposta.
(d) Modificando a ordenao por intercalao, escreva uma funo eficiente, com tempo
de execuo O(n log n), que determine o nmero de inverses em uma permutao
de n elementos.
6.8 Escreva um programa para comparar experimentalmente o desempenho da funo
mergesort com o das funes trocas_sucessivas , selecao e insercao da aula 5.
Use um vetor com nmeros (pseudo-)aleatrios para fazer os testes.
6.9 Veja animaes dos mtodos de ordenao que j vimos nas seguintes pginas:
Sort Animation de R. Mohammadi;
FACOM
UFMS
46
FACOM
O RDENAO
POR INTERCALAO
UFMS
AULA
O RDENAO
POR SEPARAO
48
O RDENAO
POR SEPARAO
v[p..r]
5
(a)
i
v[p..q]
(d)
(b)
(c)
v[q + 1..r]
1
(e)
UFMS
7.2 O RDENAO
POR SEPARAO
49
Uma chamada da funo quicksort para ordenao de um vetor v[0..n 1] deve ser feita
como a seguir:
Observe ainda que a funo quicksort est correta mesmo quando p > r, isto , quando
o vetor est vazio.
O consumo de tempo do mtodo de ordenao por separao proporcional ao nmero
de comparaes realizadas entre os elementos do vetor. Se o ndice devolvido pela funo
separa sempre tiver valor mais ou menos mdio de p e r, isto , prximo a (p + r)/2,
FACOM
UFMS
50
O RDENAO
POR SEPARAO
Exerccios
7.1 Ilustre a operao da funo separa sobre o vetor v que contm os elementos do conjunto {13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21}.
7.2 Qual o valor de q a funo separa devolve quando todos os elementos no vetor v[p..r]
tm o mesmo valor?
7.3 A funo separa produz o resultado correto quando p = r?
7.4 Escreva uma funo que rearranje um vetor v[p..r] de nmeros inteiros de modo que os
elementos negativos e nulos fiquem esquerda e os positivos fiquem direita. Em outras
palavras, rearranje o vetor de modo que tenhamos v[p..q 1] 6 0 e v[q..r] > 0 para algum
q em p..r + 1. Procure escrever uma funo eficiente que no use um vetor auxiliar.
7.5 Digamos que um vetor v[p..r] est arrumado se existe q em p..r que satisfaz
v[p..q 1] 6 v[q] < v[q + 1..r] .
Escreva uma funo que decida se v[p..r] est arrumado. Em caso afirmativo, sua funo
deve devolver o valor de q.
7.6 Que acontece se trocarmos a expresso if (p < r) pela expresso if (p != r) no
corpo da funo quicksort ?
7.7 Compare as funes quicksort e mergesort . Discuta as semelhanas e diferenas.
7.8 Como voc modificaria a funo quicksort para ordenar elementos em ordem decrescente?
7.9 Os bancos freqentemente gravam transaes sobre uma conta corrente na ordem das
datas das transaes, mas muitas pessoas preferem receber seus extratos bancrios em
listagens ordenadas pelo nmero do cheque emitido. As pessoas em geral emitem cheques na ordem da numerao dos cheques, e comerciantes usualmente descontam estes
cheques com rapidez razovel. O problema de converter uma lista ordenada por data de
transao em uma lista ordenada por nmero de cheques portanto o problema de ordenar uma entrada quase j ordenada. Argumente que, neste caso, o mtodo de ordenao
por insero provavelmente se comportar melhor do que o mtodo de ordenao por
separao.
7.10 Fornea um argumento cuidadoso para mostrar que a funo separa correta. Prove o
seguinte:
(a) Os ndices i e j nunca referenciam um elemento de v fora do intervalo [p..r];
FACOM
UFMS
7.2 O RDENAO
POR SEPARAO
51
(b) O ndice j no igual a r quando separa termina (ou seja, a partio sempre no
trivial);
(c) Todo elemento de v[p..j] menor ou igual a todo elemento de v[j + 1..r] quando a
funo separa termina.
7.11 Escreva uma verso da funo quicksort que coloque um vetor de cadeias de caracteres
em ordem lexicogrfica.
7.12 Considere a seguinte soluo do problema da separao, devido a N. Lomuto. Para separar v[p..r], esta verso incrementa duas regies, v[p..i] e v[i + 1..j], tal que todo elemento
na primeira regio menor ou igual a x = v[r] e todo elemento na segunda regio maior
que x.
(a) Ilustre a operao da funo separa_Lomuto sobre o vetor v que contm os elementos {13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21}.
(c) Qual o nmero mximo de vezes que um elemento pode ser movido pelas funes
separa e separa_Lomuto ? Justifique sua resposta.
(d) Argumente que separa_Lomuto , assim como separa , tem tempo de execuo
proporcional a n sobre um vetor de n = r p + 1 elementos.
(e) Como a troca da funo separa pela funo separa_Lomuto afeta o tempo de execuo do mtodo da ordenao por separao quando todos os valores de entrada
so iguais?
7.13 A funo quicksort contm duas chamadas recursivas para ela prpria. Depois da chamada da separa , o sub-vetor esquerdo ordenado recursivamente e ento o sub-vetor
direito ordenado recursivamente. A segunda chamada recursiva no corpo da funo
quicksort no realmente necessria; ela pode ser evitada usando uma estrutura de
controle iterativa. Essa tcnica, chamada recurso de cauda, fornecida automaticamente
FACOM
UFMS
52
O RDENAO
POR SEPARAO
por bons compiladores. Considere a seguinte verso da ordenao por separao , que
simula a recurso de cauda.
/* Recebe um vetor v[p..r-1] e o rearranja em ordem crescente */
void quicksort2(int p, int r, int v[MAX])
{
while (p < r) {
q = separa(p, r, v);
quicksort2(p, q, v);
p = q + 1;
}
}
Ilustre a operao desse novo mtodo de ordenao sobre o vetor v que contm os elementos {21, 7, 5, 11, 6, 42, 13, 2}. Use a chamada silly_sort(0, n-1, v) . Argumente
que a funo silly_sort ordena corretamente o vetor v.
7.15 Veja animaes dos mtodos de ordenao que j vimos nas seguintes pginas:
Sort Animation de R. Mohammadi;
Sorting Algorithms de J. Harrison;
Sorting Algorithms de P. Morin;
FACOM
UFMS
AULA
L ISTAS
DE PRIORIDADES
Nesta aula vamos estudar uma nova estrutura de dados chamada de lista de prioridades.
Em uma lista como esta, as relaes entre os elementos do conunto que compe a lista se do
atravs das suas prioridades. Perguntas como qual o elemento com a maior prioridade e operaes de insero e remoo de elementos deste conjunto, ou alterao de prioridades de elementos da lista, so tarefas associadas a estruturas como esta. Listas de prioridades so estruturas simples e eficientes para soluo de diversos problemas prticos como, por exemplo, o
escalonamento de processos em um computador.
Esta aula baseada especialmente nas referncias [1, 2, 13].
8.1 Heaps
Antes de estudar as listas de prioridades, precisamos estudar com cuidado uma outra estrutura de dados, que base para aquelas, chamada heap1 . Um heap nada mais que uma
estrutura de dados armazenada em um vetor. Vale observar que cada clula do vetor pode
conter um registro com vrios campos, mas o campo mais importante aquele que armazena
um nmero, a sua prioridade ou chave. Como os outros dados associados prioridade so
suprfluos para o funcionamento de um heap, optamos por trabalhar apenas com um vetor de
nmeros inteiros para abrig-lo, onde cada clula sua contm uma prioridade. Dessa forma,
um heap uma coleo de elementos identificados por suas prioridades armazenadas em um
vetor numrico S satisfazendo a seguinte propriedade:
(8.1)
para todo i > 1. Um vetor S com a propriedade (8.1) chamado um max-heap. Do mesmo
modo, a propriedade (8.1) chamada propriedade max-heap. O vetor S apresentado na figura 8.1 um max-heap.
26
18
14
16
11
12
A traduo de heap do ingls monte, pilha, amontoado, monto. O termo heap, sem traduo, ser usado
daqui por diante, j que se encontra bem estabelecido na rea.
53
54
L ISTAS
DE PRIORIDADES
para todo i > 1, ento S chamado min-heap e a propriedade (8.2) chamada propriedade
min-heap. Max-heaps e min-heaps so semelhantes mas, para nossas aplicaes neste momento, estamos interessados apenas em max-heaps.
Um max-heap pode tambm ser visto como uma rvore binria2 com seu ltimo nvel
preenchido da esquerda para direita. Uma ilustrao do max-heap da figura 8.1 mostrada na
figura 8.2.
0
26
1
18
2
14
3
16
7
4
12
6
11
Figura 8.2: Representao do max-heap da figura 8.1 como uma rvore binria, com seu ltimo
nvel preenchido da esquerda para diereita.
Observe que a visualizao de um max-heap como uma rvore binria nos permite verificar
a propriedade (8.1) facilmente. Em particular, note que o contedo de um n da rvore maior
ou igual aos contedos dos ns que so seus filhos. Por conta dessa semelhana, podemos
descrever operaes bsicas que nos permitem percorrer o max-heap facilmente. As operaes
so implementadas como funes.
2
Uma rvore para os computlogos um objeto bem diferente daquele visto pelos bilogos. Sua raiz est
posicionada no ar e suas folhas esto posicionadas no cho. A rvore binria porque todo n tem, no mximo,
dois filhos. A definio formal de uma rvore binria foge ao escopo de Algoritmos e Programao II e ser vista
mais adiante.
FACOM
UFMS
8.1 Heaps
55
Dadas as operaes implementadas nas trs funes acima, a propriedade (8.1) pode ento
ser reescrita da seguinte forma:
S[ pai(i) ] > S[i] ,
(8.3)
para todo i, com i > 0.
Se olhamos para um max-heap como uma rvore binria, definimos a altura de um n no
max-heap como o nmero de linhas ou arestas no caminho mais longo do n a uma folha.
A altura do max-heap a altura da sua raiz. Como um max-heap de n elementos pode ser
visto como uma rvore binria, sua altura proporcional a log2 n. Como veremos a seguir, as
operaes bsicas sobre heaps tm tempo de execuo proporcional altura da rvore binria,
ou seja, O(log2 n).
UFMS
56
L ISTAS
DE PRIORIDADES
/* Recebe um nmero inteiro n > 0, um vetor S de nmeros inteiros com n elementos e um ndice i e estabelece a propriedade max-heap para a sub-rvore de S com raiz S[i] */
void desce(int n, int S[MAX], int i)
{
int e, d, maior;
e = esquerdo(i);
d = direito(i);
if (e < n && S[e] > S[i])
maior = e;
else
maior = i;
if (d < n && S[d] > S[maior])
maior = d;
if (maior != i) {
troca(&S[i], &S[maior]);
desce(n, S, maior);
}
}
0
26
1
3
16
7
4
2
14
12
2
14
16
6
11
3
7
7
4
12
(a)
6
11
(b)
0
26
1
2
14
16
3
12
7
4
6
11
(c)
Figura 8.3: Um exemplo de execuo da funo desce com argumentos (10, S, 1).
FACOM
UFMS
8.1 Heaps
57
Para mostrar que a funo constroi_max_heap est correta, temos de usar o seguinte
invariante:
No incio de cada iterao da estrutura de repetio da funo, cada n i + 1, i +
2, . . . , n 1 raiz de um max-heap.
Dado o invariante acima, fcil mostrar que a funo constroi_max_heap est correta,
usando induo matemtica.
O tempo de execuo da funo constroi_max_heap calculado observando que cada
chamada da funo desce na estrutura de repetio da funo constroi_max_heap tem
tempo de execuo proporcional a log2 n, onde n o nmero de elementos no vetor S. Devemos notar ainda que um nmero proporcional a n chamadas funo desce so realizadas
nessa estrutura de repetio. Assim, o tempo de execuo da funo constroi_max_heap
proporcional a n log2 n. Apesar de correto, esse limitante superior para o tempo de execuo
da funo constroi_max_heap no muito justo. Isso significa que correto afirmar que a
funo T (n) que descreve o tempo da funo limitada superiormente pela funo cn log2 n
para todo n > n0 , onde c e n0 so constantes positivas. Porm, existe outra funo, que tambm limita superiormente a funo T (n), que menor que n log2 n. Podemos mostrar que
um limitante superior melhor pode ser obtido observando a variao nas alturas dos ns da rvore max-heap nas chamadas da funo desce . Essa anlise mais apurada fornece um tempo
de execuo de pior caso proporcional a n para a funo constroi_max_heap . Mais detalhes
sobre essa anlise podem ser encontrados no livro de Cormen et. al [1].
A figura 8.4 ilustra um exemplo de execuo da funo constroi_max_heap .
FACOM
UFMS
58
L ISTAS
26
16
14
18
12
11
8
4
3
6
7
6
12
11
5
16
i
6
14
2
16
4
12
11
(d)
26
16
18
16
4
11
8
6
14
26
3
18
12
5
8
26
(c)
7
6
6
14
3
18
7
6
5
16
26
26
9
9
2
11
(b)
12
1
4
(a)
4
3
18
6
7
18
11
12
6
14
7
18
5
16
i 26
DE PRIORIDADES
5
8
9
4
6
14
7
6
(e)
3
12
11
8
9
5
8
6
14
9
4
(f)
Figura 8.4: Exemplo de execuo da funo constroi_max_heap tendo como entrada o vetor
S = {9, 4, 8, 6, 26, 16, 14, 18, 12, 11}.
FACOM
UFMS
8.1 Heaps
59
Funes muito semelhantes s funes desce e constroi_max_heap podem ser construdas para obtermos um min-heap. Veja o exerccio 8.7.
26
18
14
16
11
12
0
26
0
26
2
14
21
3
16
7
4
12
2
14
3
6
11
3
16
7
4
12
(a)
6
11
(b)
0
26
1
47
2
14
3
16
7
4
12
6
11
(c)
Figura 8.5: Alterao na prioridade S[1] de um max-heap. (a) Alterao mantm a propriedade max-heap. (b) Alterao viola a propriedade max-heap e a prioridade deve descer. (c)
Alterao viola a propriedade max-heap e a prioridade deve subir.
Como podemos perceber na figura 8.5(a), se a alterao mantm a propriedade max-heap,
ento no necessrio fazer nada. Observe tambm que se a prioridade alterada tem valor
menor que de pelo menos um de seus filhos, ento essa nova prioridade deve descer na
rvore. Esse caso mostrado na figura 8.5(b) e resolvido facilmente usando a funo desce
FACOM
UFMS
60
L ISTAS
DE PRIORIDADES
descrita na seo 8.1.1. Por fim, se alterao em um n resulta em uma prioridade maior que
a prioridade do seu n pai, ento essa nova prioridade deve subir na rvore, como mostra
a figura 8.5(c). Nesse caso, precisamos de uma funo eficiente que resolva esse problema. A
funo sobe apresentada a seguir soluciona esse problema.
/* Recebe um nmero inteiro n > 0, um vetor S de nmeros inteiros com n elementos e um ndice i e estabelece a propriedade max-heap para a rvore S */
void sobe(int n, int S[MAX], int i)
{
while (S[pai(i)] < S[i]) {
troca(&S[i], &S[pai(i)]);
i = pai(i);
}
}
0
26
1
3
16
7
4
2
14
18
12
2
14
18
6
11
3
16
7
4
21
(a)
(b)
0
26
0
26
1
7
4
2
14
18
3
21
8
16
6
11
6
11
3
18
7
4
(c)
2
14
21
16
6
11
(d)
Figura 8.6: Um exemplo de execuo da funo sobe com argumentos (10, S, 8).
FACOM
UFMS
8.2 L ISTAS
61
DE PRIORIDADES
O tempo de execuo de pior caso da funo sobe proporcional altura da rvore binria
correspondente S, isto , proporcional log2 n.
A funo extrai_maxima implementa a operao (3) usando a funo desce , devolvendo tambm o valor de maior prioridade na lista de max-prioridades. Se a lista vazia, a
funo devolve um valor especial para indicar que a remoo no ocorreu.
FACOM
UFMS
62
L ISTAS
DE PRIORIDADES
/* Recebe um nmero inteiro n > 0 e uma lista de max-prioridades S e remove e devolve o valor da maior prioridade em S */
int extrai_maxima(int *n, int S[MAX])
{
int maior;
if (*n > 0) {
maior = S[0];
S[0] = S[*n - 1];
*n = *n - 1;
desce(*n, S, 0);
return maior;
}
else
return ;
}
/* Recebe um nmero inteiro n > 0, uma lista de max-prioridades S, um ndice i e uma prioridade p e devolve a lista de
max-prioridades com a prioridade na posio i modificada */
void aumenta_prioridade(int n, int S[MAX], int i, int p)
{
if (p < S[i])
printf("ERRO: nova prioridade menor que da clula\n");
else {
S[i] = p;
sobe(n, S, i);
}
}
/* Recebe um nmero inteiro n > 0, uma lista de max-prioridades S e uma prioridade p e devolve a lista de max-prioridades com a nova prioridade */
void insere_lista(int *n, int S[MAX], int p)
{
S[*n] = p;
*n = *n + 1;
sobe(*n, S, *n - 1);
}
FACOM
UFMS
8.3 O RDENAO
63
Podemos mostrar que a funo heapsort est correta usando o seguinte invariante do
processo iterativo:
No incio de cada iterao da estrutura de repetio da funo heapsort , o vetor
S[0..i] um max-heap contendo os i menores elementos de S[0..n 1] e o vetor
S[i + 1..n 1] contm os n i maiores elementos de S[0..n 1] em ordem crescente.
O tempo de execuo da funo heapsort proporcional a n log2 n. Note que a chamada
da funo constroi_max_heap gasta tempo proporcional a n e cada uma das n 1 chamadas
funo desce gasta tempo proporcional a log2 n.
Exerccios
Os exerccios foram extrados do livro de Cormen et. al [1].
8.1 A seqncia h23, 17, 14, 6, 13, 10, 1, 5, 7, 12i um max-heap?
8.2 Qual so os nmeros mnimo e mximo de elementos em um max-heap de altura h?
8.3 Mostre que em qualquer sub-rvore de um max-heap, a raiz da sub-rvore contm a
maior prioridade de todas as que ocorrem naquela sub-rvore.
8.4 Em um max-heap, onde pode estar armazenado o elemento de menor prioridade, considerando que todos os elementos so distintos?
FACOM
UFMS
64
L ISTAS
DE PRIORIDADES
FACOM
UFMS
AULA
I NTRODUO
AOS PONTEIROS
66
I NTRODUO
endereo
contedo
00010011
11010101
00111000
10010010
n1
AOS PONTEIROS
00001111
2000
i
2001
UFMS
9.2 O PERADORES
67
DE ENDEREAMENTO E DE INDIREO
int *p;
Essa declarao indica que p uma varivel ponteiro capaz de apontar para objetos do tipo
int . A linguagem C obriga que toda varivel ponteiro aponte apenas para objetos de um tipo
int i, *p;
p = &i;
Atribuir o endereo da varivel i para a varivel p faz com que p aponte para i, como ilustraa figura 9.4.
p
UFMS
68
I NTRODUO
AOS PONTEIROS
int i, *p = &i;
Uma vez que uma varivel ponteiro aponta para um objeto, podemos usar o operador de
indireo * para acessar o valor armazenado no objeto. Se p aponta para i, por exemplo, podemos imprimir o valor de i de forma indireta, como segue:
printf("%d\n", *p);
Observe que a funo printf mostrar o valor de i e no o seu endereo. Observe tambm
que aplicar o operador & a uma varivel produz um ponteiro para a varivel e aplicar o operador * para um ponteiro retoma o valor original da varivel:
j = *&i;
j = i;
Enquanto dizemos que p aponta para i, dizemos tambm que *p um apelido para i. Ademais, no apenas *p tem o mesmo valor que i, mas alterar o valor de *p altera tambm o valor
de i.
Uma observao importante que auxilia a escrever e ler programas com variveis ponteiros
sempre traduzir os operadores unrios de endereo & e de indireo * para endereo da
varivel e contedo da varivel apontada por, respectivamente. Sempre que usamos um desses
operadores no sentido de estabelecer indireo e apontar para valores, importante traduzilos desta forma para fins de clareza.
Observe que no programa 9.1, aps a declarao das variveis c e p, temos a inicializao
do ponteiro p, que recebe o endereo da varivel c, sendo essas duas variveis do mesmo tipo
char . Tambm ocorre a inicializao da varivel c. importante sempre destacar que o valor,
ou contedo, de um ponteiro na linguagem C no tem significado at que contenha, ou aponte,
para algum endereo vlido.
A primeira chamada da funo printf no programa 9.1 mostra o endereo onde a varivel
c se localiza na memria e seu contedo, inicializado com o caractere a na linha anterior.
Note que um endereo pode ser impresso pela funo printf usando o especificador de tipo
%p . Em seguida, um outra chamada funo printf realizada, mostrando o endereo
onde a varavel p se localiza na memria, o contedo da varivel p e o contedo da varivel
apontada por p. Como a varivel p aponta para a varivel c, o valor apresentado na sada
tambm aquele armazenado na varivel c, isto , o caractere a . Na segunda vez que ocorrem
as mesmas chamadas s funes printf , elas so precedidas pela alterao do contedo da
varivel c e, como a varivel p mantm-se apontando para a varivel c, o caractere / seja
FACOM
UFMS
9.2 O PERADORES
DE ENDEREAMENTO E DE INDIREO
69
c = /;
printf("&c = %p
printf("&p = %p
*p = Z;
printf("&c = %p
printf("&p = %p
return 0;
}
*p = Z;
c = a
p = 0x7fffffffc76f
*p = a
&c = 0x7fffffffc76f
&p = 0x7fffffffc760
c = /
p = 0x7fffffffc76f
*p = /
&c = 0x7fffffffc76f
&p = 0x7fffffffc760
c = Z
p = 0x7fffffffc76f
*p = Z
UFMS
70
I NTRODUO
0x7fffffffc76f
AOS PONTEIROS
0x7fffffffc760
0x7fffffffc76f
(a)
(b)
Figura 9.5: Ilustrao das variveis do programa 9.1 aps as inicializaes das variveis. (a)
Representao com endereos. (b) Representao esquemtica.
A linguagem C permite ainda que o operador de atribuio copie ponteiros, supondo que
possuam o mesmo tipo. Suponha que a seguinte declarao tenha sido feita:
Ento, a sentena:
p = &i;
q = p;
Essa sentena copia o contedo de p, o endereo de i, para q, fazendo com que q aponte para o
mesmo lugar que p aponta, como podemos visualizar na figura 9.6.
p
UFMS
9.3 P ONTEIROS
EM EXPRESSES
71
expresso aritmtica envolvendo nmeros do tipo inteiro, supondo o uso correto do operador
de indireo *.
importante observar tambm que os operadores & e *, por serem operadores unrios,
tm precedncia sobre os operadores binrios das expresses aritmticas em que se envolvem.
Por exemplo, em uma expresso aritmtica envolvendo nmeros do tipo inteiro, os operadores
binrios +, -, * e / tm menor prioridade que o operador unrio de indireo *.
Vejamos a seguir, como um exemplo simples, o programa 9.2 que usa um ponteiro como
operando em uma expresso aritmtica.
Programa 9.2: Outro exemplo do uso de ponteiros.
#include <stdio.h>
int main(void)
{
int i, j, *ptr1, *ptr2;
ptr1 = &i;
i = 5;
j = 2 * *ptr1 + 3;
ptr2 = ptr1;
printf("i = %d,
printf("j = %d,
printf("&ptr1 =
printf("&ptr2 =
return 0;
}
i = 5, &i = 0x7fffffffc55c
j = 13, &j = 0x7fffffffc558
&ptr1 = 0x7fffffffc550, ptr1 = 0x7fffffffc55c, *ptr1 = 5
&ptr2 = 0x7fffffffc548, ptr2 = 0x7fffffffc55c, *ptr2 = 5
Exerccios
9.1 Se i uma varivel e p uma varivel ponteiro que aponta para i, quais das seguintes
expresses so apelidos para i?
(a) *p
(b) &p
(c) *&p
(d) &*p
FACOM
UFMS
72
I NTRODUO
AOS PONTEIROS
(e) *i
(f) &i
(g) *&i
(h) &*i
9.2 Se i uma varivel do tipo int e p e q so ponteiros para int , quais das seguintes
atribuies so corretas?
(a) p = i;
(b) *p = &i;
(c) &p = q;
(d) p = &q;
(e) p = *&q;
(f) p = q;
(g) p = *q;
(h) *p = q;
(i) *p = *q;
9.3 Entenda o que o programa 9.3 faz, simulando sua execuo passo a passo. Depois disso,
implemente-o.
Programa 9.3: Programa do exerccio 9.3.
#include <stdio.h>
int main(void)
{
int a, b, *ptr1, *ptr2;
ptr1 = &a;
ptr2 = &b;
a = 1;
(*ptr1)++;
b = a + *ptr1;
*ptr2 = *ptr1 * *ptr2;
printf("a=%d, b=%d, *ptr1=%d, *ptr2=%d\n", a, b, *ptr1, *ptr2);
return 0;
}
9.4 Entenda o que o programa 9.4 faz, simulando sua execuo passo a passo. Depois disso,
implemente-o.
9.5 Entenda o que o programa 9.5 faz, simulando sua execuo passo a passo. Depois disso,
implemente-o.
FACOM
UFMS
9.3 P ONTEIROS
EM EXPRESSES
73
FACOM
UFMS
74
FACOM
I NTRODUO
AOS PONTEIROS
UFMS
AULA
P ROGRAMAS
10
EXTENSOS
Nesta aula aprenderemos como dividir e distribuir nossas funes em vrios arquivos. Esta
possibilidade uma caracterstica importante da linguagem C, permitindo que o(a) programador(a) possa ter sua prpria biblioteca de funes e possa us-la em conjunto com um ou mais
programas.
Os programas que escrevemos at o momento so bem simples e, por isso mesmo pequenos, com poucas linhas de cdigo. No entanto, programas pequenos so uma exceo.
medida que os problemas tm maior complexidade, os programas para solucion-los tm, em
geral, proporcionalmente mais linhas de cdigo. Por exemplo, a verso 2.6.25 do ncleo do
sistema operacional LINUX, de abril de 2008, tem mais de nove milhes de linhas de cdigo
na linguagem C e seria impraticvel mant-las todas no mesmo arquivo1 . Nesta aula veremos que um programa na linguagem C consiste de vrios arquivos-fontes e tambm de alguns
arquivos-cabealhos. aprenderemos a dividir nossos programas em mltiplos arquivos.
Esta aula baseada na referncia [7].
10.1 Arquivos-fontes
At a ltima aula, sempre consideramos que um programa na linguagem C consiste de um
nico arquivo. Na verdade, um programa pode ser dividido em qualquer nmero de arquivosfontes que, por conveno, tm a extenso .c . Cada arquivo-fonte contm uma parte do programa, em geral definies de funes e variveis. Um dos arquivos-fontes de um programa
deve necessariamente conter uma funo main , que o ponto de incio do programa. Quando
dividimos um programa em arquivos, faz sentido colocar funes relacionadas e variveis em
um mesmo arquivo-fonte. A diviso de um programa em arquivos-fontes mltiplos tem vantagens significativas:
agrupar funes relacionadas e variveis em um nico arquivo ajuda a deixar clara a
estrutura do programa;
cada arquivo-fonte pode ser compilado separadamente, com uma grande economia de
tempo se o programa grande modificado muitas vezes;
funes so mais facilmente re-usadas em outros programas quando agrupadas em
arquivos-fontes separados.
1
O sistema operacional LINUX, por ser livre e de cdigo aberto, permite que voc possa consultar seu cdigo
fonte, alm de modific-lo a seu gosto.
75
76
P ROGRAMAS
EXTENSOS
10.2 Arquivos-cabealhos
Quando abordamos, em aulas anteriores, a biblioteca padro e o pr-processador da linguagem C, estudamos com algum detalhe os arquivos-cabealhos. Quando dividimos um
programa em vrios arquivos-fontes, como uma funo definida em um arquivo pode chamar uma outra funo definida em outro arquivo? Como dois arquivos podem compartilhar
a definio de uma mesma macro ou a definio de um tipo? A diretiva #include nos ajuda
a responder a essas perguntas, permitindo que essas informaes possam ser compartilhadas
entre arquivos-fontes.
Muitos programas grandes contm definies de macros e definies de tipos que necessitam ser compartilhadas por vrios arquivos-fontes. Essas definies devem ser mantidas em
arquivos-cabealhos.
Por exemplo, suponha que estamos escrevendo um programa que usa macros com nomes
LOGIC , VERDADEIRO e FALSO . Ao invs de repetir essas macros em cada arquivo-fonte do
programa que necessita delas, faz mais sentido colocar as definies em um arquivo-cabealho
com um nome como logico.h tendo as seguintes linhas:
#define VERDADEIRO 1
#define FALSO
0
#define LOGIC
int
Qualquer arquivo-fonte que necessite dessas definies deve conter simplesmente a linha a
seguir:
#include "logico.h"
#define VERDADEIRO 1
#define FALSO
0
typedef logic int;
Colocar definies de macros e tipos em um arquivo-cabealho tem algumas vantagens. Primeiro, economizamos tempo por no ter de copiar as definies nos arquivos-fontes onde so
necessrias. Segundo, o programa torna-se muito mais fcil de modificar, j que a modificao
da definio de uma macro ou de um tipo necessita ser feita em um nico arquivo-cabealho. E
terceiro, no temos de nos preocupar com inconsistncias em conseqncia de arquivos-fontes
contendo definies diferentes da mesma macro ou tipo.
FACOM
UFMS
77
logic.h
#define VERDADEIRO 1
#define FALSO 0
#define LOGIC int
#include "logic.h"
#include "logic.h"
UFMS
78
P ROGRAMAS
EXTENSOS
Os prottipos dessas funes, alm de uma definio de uma macro, sero mantidos em
um arquivo-cabealho com nome geometricas.h :
FACOM
UFMS
double
double
double
double
double
double
double
double
double
79
perimetroQuadrado(double lado);
perimetroCirculo(double raio);
perimetroTriangulo(double lado1, double lado2, double lado3);
areaQuadrado(double lado);
areaCirculo(double raio);
areaTriangulo(double base, double altura);
volumeCubo(double lado);
volumeEsfera(double raio);
volumeTetraedro(double lado, double altura);
Alm desses prottipos, a macro PI tambm deve ser definida neste arquivo. Ento, um
arquivo-fonte calc.c que calcula medidas de figuras geomtricas e que contm a funo
main pode ser construdo. A figura 10.2 ilustra essa diviso.
geometricas.h
#define PI 3.141592
double perimetroQuadrado(double lado);
double perimetroCirculo(double raio);
#include "geometricas.h"
int main(void)
{
#include "geometricas.h"
}
x = areaCirculo(r);
double perimetroCirculo(double raio)
{
calc.c
geometricas.c
FACOM
UFMS
80
P ROGRAMAS
EXTENSOS
Os dois arquivos-fontes so primeiro compilados em arquivos-objetos. Esse arquivosobjetos so automaticamente passados para o ligador que os combina em um nico arquivo. A
opo -o especifica que queremos que nosso arquivo-executvel tenha o nome calc .
FACOM
UFMS
10.3 D IVISO
DE PROGRAMAS EM ARQUIVOS
81
No arquivo acima, existem 3 grupos de linhas. Cada grupo conhecido como um regra.
A primeira linha em cada regra fornece um arquivo-alvo, seguido pelos arquivos dos quais
ele depende. A segunda linha um comando a ser executado se o alvo deve ser reconstrudo
devido a uma alterao em um de seus arquivos de dependncia.
Na primeira regra, calc o alvo:
A primeira linha dessa regra estabelece que calc depende dos arquivos calc.o e
geometricas.o . Se qualquer um desses dois arquivos foi modificado desde da ltima construo do programa, ento calc precisa ser reconstrudo. O comando na prxima linha indica
como a reconstruo deve ser feita, usando o GCC para ligar os dois arquivos-objetos.
Na segunda regra, calc.o o alvo:
A primeira linha indica que calc.o necessita ser reconstrudo se ocorrer uma alterao
em calc.c ou geometricas.h . A prxima linha mostra como atualizar calc.o atravs da
recompilao de calc.c . A opo -c informa o compilador para compilar calc.c em um
arquivo-objeto, sem lig-lo.
FACOM
UFMS
82
P ROGRAMAS
EXTENSOS
Tendo criado um makefile para um programa, podemos usar o utilitrio make para construir ou reconstruir o programa. verificando a data e a hora associada com cada arquivo do
programa, make determina quais arquivos esto desatualizados. Ento, ele invoca os comandos necessrios para reconstruir o programa.
Algumas dicas para criar makefiles seguem abaixo:
cada comando em um makefile deve ser precedido por um caractere de tabulao horizontal T A B ;
um makefile armazenado em um arquivo com nome Makefile ; quando o utilitrio
make usado, ele automaticamente verifica o contedo do diretrio atual buscando por
esse arquivo;
use
make alvo
onde alvo um dos alvos listados no makefile; se nenhum alvo especificado, make
construir o alvo da primeira regra.
O utilitrio make complicado o suficiente para existirem dezenas de livros e manuais que
nos ensinam a us-lo. Com as informaes desta aula, temos as informaes bsicas necessrias para us-lo na construo de programas extensos divididos em diversos arquivos. Mais
informaes sobre o utilitrio make devem ser buscadas no manual do GNU/Make.
Exerccios
10.1
que receba um nmero inteiro n, com 0 < 0 6 100, e um vetor v de nmeros inteiros
e gere n nmeros inteiros aleatrios armazenando-os em v. Use a funo rand da
biblioteca stdlib .
(b) Crie um arquivo-fonte com todas as funes de ordenao que vimos nas aulas 5, 6, 7
e 8. Crie tambm um arquivo-cabealho correspondente.
(c) Escreva um programa que receba um inteiro n, com 1 6 n 6 10000, gere um seqncia de n nmeros aleatrios e execute os mtodos de ordenao que conhecemos sobre esse vetor, medindo seu tempo de execuo. Use as funes clock e difftime
da biblioteca time .
(d) Crie um makefile para compilar e ligar seu programa.
FACOM
UFMS
AULA
P ONTEIROS
11
E FUNES
Nesta aula revemos ponteiros e funes na linguagem C. At o momento, aprendemos algumas regras de como construir funes que tm parmetros de entrada e sada ou argumentos passados por referncia. Esses argumentos/parmetros, como veremos daqui por diante,
so na verdade ponteiros. O endereo de uma varivel passado como argumento para uma
funo. O parmetro correspondente que recebe o endereo ento um ponteiro. Qualquer
alterao realizada no contedo do parmetro tem reflexos externos funo, no argumento
correspondente. Esta aula baseada especialmente na referncia [7].
83
84
P ONTEIROS
E FUNES
Agora que entendemos os conceitos bsicos que envolvem os ponteiros, podemos olhar o
programa 11.1 e compreender o que est acontecendo, especialmente no que se refere ao uso de
ponteiros como argumentos de funes. Suponha que algum est executando esse programa.
A execuo inicia na primeira linha da funo main e seu efeito ilustrado na figura 11.1.
aux
UFMS
11.1 PARMETROS
DE ENTRADA E SADA ?
85
Depois da declarao da varivel aux , a execuo faz com que o contedo da varivel
apontada por a seja armazenado na varivel aux . Veja a figura 11.5.
aux
aux
aux
UFMS
86
P ONTEIROS
E FUNES
Na linha seguinte da funo main a chamada da funo printf , que mostra os contedo das variveis x e y , que foram trocados, conforme j constatado e ilustrado na figura 11.8. O programa ento salta para a prxima linha e chega ao fim de sua execuo.
Esse exemplo destaca que so realizadas cpias do valores dos argumentos que nesse
caso so endereos das variveis da funo main para os parmetros respectivos da funo
troca . No corpo dessa funo, sempre que usamos o operador de indireo para acessar
algum valor, estamos na verdade acessando o contedo da varivel correspondente dentro da
funo main , que chamou a funo troca . Isso ocorre com as variveis x e y da funo
main , quando copiamos seus endereos nos parmetros a e b da funo troca .
Cpia? Como assim cpia? Ns aprendemos que parmetros passados desta mesma forma
so parmetros de entrada e sada, ou seja, so parmetros passados por referncia e no por
cpia. Esse exemplo mostra uma caracterstica muito importante da linguagem C, que ficou
dissimulada nas aulas anteriores: s h passagem de argumentos por cpia na linguagem C,
ou ainda, no h passagem de argumentos por referncia na linguagem C. O que fazemos de
fato simular a passagem de um argumento por referncia usando ponteiros. Assim, passando
(por cpia) o endereo de uma varivel como argumento para uma funo, o parmetro correspondente deve ser um ponteiro e, mais que isso, um ponteiro para a varivel correspondente
cujo endereo foi passado como argumento. Dessa forma, qualquer modificao indireta realizada no corpo dessa funo usando esse ponteiro ser realizada na verdade no contedo da
varivel apontada pelo parmetro, que simplesmente o contedo da varivel passada como
argumento na chamada da funo.
No h nada de errado com o que aprendemos nas aulas anteriores sobre argumentos de
entrada e sada, isto , passagem de argumentos por referncia. No entanto, vale ressaltar que
passagem de argumentos por referncia um tpico conceitual quando falamos da linguagem
de programao C. O correto repetir sempre que s h passagem de argumentos por cpia na
linguagem C.
UFMS
11.2 D EVOLUO
DE PONTEIROS
87
Quando chamamos a funo max , passamos ponteiros para duas variveis do tipo int e
armazenamos o resultado em uma varivel ponteiro:
int i, j, *p;
.
.
.
p = max(&i, &j);
Exerccios
11.1
que receba um nmero inteiro n, com 1 6 n 6 100, e um vetor v com n > 0 nmeros
inteiros e devolva um maior e um menor dos elementos desse vetor.
(b) Escreva um programa que receba n > 0 nmeros inteiros, armazene-os em um vetor
e, usando a funo do item (a), mostre na sada um maior e um menor elemento
desse conjunto.
Simule no papel a execuo de seu programa antes de implement-lo.
11.2
que receba um nmero intero n, com 1 6 n 6 100, e um vetor v com n > 0 nmeros
inteiros e devolva um maior e um segundo maior elementos desse vetor.
(b) Escreva um programa que receba n > 0 nmeros inteiros, armazene-os em um vetor
e, usando a funo do item (a), mostre na sada um maior e um segundo maior
elemento desse conjunto.
Simule no papel a execuo de seu programa antes de implement-lo.
11.3
FACOM
UFMS
88
P ONTEIROS
E FUNES
que receba dois nmeros inteiros a e b e devolva a soma e o produto destes dois
nmeros.
(b) Escreva um programa que receba n nmeros inteiros, com n > 0 par, calcule a soma
e o produto deste conjunto usando a funo do item (a) e determine quantos deles
so maiores que esta soma e quantos so maiores que o produto. Observe que os
nmeros na entrada podem ser negativos.
Simule no papel a execuo de seu programa antes de implement-lo.
11.4
FACOM
UFMS
AULA
P ONTEIROS
12
E VETORES
Nas aulas 9 e 11 aprendemos o que so os ponteiros e tambm como so usados como argumentos/parmetros de funes e devolvidos de funes. Nesta aula veremos outra aplicao
para os ponteiros. A linguagem C nos permite usar expresses aritmticas de adio e subtrao com ponteiros que apontam para elementos de vetores. Essa uma forma alternativa de
trabalhar com vetores e seus ndices. Para nos tornarmos melhores programadores na linguagem C necessrio conhecer bem essa relao ntima entre ponteiros e vetores. Alm disso, o
uso de ponteiros para trabalhar com vetores vantajoso em termos de eficincia do programa
executvel resultante. Esta aula baseada nas referncias [2, 7].
Podemos fazer o ponteiro p apontar para o primeiro elemento do vetor v fazendo a seguinte
atribuio, como mostra a figura 12.1:
p = &v[0];
v
0
90
P ONTEIROS
E VETORES
5
0
Figura 12.2: *p = 5;
Podemos ainda executar aritmtica com ponteiros ou aritmtica com endereos sobre p e
assim acessamos outros elementos do vetor v. A linguagem C possibilita trs formas de aritmtica com ponteiros: (i) adicionar um nmero inteiro a um ponteiro; (ii) subtrair um nmero
inteiro de um ponteiro; e (iii) subtrair um ponteiro de outro ponteiro.
Vamos olhar para cada uma dessas operaes. Suponha que temos declaradas as seguintes
variveis:
int v[10], *p, *q, i;
p = &v[5];
q = &v[1];
UFMS
12.1 A RITMTICA
91
COM PONTEIROS
v
0
v
0
v
0
FACOM
UFMS
92
P ONTEIROS
E VETORES
v
0
v
0
v
0
FACOM
UFMS
12.2 U SO
93
v
0
.
.
.
#define DIM 100
int main(void)
{
int v[DIM], soma, *p;
.
.
.
soma = 0;
for (p = &v[0]; p < &v[DIM]; p++)
soma = soma + *p;
.
.
.
v[i++] = j;
FACOM
UFMS
94
P ONTEIROS
E VETORES
*p++ = j;
*(p++) = j;
O valor da expresso *p++ o valor de *p , antes do incremento. Depois que esse valor
devolvido, a sentena incrementa p.
A expresso *p++ no a nica combinao possvel dos operadores * e ++ . Podemos
escrever (*p)++ para incrementar o valor de *p . Nesse caso, o valor devolvido pela expresso tambm *p , antes do incremento. Em seguida, a sentena incrementa *p . Ainda,
podemos escrever *++p ou ainda ++*p . O primeiro caso incrementa p e o valor da expresso
*p , depois do incremento. O segundo incrementa *p e o valor da expresso *p , depois
do incremento.
O trecho de cdigo acima, que realiza a soma dos elementos do vetor v usando aritmtica
com ponteiros, pode ento ser reescrito como a seguir, usando uma combinao dos operadores
* e ++ .
.
.
.
soma = 0;
p = &v[0];
while (p < &v[DIM])
soma = soma + *p++;
.
.
.
int v[10];
FACOM
UFMS
12.3 U SO
95
Usando v como um ponteiro para o primeiro elemento do vetor, podemos modificar o contedo de v[0] da seguinte forma:
*v = 7;
*(v + 1) = 12;
soma = 0;
for (p = &v[0]; p < &v[DIM]; p++)
soma = soma + *p;
Para simplificar essa estrutura de repetio, podemos substituir &v[0] por v e &v[DIM]
por v + DIM , como mostra o trecho de cdigo abaixo:
soma = 0;
for (p = v; p < v + DIM; p++)
soma = soma + *p;
while (*v != 0)
v++;
O programa 12.1 mostra um exemplo do uso desses conceitos, realizando a impresso dos
elementos de um vetor na ordem inversa da qual forma lidos.
FACOM
UFMS
96
P ONTEIROS
E VETORES
Outro uso do identificador de um vetor como um ponteiro quando um vetor um argumento em uma chamada de funo. Nesse caso, o vetor sempre tratado como um ponteiro.
Considere a seguinte funo que recebe um vetor de n nmeros inteiros e devolve um maior
elemento nesse vetor.
/* Recebe um nmero inteiro n > 0 e um vetor v com n
nmeros inteiros e devolve um maior elemento em v */
int max(int n, int v[MAX])
{
int i, maior;
maior = v[0];
for (i = 1; i < n; i++)
if (v[i] > maior)
maior = v[i];
return maior;
}
M = max(N, u);
Essa chamada faz com que o endereo do primeiro compartimento do vetor u seja atribudo
v. O vetor u no de fato copiado.
Para indicar que queremos que um parmetro que um vetor no seja modificado, podemos incluir a palavra reservada const precedendo a sua declarao.
FACOM
UFMS
12.3 U SO
97
Quando uma varivel simples passada para uma funo, isto , quando um argumento
de uma funo, seu valor copiado no parmetro correspondente. Ento, qualquer alterao no
parmetro correspondente no afeta a varivel. Em contraste, um vetor usado como um argumento no est protegido contra alteraes, j que no ocorre uma cpia do vetor todo. Desse
modo, o tempo necessrio para passar um vetor a uma funo independe de seu tamanho. No
h perda por passar vetores grandes, j que nenhuma cpia do vetor realizada. Alm disso,
um parmetro que um vetor pode ser declarado como um ponteiro. Por exemplo, a funo
max descrita acima pode ser declarada como a seguir:
/* Recebe um nmero inteiro n > 0 e um ponteiro v para um vetor com n nmeros inteiros e devolve um maior elemento em v */
int max(int n, int *v)
{
int i, maior;
maior = v[0];
for (i = 1; i < n; i++)
if (v[i] > maior)
maior = v[i];
return maior;
}
Neste caso, declarar o parmetro v como sendo um ponteiro equivalente a declarar v como
sendo um vetor. O compilador trata ambas as declaraes como idnticas.
Apesar de a declarao de um parmetro como um vetor ser equivalente declarao do
mesmo parmetro como um ponteiro, o mesmo no vale para uma varivel. A declarao a
seguir:
int v[10];
faz com que o compilador reserve espao para 10 nmeros inteiros. Por outro lado, a declarao
abaixo:
int *v;
faz o compilador reservar espao para uma varivel ponteiro. Nesse ltimo caso, v no um
vetor e tentar us-lo como tal pode causar resultados desastrosos. Por exemplo, a atribuio:
*v = 7;
armazena o valor 7 onde v est apontando. Como no sabemos para onde v est apontando, o
resultado da execuo dessa linha de cdigo imprevisvel.
FACOM
UFMS
98
P ONTEIROS
E VETORES
Do mesmo modo, podemos usar uma varivel ponteiro, que aponta para uma posio de
um vetor, como um vetor. O trecho de cdigo a seguir ilustra essa afirmao.
.
.
.
#define DIM 100
int main(void)
{
int v[DIM], soma, *p;
.
.
.
soma = 0;
p = v;
for (i = 0; i < DIM; i++)
soma = soma + p[i];
O compilador trata a referncia p[i] como *(p + i) , que uma forma possvel de usar
aritmtica com ponteiros, como vimos anteriormente. Essa possibilidade de uso, que parece um
tanto estranha primeira vista, muito til em alocao dinmica de memria, como veremos
em uma prxima aula.
Exerccios
12.1 Suponha que as declaraes e atribuies simultneas tenham sido realizadas nas variveis listadas abaixo:
UFMS
12.3 U SO
99
.
.
.
#define N 10
int main(void)
{
int v[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = &v[0], *q = &v[N - 1], temp;
while (p < q) {
temp = *p;
*p++ = *q;
*q-- = temp;
}
.
.
.
FACOM
UFMS
100
FACOM
P ONTEIROS
E VETORES
UFMS
AULA
P ONTEIROS
13
E MATRIZES
Na aula 12, trabalhamos com ponteiros e vetores. Nesta aula veremos as relaes entre
ponteiros e matrizes. Neste caso, necessrio estender o conceito de indireo (simples) para
indireo dupla. Veremos tambm como estender esse conceito para indireo mltipla e suas
relaes com variveis compostas homogneas multi-dimensionais.
Esta aula baseada na referncia [7].
linha 1
linha n 1
int A[LINHAS][COLUNAS];
A forma que aprendemos inicializar uma matriz pode ser aplicada matriz A declarada
acima e ento o seguinte trecho de cdigo realiza a inicializao desejada:
101
102
P ONTEIROS
E MATRIZES
int i, j;
.
.
.
for (i = 0; i < LINHAS; i++)
for (j = 0; j < COLUNAS; j++)
A[i][j] = 0;
Mas se vemos a matriz A da forma como armazenada na memria, isto , como um vetor
unidimensional, podemos trocar o par de estruturas de repetio por uma nica estrutura de
repetio:
int *p;
.
.
.
for (p = &A[0][0]; p <= &A[LINHAS-1][COLUNAS-1]; p++)
*p = 0;
p = A[i];
UFMS
13.3 P ROCESSAMENTO
103
Como A[i] um ponteiro para a linha i da matriz A, podemos passar A[i] para um
funo que espera receber um vetor como argumento. Em outras palavras, um funo que foi
projetada para trabalhar com um vetor tambm pode trabalhar com uma linha de uma matriz.
Dessa forma, a funo max da aula 12 pode ser chamada com a linha i da matriz A como
argumento:
M = max(COLUNAS, A[i]);
Nesse exemplo, declaramos p como um ponteiro para um vetor de dimenso COLUNAS , cujos elementos so nmeros inteiros. Os parnteses envolvendo *p so necessrios, j que sem
eles o compilador trataria p como um vetor de ponteiros em vez de um ponteiro para um vetor.
A expresso p++ avana p para a prxima linha. Na expresso (*p)[j] , *p representa uma
linha inteira de A e assim (*p)[j] seleciona o elemento na coluna j da linha. Os parnteses
so essenciais na expresso (*p)[i] , j que, sem eles, o compilador interpretaria a expresso
como *(p[i]) .
UFMS
104
P ONTEIROS
E MATRIZES
int A[LINHAS][COLUNAS];
Neste caso, A no um ponteiro para A[0][0] . Ao contrrio, um ponteiro para A[0] . Isso
faz mais sentido se olharmos sob o ponto de vista da linguagem C, que considera A no como
uma matriz bidimensional, mas como um vetor. Quando usado como um ponteiro, A tem tipo
int (*)[COLUNAS] , um ponteiro para um vetor de nmeros inteiros de tamanho COLUNAS .
Saber que A aponta para A[0] til para simplificar estruturas de repetio que processam elementos de uma matriz. Por exemplo, ao invs de escrever:
Com essa idia, podemos fazer o compilador acreditar que uma varivel composta homognea multi-dimensional unidimensional, isto , um vetor. Por exemplo, podemos passar a
matriz A como argumento para a funo max da aula 12 da seguinte forma:
M = max(LINHAS*COLUNAS, A[0]);
j que A[0] aponta para o elemento na linha 0 e coluna 0 e tem tipo int * e assim essa
chamada ser executada corretamente.
Exerccios
13.1 Escreva uma funo que preencha uma matriz quadrada de dimenso n com a matriz
identidade In . Use um nico ponteiro que percorra a matriz.
13.2 Reescreva a funo abaixo usando aritmtica de ponteiros em vez de ndices de matrizes.
Em outras palavras, elimine as variveis i e j e todos os [] . Use tambm uma nica
estrutura de repetio.
FACOM
UFMS
13.4 I DENTIFICADORES
105
FACOM
UFMS
106
FACOM
P ONTEIROS
E MATRIZES
UFMS
AULA
P ONTEIROS
14
E CADEIAS
Nosso primeiro contato com literais foi ainda em Algoritmos e Programao I. Literais ocorrem com freqncia na chamada das funes printf e scanf . Mas quando chamamos uma
dessas funes e fornecemos uma literal como argumento, o que de fato estamos passando?
Em essncia, a linguagem C trata literais como cadeias de caracteres. Quando o compilador
encontra uma literal de comprimento n em um programa, ele reserva um espao de n + 1 bytes
na memria. Essa rea de memria conter os caracteres da literal mais o caracter nulo que indica o final da cadeia. O caracter nulo um byte cujos bits so todos zeros e representado pela
seqncia de caracteres \0 . Por exemplo, a literal "abc" armazenada como uma cadeia de
quatro caracteres, como mostra a figura 14.1.
a
\0
Frase de Bill Gates, dono da Micro$oft, em uma entrevista para Computer Magazine.
107
108
P ONTEIROS
E CADEIAS
printf("abc");
o endereo da literal "abc" passado como argumento para a funo printf , isto , o endereo de onde se encontra o caractere a na memria.
Em geral, podemos usar uma literal sempre que a linguagem C permita o uso de um ponteiro do tipo char * . Por exemplo, uma literal pode ocorrer do lado direito de uma atribuio,
como mostrado a seguir:
char *p;
p = "abc";
Essa atribuio no copia os caracteres de "abc" , mas faz o ponteiro p apontar para o primeiro
caractere da literal, como mostra a figura 14.2.
p
\0
char cadeia[TAM+1];
onde TAM uma macro definida com o tamanho da cadeia de caracteres mais longa que pode
ser armazenada na varivel cadeia .
Lembrando ainda, podemos inicializar uma cadeia de caracteres no momento de sua declarao, como mostra o exemplo abaixo:
UFMS
14.1 L ITERAIS
109
E PONTEIROS
\0
No caso em que o inicializador menor que o tamanho definido para a cadeia de caracteres,
o compilador preencher as posies finais restantes da cadeia com o caractere nulo. Por outro
lado, sempre importante garantir que o inicializador tenha menor comprimento que o tamanho do vetor declarado. Tambm, podemos omitir o tamanho do vetor em uma declarao e
inicializao simultneas, caso em que o vetor ter o tamanho equivalente ao comprimento do
inicializador mais uma unidade, que equivale ao caractere nulo.
Agora, vamos comparar a declarao abaixo:
que declara um vetor data , que uma cadeia de caracteres, com a declarao a seguir:
que declara data como um ponteiro. Devido relao estrita entre vetores e ponteiros que
vimos na aula 12, podemos usar as duas verses da declarao de data como uma cadeia
de caracteres. Em particular, qualquer funo que receba um vetor/cadeia de caracteres ou
um ponteiro para caracteres aceita qualquer uma das verses da declarao da varivel data
apresentada acima.
No entanto, devemos ter cuidado para no cometer o erro de acreditar que as duas verses
da declarao de data so equivalentes e intercambiveis. Existem diferenas significativas
entre as duas, que destacamos abaixo:
na verso em que a varivel declarada como um vetor, os caracteres armazenados em
data podem ser modificados, como fazemos com elementos de qualquer vetor; na verso em que a varivel declarada como um ponteiro, data aponta para uma literal que,
como j vimos, no pode ser modificada;
FACOM
UFMS
110
P ONTEIROS
E CADEIAS
Se precisamos que uma cadeia de caracteres seja modificada, nossa responsabilidade declarar um vetor de caracteres no qual ser armazenada essa cadeia. Declarar um ponteiro no
suficiente, neste caso. Por exemplo, a declarao abaixo:
char *p;
faz com que o compilador reserve espao suficiente para uma varivel ponteiro. Infelizmente,
o compilador no reserva espao para uma cadeia de caracteres, mesmo porque, no h indicao alguma de um possvel comprimento da cadeia de caracteres que queremos armazenar.
Antes de usarmos p como uma cadeia de caracteres, temos de faz-la apontar para um vetor
de caracteres. Uma possibilidade fazer p apontar para uma varivel que uma cadeia de
caracteres, como mostramos a seguir:
Com essa atribuio, p aponta para o primeiro caractere de cadeia e assim podemos usar
p como uma cadeia de caracteres. Outra possibilidade fazer p apontar para uma cadeia de
caracteres dinamicamente alocada, como veremos na aula 16.
Ainda poderamos discorrer sobre processos para leitura e escrita de cadeias de caracteres,
sobre acesso aos caracteres de uma cadeia de caracteres e tambm sobre o uso das funes da
biblioteca da linguagem C que trabalham especificamente com cadeias de caracteres, o que j
fizemos nas aulas de Algoritmos e Programao I. Ainda veremos a seguir dois tpicos importantes sobre cadeias de caracteres: vetores de cadeias de caracteres e argumentos de linha de
comando.
FACOM
UFMS
14.2 V ETORES
111
DE CADEIAS DE CARACTERES
Observe que estamos omitindo o nmero de linhas da matriz, que fornecido pelo inicializador, mas a linguagem C exige que o nmero de colunas seja especificado, conforme fizemos na
declarao.
A figura 14.4 ilustra a declarao e inicializao da varivel planetas . Observe que todas as cadeias cabem nas colunas da matriz e, tambm, que h um tanto de compartimentos
desperdiados na matriz, preenchidos com o caractere \0 , j que nem todas as cadeias so
compostas por 8 caracteres.
0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
\0
Note que h poucas diferenas entre essa declarao e a declarao anterior da varivel
planetas : removemos um par de colchetes com um nmero no interior deles e colocamos
um asterisco precedendo o identificador da varivel. No entanto, o efeito dessa declarao na
memria muito diferente, como podemos ver na figura 14.5.
Cada elemento do vetor planetas um ponteiro para uma cadeia de caracteres, terminada com um caractere nulo. No h mais desperdcio de compartimentos nas cadeias de caracteres, apesar de termos de alocar espao para os ponteiros no vetor planetas . Para acessar
FACOM
UFMS
112
P ONTEIROS
E CADEIAS
planetas
0
\0
\0
\0
\0
\0
\0
\0
\0
\0
um dos nomes dos planetas necessitamos apenas do ndice do vetor. Para acessar um caractere
do nome de um planeta devemos fazer da mesma forma como acessamos um elemento em
uma matriz. Por exemplo, para buscar cadeias de caracteres no vetor planetas que iniciam
com a letra M, podemos usar o seguinte trecho de cdigo:
prompt$ ls
em uma linha de comando, o resultado ser uma listagem de nomes dos arquivos no diretrio
atual. Se digitamos o comando seguido de uma opo, como abaixo:
FACOM
UFMS
14.3 A RGUMENTOS
113
NA LINHA DE COMANDOS
prompt$ ls -l
ento o resultado uma listagem detalhada2 que nos mostra o tamanho de cada arquivo, seu
proprietrio, a data e hora em que houve a ltima modificao no arquivo e assim por diante.
Para modificar ainda mais o comportamento do comando ls podemos especificar que ele
mostre detalhes de apenas um arquivo, como mostrado abaixo:
prompt$ ls -l exerc1.c
prompt$ ls -l exerc1.c
ento argc conter o valor 3, argv[0] apontar para a cadeia de caracteres com o nome do
programa, argv[1] apontar para a cadeia de caracteres "-l" , argv[2] apontar para a
cadeia de caracteres "exerc1.c" e argv[3] apontar para nulo. A figura 14.6 ilustra essa
situao. Observe que o nome do programa no foi listado porque pode incluir o nome do
diretrio ao qual o programa pertence ou ainda outras informaes que dependem do sistema
operacional.
l do ingls long.
FACOM
UFMS
PSfrag
114
P ONTEIROS
E CADEIAS
argv
0
1
\0
\0
int i;
.
.
.
for (i = 1; i < argc; i++)
printf("%s\n", argv[i]);
FACOM
UFMS
14.3 A RGUMENTOS
NA LINHA DE COMANDOS
115
Se o programa 14.1 tem o nome planetas.c e seu executvel correspondente tem nome
planetas , ento podemos executar esse programa com uma seqncia de cadeias de caracteres, como mostramos no exemplo abaixo:
Jupiter o planeta 5
venus no um planeta
Terra o planeta 3
Joaquim no um planeta
Exerccios
14.1 As chamadas de funes abaixo supostamente escrevem um caractere de mudana de
linha na sada, mas algumas delas esto erradas. Identifique quais chamadas no funcionam e explique o porqu.
(a) printf("%c", \n);
(b) printf("%c", "\n");
(c) printf("%s", \n);
(d) printf("%s", "\n");
(e) printf(\n);
(f) printf("\n");
(g) putchar(\n);
(h) putchar("\n");
14.2 Suponha que declaramos um ponteiro p como abaixo:
char *p = "abc";
Quais das chamadas abaixo esto corretas? Mostre a sada produzida por cada chamada
correta e explique por que a(s) outra(s) no est(o) correta(s).
(a) putchar(p);
(b) putchar(*p);
(c) printf("%s", p);
(d) printf("%s", *p);
FACOM
UFMS
116
P ONTEIROS
E CADEIAS
char s[MAX+1];
int i, j;
12abc34 56def78
#include <stdio.h>
int main(void)
{
char s[] = "Dvmuvsb", *p;
for (p = s; *p; p++)
--*p;
printf("%s\n", s);
return 0;
}
14.6
FACOM
UFMS
14.3 A RGUMENTOS
NA LINHA DE COMANDOS
117
que receba uma cadeia de caracteres (terminada com um caractere nulo) contendo
caracteres arbitrrios e substitua os caracteres que so letras minsculas nessa cadeia
por letras maisculas. Use cadeia apenas como vetor, juntamente com os ndices
necessrios.
(b) Escreva uma funo com a seguinte interface:
void maiuscula(char *cadeia)
que receba uma cadeia de caracteres (terminada com um caractere nulo) contendo
caracteres arbitrrios e substitua os caracteres que so letras minsculas nessa cadeia
por letras maisculas. Use apenas ponteiros e aritmtica com ponteiros.
14.7
(a) Escreva uma funo que receba uma cadeia de caracteres e devolva o nmero total
de caracteres que ela possui.
(b) Escreva uma funo que receba uma cadeia de caracteres e devolva o nmero de
vogais que ela possui.
(c) Escreva uma funo que receba uma cadeia de caracteres e devolva o nmero de
consoantes que ela possui.
(d) Escreva um programa que receba diversas cadeias de caracteres e faa a mdia do
nmero de vogais, de consoantes e de smbolos de pontuao que elas possuem.
Use apenas ponteiros nas funes em (a), (b) e (c).
14.8 Escreva um programa que encontra a maior e a menor palavra de uma seqncia de
palavras informadas pelo(a) usurio(a). O programa deve terminar se uma palavra de
quatro letras for fornecida na entrada. Considere que nenhuma palavra tem mais que 20
letras.
Um exemplo de entrada e sada do programa pode ser assim visualizado:
Informe
Informe
Informe
Informe
Informe
Informe
Informe
uma
uma
uma
uma
uma
uma
uma
palavra:
palavra:
palavra:
palavra:
palavra:
palavra:
palavra:
laranja
melao
tomate
cereja
uva
banana
maca
14.9 Escreva um programa com nome reverso.c que mostra os argumentos da linha de
comandos em ordem inversa. Por exemplo, executando o programa da seguinte forma:
UFMS
118
P ONTEIROS
E CADEIAS
faca e garfo
14.10 Escreva um programa com nome soma.c que soma todos os argumentos informados
na linha de comandos, considerando que todos eles so nmeros inteiros. Por exemplo,
executando o programa da seguinte forma:
prompt$ ./soma 81 25 2
108
FACOM
UFMS
AULA
P ONTEIROS
15
E REGISTROS
Nesta aula trabalharemos com ponteiros e registros. Primeiro, veremos como declarar e
usar ponteiros para registros. Essas tarefas so equivalentes as que j fizemos quando usamos
ponteiros para nmeros inteiros, por exemplo. Alm disso, vamos adicionar tambm ponteiros
como campos de registros. muito comum usar registros contendo ponteiros em estruturas de
dados poderosas, como listas lineares e rvores, para soluo de problemas. Esta aula baseada
nas referncias [2, 7].
struct
int
int
int
};
data {
dia;
mes;
ano;
A partir dessa definio, podemos declarar variveis do tipo struct data , como abaixo:
E ento, assim como fizemos com ponteiros para inteiros, caracteres e nmeros de ponto
flutuante, podemos declarar um ponteiro para o registro data da seguinte forma:
Podemos, a partir dessa declarao, fazer uma atribuio varivel p como a seguir:
p = &hoje;
119
120
P ONTEIROS
E REGISTROS
Alm disso, podemos atribuir valores aos campos do registro de forma indireta, como fazemos abaixo:
(*p).dia = 11;
Essa atribuio tem o efeito de armazenar o nmero inteiro 11 no campo dia da varivel
hoje , indiretamente atravs do ponteiro p no entanto. Nessa atribuio, os parnteses envolvendo *p so necessrios porque o operador . , de seleo de campo de um registro, tem
maior prioridade que o operador * de indireo. importante relembrar tambm que essa
forma de acesso indireto aos campos de um registro pode ser substituda, e tem o mesmo efeito,
pelo operador -> como mostramos no exemplo abaixo:
p->dia = 11;
data {
dia;
mes;
ano;
int main(void)
{
struct data hoje, *p;
p = &hoje;
p->dia = 13;
p->mes = 10;
p->ano = 2010;
printf("A data de hoje %d/%d/%d\n", hoje.dia, hoje.mes, hoje.ano);
return 0;
}
UFMS
15.2 R EGISTROS
121
CONTENDO PONTEIROS
10 2010
hoje
struct reg_pts {
int *pt1;
int *pt2;
};
A partir dessa definio, podemos declarar variveis (registros) do tipo struct reg_pts
como a seguir:
Em seguida, a varivel bloco pode ser usada como sempre fizemos. Note apenas que
bloco no um ponteiro, mas um registro que contm dois campos que so ponteiros. Veja o
programa 15.2, que mostra o uso dessa varivel.
Observe atentamente a diferena entre (*p).dia e *reg.pt1 . No primeiro caso, p um
ponteiro para um registro e o acesso indireto a um campo do registro, via esse ponteiro, tem de
ser feito com a sintaxe (*p).dia , isto , o contedo do endereo contido em p um registro e,
portanto, a seleo do campo descrita fora dos parnteses. No segundo caso, reg um registro e no um ponteiro para um registro e como contm campos que so ponteiros, o acesso
ao contedo dos campos realizado atravs do operador de indireo * . Assim, *reg.pt1
significa que queremos acessar o contedo do endereo apontado por reg.pt1 . Como o operador de seleo de campo . de um registro tem prioridade pelo operador de indireo * ,
no h necessidade de parnteses, embora pudssemos us-los da forma *(reg.pt1) . A figura 15.2 ilustra essa situao.
pt1 pt2
100
i1 2
i2
reg
FACOM
UFMS
122
P ONTEIROS
E REGISTROS
Exerccios
#include <stdio.h>
struct dois_valores {
int vi;
float vf;
};
int main(void)
{
struct dois_valores reg1 = {53, 7.112}, reg2, *p = ®1;
reg2.vi = (*p).vf;
reg2.vf = (*p).vi;
printf("1: %d %f\n2: %d %f\n", reg1.vi, reg1.vf, reg2.vi, reg2.vf);
return 0;
}
UFMS
15.2 R EGISTROS
CONTENDO PONTEIROS
123
#include <stdio.h>
struct pts {
char *c;
int *i;
float *f;
};
int main(void)
{
char caractere;
int inteiro;
float real;
struct pts reg;
reg.c = &caractere;
reg.i = &inteiro;
reg.f = ℜ
scanf("%c%d%f", reg.c, reg.i, reg.f);
printf("%c\n%d\n%f\n", caractere, inteiro, real);
return 0;
}
#include <stdio.h>
struct celula {
int valor;
struct celula *prox;
};
int main(void)
{
struct celula reg1, reg2, *p;
scanf("%d%d", ®1.valor, ®2.valor);
reg1.prox = ®2;
reg2.prox = NULL;
for (p = ®1; p != NULL; p = p->prox)
printf("%d ", p->valor);
printf("\n");
return 0;
}
FACOM
UFMS
124
FACOM
P ONTEIROS
E REGISTROS
UFMS
AULA
U SO AVANADO DE
16
PONTEIROS
Nas aulas 9 a 15 vimos formas importantes de uso de ponteiros: como parmetros de funes simulando passagem por referncia e como elementos da linguagem C que podem acessar
indiretamente outros compartimentos de memria, seja uma varivel, uma clula de um vetor
ou de uma matriz ou ainda um campo de um registro, usando, inclusive uma aritmtica especfica para tanto.
Nesta aula, baseada nas referncias [2, 7], veremos outros usos para ponteiros: como auxiliares na alocao dinmica de espaos de memria, como ponteiros para funes e como
ponteiros para outros ponteiros.
126
U SO
AVANADO DE PONTEIROS
ptpt1 = &pt1;
ptpt2 = &pt2;
fazem das variveis ptpt1 e ptpt2 ponteiros para ponteiros. Ou seja, ptpt1 contm o endereo da varivel pt1 e ptpt2 contm o endereo da varivel pt2 . Por sua vez, a varivel
pt1 contm o endereo da varivel x e a varivel pt2 contm o endereo da varivel y ,
o que caracteriza as variveis ptpt1 e ptpt2 declaradas no programa 16.1 como ponteiros
para ponteiros.
Observe finalmente que para acessar o contedo do endereo apontado pelo endereo apontado por ptpt1 temos de usar o smbolo de indireo dupla ** , como pode ser verificado na
ltima chamada funo printf do programa.
Veja a figura 16.1 que ilustra indireo dupla no contexto do programa 16.1.
FACOM
UFMS
16.2 A LOCAO
127
DINMICA DE MEMRIA
1
ptpt1
pt1
4
pt2
ptpt2
int vet[100];
float mat[40][60];
Nesse caso, temos a declarao de um vetor com identificador vet e 100 posies de memria
que podem armazenar nmeros inteiros e uma matriz com identificador mat de 40 linhas e
60 colunas que so compartimentos de memria que podem armazenar nmeros de ponto flutuante. Todos os compartimentos dessas variveis compostas homogneas ficam disponveis
para uso durante a execuo do programa.
Em diversas aplicaes, para que os dados de entrada sejam armazenados em variveis
compostas homogneas com dimenso(es) adequadas, necessrio saber antes essa(s) dimenso(es), o que ento solicitado a um(a) usurio(a) do programa logo de incio. Por exemplo,
o(a) usurio(a) da aplicao pode querer informar a quantidade de elementos que ser armazenada no vetor vet , um valor que ser mantido no programa para verificao do limite de
armazenamento e que no deve ultrapassar o limite mximo de 100 elementos. Note que a
previso de limitante mximo deve sempre ser especificada tambm. Do mesmo modo, o(a)
usurio(a) pode querer informar, antes de usar essa estrutura, quantas linhas e quantas colunas
da matriz mat sero usadas, sem ultrapassar o limite mximo de 40 linhas e 60 colunas. Se o(a)
usurio(a), por exemplo, usar apenas 10 compartimentos do vetor vet ou apenas 3 3 compartimentos da matriz mat durante a execuo do programa, os compartimentos restantes no
sero usados, embora tenham sido alocados na memria durante suas declaraes, ocupando
espao desnecessrio na memria do sistema computacional.
FACOM
UFMS
128
U SO
AVANADO DE PONTEIROS
Essa alocao que acabamos de descrever, e que conhecemos bem de muito tempo, chamada de alocao esttica de memria, o que significa que, no incio da execuo do programa, quando encontramos uma declarao como essa, ocorre a reserva na memria principal de um nmero fixo de compartimentos correspondentes ao nmero especificado na declarao. Esse espao fixo e no pode ser alterado durante a execuo do programa de forma
alguma. Ou seja, no h possibilidade de realocar espao na memria, nem diminuindo-o nem
aumentando-o.
Alocao esttica de variveis e compartimentos no-usados disponveis na memria podem no ter impacto significativo em programas pequenos, que fazem pouco uso da memria
principal, como a grande maioria de nossos programas que desenvolvemos at aqui. No entanto, devemos sempre ter em mente que a memria um recurso limitado e que em programas
maiores e que armazenam muitas informaes na memria principal temos, de alguma forma,
de us-la de maneira eficiente, economizando compartimentos sempre que possvel. Essa diretriz, infelizmente, no pode ser atingida com uso de alocao esttica de compartimentos de
memria.
Nesse sentido, se pudssemos declarar, por exemplo, variveis compostas homogneas
vetores e matrizes em particular com o nmero exato de compartimentos que sero de fato
usados durante a execuo do programa, ento evidente que no haveria esse desperdcio
mencionado.
No exemplo anterior das declaraes das variveis vet e mat , economizaramos espao
significativo na memria principal, caso necessitssemos usar apenas 10 compartimentos no
vetor vet e 9 compartimentos na matriz mat , por exemplo, j que ambas foram declaradas
com muitos compartimentos mais. No entanto, em uma outra execuo subseqente do mesmo
programa que declara essas variveis, poderiam ser necessrias capacidades diferentes, bem
maiores, para ambas as variveis. Dessa forma, fixar um valor especfico baseado na execuo
do programa torna-se invivel e o que melhor podemos fazer quando usamos alocao esttica
de memria prever um limitante mximo para esssas quantidades, conforme vimos fazendo
at aqui.
Felizmente para ns, programadores e programadoras da linguagem C, possvel alocar
dinamicamente um ou mais blocos de memria na linguagem C. Isso significa que possvel
alocar compartimentos de memria durante a execuo do programa, com base nas demandas
do(a) usurio(a).
Alocao dinmica de memria significa que um programa solicita ao sistema computacional, durante a sua execuo, blocos da memria principal que estejam disponveis para uso
naquele momento. Caso haja espao suficiente disponvel na memria principal, a solicitao
atendida e o espao solicitado fica reservado na memria para aquele uso especfico. Caso contrrio, isto , caso no haja possibilidade de atender a solicitao de espao, uma mensagem de
erro em tempo de execuo emitida para o(a) usurio(a) do programa. O(a) programador(a)
tem de estar preparado para esse tipo de situao, incluindo testes de verificao de situaes
como essa no arquivo-fonte, informando tambm ao() usurio(a) do programa sobre a situao de impossibilidade de uso do espao solicitado de memria. O(a) programador(a) deve
solicitar ainda alguma deciso do(a) usurio(a) quando dessa circunstncia e, provavelmente,
encerrar a execuo do programa.
Vejamos um exemplo no programa 16.2 que faz alocao dinmica de memria para alocao de um vetor.
FACOM
UFMS
16.2 A LOCAO
DINMICA DE MEMRIA
129
Na segunda linha do programa 16.2, inclumos o arquivo-cabealho stdlib.h , que contm a declarao da funo malloc , usada nesse programa. A funo malloc tem sua interface apresentada a seguir:
A funo malloc reserva uma certa quantidade especfica de memria e devolve um ponteiro do tipo void . No programa acima, reservamos n compartimentos contnuos que podem
armazenar nmeros inteiros, o que se reflete na expresso n * sizeof(int) . Essa expresso,
na verdade, reflete o nmero de bytes que sero alocados continuamente na memria, que depende do sistema computacional onde o programa compilado. O operador unrio sizeof
devolve como resultado o nmero de bytes dados pelo seu operando, que pode ser um tipo de
dados ou uma expresso. Nesse caso, nas mquinas que usamos no laboratrio, a expresso
sizeof(int) devolve 4 bytes. Esse nmero multiplicado por n, o nmero de compartimentos que desejamos para armazenar nmeros inteiros. O endereo da primeira posio de
FACOM
UFMS
130
U SO
AVANADO DE PONTEIROS
memria onde encontram-se esses compartimentos devolvido pela funo malloc . Essa
funo devolve um ponteiro do tipo void . Por isso, usamos o modificador de tipo (int *)
para indicar que o endereo devolvido de fato um ponteiro para um nmero inteiro. Por fim,
esse endereo armazenado em vetor , que foi declarado como um ponteiro para nmeros
inteiros. A partir da, podemos usar vetor da forma como preferirmos, como um ponteiro ou
como um vetor.
O programa 16.3 um exemplo de alocao dinmica de memria de uma matriz de nmeros inteiros. Esse programa um pouco mais complicado que o programa 16.2, devido ao uso
distinto que faz da funo malloc .
Programa 16.3: Um exemplo de alocao dinmica de uma matriz.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i, j, m, n, **matriz, **pt;
printf("Informe a dimenso da matriz: ");
scanf("%d%d", &m, &n);
matriz = (int **) malloc(m * sizeof(int *));
if (matriz == NULL) {
printf("No h espao suficiente na memria\n");
return 0;
}
for (pt = matriz, i = 0; i < m; i++, pt++) {
*pt = (int *) malloc(n * sizeof(int));
if (*pt == NULL) {
printf("No h espao suficiente na memria\n");
return 0;
}
}
for (i = 0; i < m; i++)
for (j = 0; j < n; j++) {
printf("Informe o elemento (%d,%d): ", i, j);
scanf("%d", &matriz[i][j]);
}
printf("\nMatriz:\n");
pt = matriz;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++)
printf("%d ", *(*(pt+i)+j));
printf("\n");
} printf("\n");
for (i = 0; i < m; i++)
free(matriz[i]);
free(matriz);
return 0;
}
FACOM
UFMS
16.2 A LOCAO
DINMICA DE MEMRIA
131
Observe por fim que as linhas em que ocorrem a alocao dinmica de memria no programa 16.3 podem ser substitudas de forma equivalente pelas linhas a seguir:
Alm da funo malloc existem duas outras funes para alocao de memria na
stdlib.h : calloc e realloc , mas a primeira mais freqentemente usada que essas outras duas. Essas funes solicitam blocos de memria de um espao de armazenamento conhecido tambm como heap ou ainda lista de espaos disponveis. A chamada freqente dessas
funes pode exaurir o heap do sistema, fazendo com que essas funes devolvam um ponteiro
nulo. Pior ainda, um programa pode alocar blocos de memria e perd-los de algum modo,
gastando espao desnecessrio. Considere o exemplo a seguir:
p = malloc(...);
q = malloc(...);
p = q;
Aps as duas primeiras sentenas serem executadas, p aponta para um bloco de memria e q
aponta para um outro. No entanto, aps a atribuio de q para p na ltima sentena, as duas
variveis apontam para o mesmo bloco de memria, o segundo bloco, sem que nenhuma delas
aponte para o primeiro. Alm disso, no poderemos mais acessar o primeiro bloco, que ficar
perdido na memria, ocupando espao desnecessrio. Esse bloco chamado de lixo.
A funo free usada para ajudar os programadores da linguagem C a resolver o problema de gerao de lixo na memria durante a execuo de programas. Essa funo tem a
seguinte interface na stdlib.h :
Para usar a funo free corretamente, devemos lhe passar um ponteiro para um bloco de
memria que no mais necessitamos, como fazemos abaixo:
p = malloc(...);
q = malloc(...);
free(p);
p = q;
A chamada funo free devolve o bloco de memria apontado por p para o heap, que fica
disponvel para uso em chamadas subseqentes das funes de alocao de memria.
O argumento da funo free deve ser um ponteiro que foi previamente devolvido por
uma funo de alocao de memria.
FACOM
UFMS
132
U SO
AVANADO DE PONTEIROS
O argumento sin o nome/identificador da funo seno, que foi includa no programa atravs da biblioteca math.h .
Observe que no h parnteses aps o identificador da funo sin . Quando o nome de
uma funo no seguido por parnteses, o compilador produz um ponteiro para a funo em
vez de gerar cdigo para uma chamada da funo. No exemplo acima, no h uma chamada
funo sin . Ao invs disso, estamos passando para a funo integral um ponteiro para
FACOM
UFMS
16.3 P ONTEIROS
PARA FUNES
133
a funo sin . Podemos pensar em ponteiros para funes como pensamos com ponteiros
para vetores e matrizes. Relembrando, se, por exemplo, v o identificador de um vetor, ento
v[i] representa um elemento do vetor enquanto que v representa um ponteiro para o vetor,
ou melhor, para o primeiro elemento do vetor. Da forma similar, se f o identificador de
uma funo, a linguagem C trata f(x) como uma chamada da funo, mas trata f como um
ponteiro para a funo.
Dentro do corpo da funo integral podemos chamar a funo apontada por f da seguinte forma:
y = (*f)(x);
void (*ptf)(int);
O ponteiro ptf pode apontar para qualquer funo que tenha um nico parmetro do tipo
int e que devolva um valor do tipo void .
Se f uma funo com essas caractersticas, podemos fazer ptf apontar para f da seguinte forma:
ptf = f;
(*ptf)(i);
O programa 16.4 imprime uma tabela mostrando os valores das funes seno, cosseno e
tangente no intervalo e incremento escolhidos pelo(a) usurio(a). O programa usa as funes
sin , cos e tan de math.h .
FACOM
UFMS
134
U SO
AVANADO DE PONTEIROS
FACOM
UFMS
16.3 P ONTEIROS
PARA FUNES
135
Exerccios
16.1 Dados dois vetores x e y, ambos com n elementos, 1 6 n 6 100, determinar o produto
escalar desses vetores. Use alocao dinmica de memria.
16.2 Dizemos que uma seqncia de n elementos, com n par, balanceada se as seguintes
somas so todas iguais:
a soma do maior elemento com o menor elemento;
a soma do segundo maior elemento com o segundo menor elemento;
a soma do terceiro maior elemento com o terceiro menor elemento;
e assim por diante . . .
Exemplo:
2 12 3 6 16 15 uma seqncia balanceada, pois 16 + 2 = 15 + 3 = 12 + 6.
Dados n (n par e 0 6 n 6 100) e uma seqncia de n nmeros inteiros, verificar se essa
seqncia balanceada. Use alocao dinmica de memria.
16.3 Dada uma cadeia de caracteres com no mximo 100 caracteres, contar a quantidade de
letras minsculas, letras maisculas, dgitos, espaos e smbolos de pontuao que essa
cadeia possui. Use alocao dinmica de memria.
16.4 Dada uma matriz de nmeros reais A com m linhas e n colunas, 1 6 m, n 6 100, e um
vetor de nmeros reais v com n elementos, determinar o produto de A por v. Use alocao
dinmica de memria.
16.5 Dizemos que uma matriz quadrada de nmeros inteiros distintos um quadrado mgico
se a soma dos elementos de cada linha, a soma dos elementos de cada coluna e a soma
dos elementos da diagonal principal e secundria so todas iguais.
Exemplo:
A matriz
8 0 7
4 5 6
3 10 2
um quadrado mgico.
Dada uma matriz quadrada de nmeros inteiros Ann , com 1 6 n 6 100, verificar se A
um quadrado mgico. Use alocao dinmica de memria.
16.6 Simule a execuo do programa a seguir.
FACOM
UFMS
136
U SO
AVANADO DE PONTEIROS
#include <stdio.h>
int f1(int (*f)(int))
{
int n = 0;
while ((*f)(n))
n++;
return n;
}
int f2(int i)
{
return i * i + i - 12;
}
int main(void)
{
printf("Resposta: %d\n", f1(f2));
return 0;
}
FACOM
+ g(j) .
UFMS
AULA
17
A RQUIVOS
Nos programas que fizemos at aqui, a entrada e a sada de dados sempre ocorreram em
uma janela de terminal ou console do sistema operacional. Na linguagem C, no existem
palavras-chaves definidas para tratamento de operaes de entrada e sada. Essas tarefas so
realizadas atravs de funes. Pouco usamos outras funes de entrada e sada da linguagem
C alm das funes scanf e printf , localizadas na biblioteca padro de entrada e sada,
com arquivo-cabealho stdio.h . Isso significa que o fluxo de dados de entrada e sada dos
programas que fizemos sempre passa pela memria principal. Nesta aula, baseada na referncia [7], aprenderemos funes que realizam tarefas de entrada e sada armazenados em um
dispositivo de memria secundria em arquivos.
Veremos mais sobre arquivos na linguagem C e as funes de suporte associadas a eles a partir da seo 17.3. Em particular, veremos arquivos do sistema com nomes padronizados na
seo 17.3.5.
137
138
A RQUIVOS
UFMS
17.2 R EDIRECIONAMENTO
DE ENTRADA E SADA
139
Esse comando instrui o sistema operacional a executar o programa decbin redirecionando sua sada, que normalmente seria apresentada no terminal, para um arquivo com nome
resultado . Dessa forma, qualquer informao a ser apresentada por uma funo de sada,
como printf , no ser mostrada no terminal, mas ser escrita no arquivo resultado , conforme especificado na linha de comandos do terminal.
Por outro lado, podemos redirecionar a entrada de um programa executvel, de tal forma
que chamadas a funes que realizam entrada de dados, no mais solicitem essas informaes
ao usurio a partir do terminal, mas as obtenha a partir de um arquivo. Por exemplo, o programa 17.1 usa a funo scanf para ler um nmero inteiro a partir de um terminal. Podemos
redirecionar a entrada do programa decbin quando est sendo executado, fazendo que essa
entrada seja realizada a partir de um arquivo. Por exemplo, se temos um arquivo com nome
numero que contm um nmero inteiro, podemos digitar o seguinte em uma linha de terminal:
prompt$ ./decbin < numero
Com esse redirecionamento, o programa 17.1, que solicita um nmero a ser informado pelo
usurio, no espera at que um nmero seja digitado. Ao contrrio, pelo redirecionamento, a
entrada do programa tomada do arquivo numero . Ou seja, a chamada funo scanf tem
o efeito de ler um valor do arquivo numero e no do terminal, embora a funo scanf no
saiba disso.
Podemos redirecionar a entrada e a sada de um programa simultaneamente da seguinte
maneira:
prompt$ ./decbin < numero > resultado
Observe ento que esse comando faz com que o programa decbin seja executado tomando
a entrada de dados a partir do arquivo numero e escrevendo a sada de dados no arquivo
resultado .
O redirecionamento de entrada e sada uma ferramenta til, j que, podemos manter um
arquivo de entradas e realizar diversos testes sobre um programa executvel a partir desse
arquivo de entradas. Alm disso, se temos, por exemplo, um arquivo alvo que contm solues correspondentes s entradas, podemos comparar esse arquivo de solues com as sadas
de nosso programa, que tambm podem ser armazenadas em um arquivo diferente. Utilitrios para comparao de contedos de arquivos disponveis no sistema operacional Linux, tais
como diff e cmp , so usados para atingir esse objetivo. Por exemplo,
FACOM
UFMS
140
A RQUIVOS
UFMS
17.3 F UNES
modo
r
w
a
r+
w+
a+
rb
wb
ab
r+b ou rb+
w+b ou wb+
a+b ou ab+
141
Descrio
modo de leitura de texto
modo de escrita de texto
modo de adicionar texto
modo de leitura e escrita de texto
modo de leitura e escrita de texto
modo de leitura e escrita de texto
modo de leitura em binrio
modo de escrita em binrio
modo de adicionar em binrio
modo de leitura e escrita em binrio
modo de leitura e escrita em binrio
modo de leitura e escrita em binrio
onde:
Algumas observaes sobre os modos de abertura de arquivos se fazem necessrias. Primeiro, observe que se o arquivo no existe e aberto com o modo de leitura ( r ) ento a
abertura falha. Tambm, se o arquivo aberto com o modo de adicionar ( a ), ento todas as
operaes de escrita ocorrem o final do arquivo, desconsiderando a posio atual do ponteiro
do arquivo. Por fim, se o arquivo aberto no modo de atualizao ( + ) ento a operao de
escrita no pode ser imediatamente seguida pela operao de leitura, e vice-versa, a menos
que uma operao de reposicionamento do ponteiro do arquivo seja executada, tal como uma
chamada a qualquer uma das funes fseek , fsetpos , rewind ou fflush .
Por exemplo, o comando de atribuio a seguir
tem o efeito de abrir um arquivo com nome entrada no modo de leitura. A chamada funo
fopen devolve um identificador para o arquivo aberto que atribudo ao ponteiro ptarq do
tipo FILE . Ento, esse ponteiro posicionado no primeiro caracter do arquivo. A declarao
prvia do ponteiro ptarq deve ser feita da seguinte forma:
FILE *ptarq;
A funo fclose faz o oposto que a funo fopen faz, ou seja, informa o sistema que
o programador no necessita mais usar o arquivo. Quando um arquivo fechado, o sistema
realiza algumas tarefas importantes, especialmente a escrita de quaisquer dados que o sistema
possa ter mantido na memria principal para o arquivo na memria secundria, e ento dissocia o identificador do arquivo. Depois de fechado, no podemos realizar tarefas de leitura ou
escrita no arquivo, a menos que seja reaberto. A funo fclose tem a seguinte interface:
FACOM
UFMS
142
A RQUIVOS
A funo fgetc l o prximo caracter do arquivo apontado por ptarq , avanando esse
ponteiro em uma posio. Se a leitura realizada com sucesso, o caracter lido devolvido pela
funo. Note, no entanto, que a funo, ao invs de especificar o valor de devoluo como
sendo do tipo unsigned char , especifica-o como sendo do tipo int . Isso se deve ao fato de
que a leitura pode falhar e, nesse caso, o valor devolvido o valor armazenado na constante
simblica EOF , definida no arquivo-cabealho stdio.h . O valor correspondente constante
simblica EOF obviamente um valor diferente do valor de qualquer caracter e, portanto, um
valor negativo. Do mesmo modo, se o fim do arquivo encontrado, a funo fgetc tambm
devolve EOF .
A funo fputc da biblioteca padro de entrada e sada da linguagem C permite que um
nico caracter seja escrito em um arquivo. A interface dessa funo apresentada a seguir:
e
int fprintf(FILE *ptarq, char *formato, ...)
Essas duas funes so semelhantes s respectivas funes scanf e printf que conhecemos bem, a menos de um parmetro a mais que informado, justamente o primeiro, que
FACOM
UFMS
17.3 F UNES
143
o ponteiro para o arquivo que ser quer realizar as operaes de entrada e sada formatadas.
Dessa forma, o exemplo de chamada a seguir
realiza a escrita no arquivo apontado por ptarq da mensagem entre aspas duplas, substituindo o valor numrico correspondente armazenado na varivel numero . O nmero de caracteres escritos no arquivo devolvido pela funo fprintf . Se um erro ocorrer, ento o
valor -1 devolvido.
Do mesmo modo,
realiza a leitura de um valor que ser armazenado na varivel numero a partir de um arquivo
identificado pelo ponteiro ptarq . Se a leitura for realizada com sucesso, o nmero de valores
lidos pela funo fscanf devolvido. Caso contrrio, isto , se houver falha na leitura, o
valor EOF devolvido.
H outras funes para entrada e sada de dados a partir de arquivos, como as funes
fread e fwrite , que no sero cobertas nesta aula. O leitor interessado deve procurar as
referncias bibliogrficas do curso.
onde ptarq o ponteiro para um arquivo. Se o ponteiro ptarq contm um valor nulo, ento todos espaos de armazenamento temporrios na memria principal de todos os arquivos
abertos so descarregados nos dispositivos de memria secundria. Se a descarga realizada
com sucesso, a funo devolve o valor 0 (zero). Caso contrrio, a funo devolve o valor EOF .
Sobre as funes que tratam do posicionamento do ponteiro de um arquivo, existe uma
funo especfica da biblioteca padro da linguagem C que realiza um teste de final de arquivo.
Essa funo tem identificador feof e a seguinte interface:
FACOM
UFMS
144
A RQUIVOS
onde ptarq o ponteiro associado a um arquivo e pos uma varivel que, aps a execuo
dessa funo, conter o valor da posio atual do ponteiro do arquivo. Observe que fpos_t
um novo tipo de dado, definido no arquivo-cabealho stdio.h , adequado para armazenamento de uma posio qualquer de um arquivo. Se a obteno dessa posio for realizada com
sucesso, a funo devolve 0 (zero). Caso contrrio, a funo devolve um valor diferente de 0
(zero).
A funo fsetpos posiciona o ponteiro de um arquivo em alguma posio escolhida e tem
a seguinte interface:
onde ptarq o ponteiro para um arquivo e pos uma varivel que contm a posio para
onde o ponteiro do arquivo ser deslocada. Se a determinao dessa posio for realizada com
sucesso, a funo devolve 0 (zero). Caso contrrio, a funo devolve um valor diferente de 0
(zero).
A funo ftell determina a posio atual de um ponteiro em um dado arquivo. Sua
interface apresentada a seguir:
A funo ftell devolve a posio atual no arquivo apontado por ptarq . Se o arquivo
binrio, ento o valor o nmero de bytes a partir do incio do arquivo. Se o arquivo de texto,
ento esse valor pode ser usado pela funo fseek , como veremos a seguir. Se h sucesso na
sua execuo, a funo devolve a posio atual no arquivo. Caso contrrio, a funo devolve o
valor -1L .
A funo fseek posiciona o ponteiro de um arquivo para uma posio determinada por
um deslocamento. A interface da funo dada a seguir:
FACOM
UFMS
17.3 F UNES
145
A funo rename faz com que o arquivo com nome armazenado na cadeia de caracteres
antigo tenha seu nome trocado pelo nome armazenado na cadeia de caracteres novo . Se a
funo realiza a tarefa de troca de nome, ento rename devolve o valor 0 (zero). Caso contrrio, um valor diferente de 0 (zero) devolvido e o arquivo ainda pode ser identificado por seu
nome antigo.
UFMS
146
A RQUIVOS
scanf("%d", &numero);
e
fscanf(stdin, "%d", &numero);
printf("Programar bacana!\n");
e
fprintf(stdout, "Programa bacana!\n");
so equivalentes e imprimem a mensagem acima entre as aspas duplas na sada padro, que
normalmente o terminal.
O ponteiro stderr se refere ao arquivo padro de erro, onde muitas das mensagens de erro
produzidas pelo sistema so armazenadas e tambm normalmente associado ao terminal.
Uma justificativa para existncia de tal arquivo , por exemplo, quando as sadas todas do
programa so direcionadas para um arquivo. Assim, as sadas do programa so escritas em
um arquivo e as mensagens de erro so escritas na sada padro, isto , no terminal. Ainda
h a possibilidade de escrever nossas prprias mensagens de erro no arquivo apontado por
stderr .
17.4 Exemplos
Nessa seo apresentaremos dois exemplos que usam algumas das funes de entrada e
sada em arquivos que aprendemos nesta aula.
FACOM
UFMS
17.4 E XEMPLOS
147
FACOM
UFMS
148
A RQUIVOS
FACOM
UFMS
17.4 E XEMPLOS
149
Exerccios
17.1 Escreva um programa que leia o contedo de um arquivo cujo nome fornecido pelo
usurio e copie seu contedo em um outro arquivo, trocando todas as letras minsculas
por letras maisculas.
17.2 Suponha que temos dois arquivos cujas linhas so ordenadas lexicograficamente. Por
exemplo, esses arquivos podem conter nomes de pessoas, linha a linha, em ordem alfabtica. Escreva um programa que leia o contedo desses dois arquivos, cujos nomes
so fornecidos pelo usurio, e crie um novo arquivo resultante contendo todas as linhas
dos dois arquivos ordenadas lexicograficamente. Por exemplo, se os arquivos contm as
linhas
Arquivo 1
Arquivo 2
Antnio
Berenice
Diana
Solange
Snia
Zuleica
Carlos
Clia
Fbio
Henrique
FACOM
UFMS
150
FACOM
A RQUIVOS
UFMS
AULA
L ISTAS
18
LINEARES
18.1 Definio
Informalmente, uma lista linear uma estrutura de dados que armazena um conjunto de
informaes que so relacionadas entre si. Essa relao se expressa apenas pela ordem relativa
entre os elementos. Por exemplo, nomes e telefones de uma agenda telefnica, as informaes
bancrias dos funcionrios de uma empresa, as informaes sobre processos em execuo pelo
sistema operacional, etc, so informaes que podem ser armazenadas em uma lista linear.
Cada informao contida na lista na verdade um registro contendo os dados relacionados, que
chamaremos daqui por diante de clula. Em geral, usamos um desses dados como uma chave
para realizar diversas operaes sobre essa lista, tais como busca de um elemento, insero,
remoo, etc. J que os dados que acompanham a chave so irrelevantes e participam apenas
das movimentaes das clulas, podemos imaginar que uma lista linear composta apenas
pelas chaves dessas clulas e que essas chaves so representadas por nmeros inteiros.
Agora formalmente, uma lista linear um conjunto de n > 0 clulas c1 , c2 , . . . , cn determinada pela ordem relativa desses elementos:
(i) se n > 0 ento c1 a primeira clula;
(ii) a clula ci precedida pela clula ci1 , para todo i, 1 < i 6 n.
As operaes bsicas sobre uma lista linear so as seguintes:
busca;
incluso; e
remoo.
151
152
L ISTAS
LINEARES
Dependendo da aplicao, muitas outras operaes tambm podem ser realizadas sobre
essa estrutura como, por exemplo, alterao de um elemento, combinao de duas listas, ordenao da lista de acordo com as chaves, determinao do elemento de menor ou maior chave,
determinao do tamanho ou nmero de elementos da lista, etc.
As listas lineares podem ser armazenadas na memria de duas maneiras distintas:
Alocao esttica ou seqencial: os elementos so armazenados em posies consecutivas de
memria, com uso da vetores;
Alocao dinmica ou encadeada: os elementos podem ser armazenados em posies no
consecutivas de memria, com uso de ponteiros.
A aplicao, ou problema que queremos resolver, que define o tipo de armazenamento a
ser usado, dependendo das operaes sobre a lista, do nmero de listas envolvidas e das caractersticas particulares das listas. Na aula 4 vimos especialmente as operaes bsicas sobre uma
lista linear em alocao seqencial, especialmente a operao de busca, sendo que as restantes
foram vistas nos exerccios.
As clulas de uma lista linear em alocao encadeada encontram-se dispostas em posies
aleatrias da memria e so ligadas por ponteiros que indicam a posio da prxima clula da
lista. Assim, um campo acrescentado a cada clula da lista indicando o endereo do prximo
elemento da lista. Veja a figura 18.1 para um exemplo de uma clula de uma lista.
chave prox
Figura 18.1: Representao de uma clula de uma lista linear em alocao encadeada.
A definio de uma clula de uma lista linear encadeada ento descrita como a seguir:
struct cel {
int chave;
struct cel *prox;
};
boa prtica de programao definir um novo tipo de dados para as clulas de uma lista
linear em alocao encadeada:
Uma clula c e um ponteiro p para uma clula podem ser declarados da seguinte forma:
celula c;
celula *p;
FACOM
UFMS
18.1 D EFINIO
153
celula c, *lista;
c.prox = NULL;
lista = &c;
ou ainda
celula *lista;
lista = (celula *) malloc(sizeof (celula));
lista->prox = NULL;
A figura 18.3 mostra a representao na memria de uma lista linear encadeada com cabea
e vazia.
lista
Figura 18.3: Uma lista linear encadeada com cabea e vazia lista.
FACOM
UFMS
154
L ISTAS
LINEARES
Para criar uma lista vazia lista sem cabea, basta escrever as seguintes sentenas:
celula *lista;
lista = NULL;
A figura 18.4 mostra a representao na memria de uma lista linear encadeada sem cabea
vazia.
lista
Figura 18.4: Uma lista linear encadeada sem cabea e vazia lista.
Listas lineares com cabea so mais fceis de manipular do que aquelas sem cabea. No
entanto, as listas com cabea tm sempre a desvantagem de manter uma clula a mais na memria.
Para imprimir o contedo de todas as clulas de uma lista linear podemos usar a seguinte
funo:
Se lista uma lista linear com cabea, a chamada da funo deve ser:
imprime_lista(lista->prox);
Se lista uma lista linear sem cabea, a chamada da funo deve ser:
imprime_lista(lista);
UFMS
18.2 L ISTAS
155
18.2.1 Busca
O processo de busca de um elemento em uma lista linear em alocao encadeada com cabea muito simples. Basta verificar se o elemento igual ao contedo de alguma clula da
lista, como pode ser visto na descrio da funo abaixo. A funo busca_C recebe uma lista
linear encadeada com cabea apontada por lst e um nmero inteiro x e devolve o endereo de uma clula contendo x , caso x ocorra em lst . Caso contrrio, a funo devolve o
ponteiro nulo NULL .
/* Recebe um nmero inteiro x e uma lista encadeada com cabea lst e devolve o endereo da clula que contm x ou NULL se tal clula no existe */
celula *busca_C(int x, celula *lst)
{
celula *p;
p = lst->prox;
while (p != NULL && p->chave != x)
p = p->prox;
return p;
}
fcil notar que este cdigo muito simples e sempre devolve o resultado correto, mesmo
quando a lista est vazia. Um exemplo de execuo da funo busca_C mostrado na figura 18.5.
x
lst
2
Figura 18.5: Resultado de uma chamada da funo busca_C para uma dada lista linear e a
chave 1.
Uma verso recursiva da funo acima apresentada a seguir.
FACOM
UFMS
156
L ISTAS
LINEARES
/* Recebe um nmero inteiro x e uma lista encadeada com cabea lst e devolve o endereo da clula que contm x ou NULL se tal clula no existe */
celula *buscaR_C(int x, celula *lst)
{
if (lst->prox == NULL)
return NULL;
if (lst->prox->chave == x)
return lst->prox;
return buscaR_C(x, lst->prox);
}
18.2.2 Remoo
O problema nesta seo a remoo de uma clula de uma lista linear encadeada. Parece
simples remover tal clula apontada pelo ponteiro, mas esta idia tem um defeito fundamental: a clula anterior a ela deve apontar para a clula seguinte, mas neste caso no temos o
endereo da clula anterior. Dessa forma, no processo de remoo, necessrio apontar para a
clula anterior quela que queremos remover. Como uma lista linear encadeada com n cabea
sempre tem uma clula anterior, a implementao da remoo muito simples neste caso. A
funo remove_C recebe um ponteiro p para uma clula de uma lista linear encadeada e remove a clula seguinte, supondo que o ponteiro p aponta para uma clula e existe uma clula
seguinte quela apontada por p .
/* Recebe o endereo p de uma clula em uma lista encadeada e remove a clula p->prox, supondo que p != NULL e p->prox != NULL */
void remove_C(celula *p)
{
celula *lixo;
lixo = p->prox;
p->prox = lixo->prox;
free(lixo);
}
18.2.3 Insero
Nesta seo, queremos inserir uma nova clula em uma lista linear encadaeda com cabea.
Suponha que queremos inserir uma nova clula com chave y entre a clula apontada por p
e a clula seguinte. A funo insere_C abaixo recebe um nmero inteiro y e um ponteiro
p para uma clula da lista e realiza a insero de uma nova clula com chave y entre a clula
apontada por p e a clula seguinte da lista.
FACOM
UFMS
18.2 L ISTAS
157
(i)
p
lixo
(ii)
p
lixo
(iii)
p
lixo
(iv)
Figura 18.6: Resultado de uma chamada da funo remove_C para uma clula apontada por
p de uma dada lista linear. As figuras de (i) a (iv) representam a execuo linha a linha da
funo.
FACOM
UFMS
158
L ISTAS
LINEARES
(i)
y
nova
(ii)
y
nova
(iii)
Figura 18.7: Resultado de uma chamada da funo insere_C para uma nova chave 1 e a
clula apontada por p de uma dada lista linear.
UFMS
18.2 L ISTAS
159
/* Recebe um nmero inteiro x e uma lista encadeada com cabea lst e remove da lista a primeira clula que contiver x, se tal clula existir */
void busca_remove_C(int x, celula *lst)
{
celula *p, *q;
p = lst;
q = lst->prox;
while (q != NULL && q->chave != x) {
p = q;
q = q->prox;
}
if (q != NULL) {
p->prox = q->prox;
free(q);
}
}
Agora, nosso problema o de inserir uma nova clula com uma nova chave em uma lista
linear encadaeda com cabea, antes da primeira clula que contenha como chave um nmero
inteiro fornecido. Se tal clula no existe, inserimos a nova chave no final da lista. A funo
busca_remove_C apresentada a seguir implementa esse processo. Um exemplo de execuo
dessa funo ilustrado na figura 18.8.
FACOM
UFMS
160
L ISTAS
LINEARES
lst
2
(i)
x
lst
2
(ii)
x
lst
2
(iii)
x
lst
2
(iv)
Figura 18.8: Resultado de uma chamada da funo busca_remove_C para a chave 1 e uma
lista lst . As figuras de (i) a (iv) representam a execuo linha a linha da funo.
FACOM
UFMS
18.2 L ISTAS
161
lst
2
(i)
y
nova
q
1
lst
2
(ii)
y
nova
q
1
lst
2
(iii)
y
nova
lst
2
(iv)
Figura 18.9: Resultado de uma chamada da funo busca_insere_C para as chaves 1 e 3 e
uma lista lst . As figuras de (i) a (iv) representam a execuo linha a linha da funo.
FACOM
UFMS
162
L ISTAS
LINEARES
18.3.1 Busca
A busca de um elemento em uma lista linear em alocao encadeada sem cabea muito
simples e semelhante ao mesmo processo realizado sobre uma lista linear encadeada com cabea. Seguem os cdigos da funo no-recursiva busca_S e da funo recursiva buscaR_S .
/* Recebe um nmero inteiro x e uma lista encadeada sem cabea lst e devolve o endereo da clula que contm x ou NULL se tal clula no existe */
celula *busca_S(int x, celula *lst)
{
celula *p;
p = lst;
while (p != NULL && p->chave != x)
p = p->prox;
return p;
}
/* Recebe um nmero inteiro x e uma lista encadeada sem cabea lst e devolve o endereo da clula que contm x ou NULL se tal clula no existe */
celula *buscaR_S(int x, celula *lst)
{
if (lst == NULL)
return NULL;
if (lst->chave == x)
return lst;
return buscaR_S(x, lst->prox);
}
UFMS
18.3 L ISTAS
163
/* Recebe um nmero inteiro x e uma lista encadeada com cabea lst e remove da lista a primeira clula que contiver x, se tal clula existir */
void busca_remove_S(int x, celula **lst)
{
celula *p, *q;
p = NULL;
q = *lst;
while (q != NULL && q->chave != x) {
p = q;
q = q->prox;
}
if (q != NULL)
if (p != NULL) {
p->prox = q->prox;
free(q);
}
else {
*lst = q->prox;
free(q);
}
}
(i)
*lst
(ii)
*lst
(iii)
Figura 18.10: Remoo de uma chave em uma lista linear encadeada sem cabea. (i) Aps a
busca do valor. (ii) Modificao do ponteiro p->prox . (iii) Liberao da memria.
FACOM
UFMS
164
L ISTAS
LINEARES
busca_remove_S(x, &lista);
Observe que os ponteiros p e q so variveis locais funo busca_remove_S , que auxiliam tanto na busca como na remoo propriamente. Alm disso, se p == NULL depois da
execuo da estrutura de repetio da funo, ento a clula que contm x que queremos
remover, encontra-se na primeira posio da lista. Dessa forma, h necessidade de alterar o
contedo do ponteiro *lst para o registro seguinte desta lista.
Agora, nosso problema realizar uma bsuca seguida de uma insero de uma nova clula em uma lista linear ecncadeada sem cabea. Do mesmo modo, h muitas semelhanas
entre a funo de busca seguida de insero em uma lista linear encadeada com cabea e sem
cabea. De novo, devemos reparar na diferena fundamental entre as duas implementaes:
uma insero em uma lista linear encadeada sem cabea pode modificar o ponteiro que identifica a lista quando inserimos uma nova clula em uma lista vazia. Dessa forma, a funo
busca_insere_S descrita abaixo tem como parmetro que identifica a lista linear um ponteiro para um ponteiro.
Uma chamada funo busca_insere_S pode ser feita como abaixo, para nmeros inteiros y e x e uma lista :
busca_insere_S(y, x, &lista);
FACOM
UFMS
18.3 L ISTAS
165
Exerccios
18.1 Se conhecemos apenas o ponteiro p para uma clula de uma lista linear em alocao
encadeada, como na figura 18.11, e nada mais conhecido, como podemos modificar
a lista linear de modo que passe a conter apenas os valores 20, 4, 19, 47, isto , sem o
contedo da clula apontada por p ?
p
20
19
47
dir
(i)
esq
dir
(ii)
Figura 18.12: A figura (ii) foi obtida de (i) atravs da execuo da funo dada no exerccio
18.2(a) por trs vezes.
FACOM
UFMS
166
L ISTAS
LINEARES
acontece se trocarmos
while (p != NULL && p->chave != x)
while (p->chave != x && p != NULL na funo busca_C ?
por
18.4 Escreva uma funo que encontre uma clula cuja chave tem valor mnimo em uma lista
linear encadeada. Considere listas com e sem cabea e escreva verses no-recursivas e
recursivas para a funo.
18.5 Escreva uma funo busca_insere_fim que receba um nmero inteiro y , uma lista
linear encadeada lst e um ponteiro f para o fim da lista e realize a insero desse
valor no final da lista. Faa uma verses para listas lineares com cabea e sem cabea.
18.6
(a) Escreva duas funes: uma que copie um vetor para uma lista linear encadeada com
cabea; outra, que gfaa o mesmo para uma lista linear sem cabea.
(b) Escreva duas funes: uma que copie uma lista linear encadeada com cabea em um
vetor; outra que copie uma lista linear encadeada sem cabea em um vetor.
18.7 Escreva uma funo que decida se duas listas dadas tm o mesmo contedo. Escreva
duas verses: uma para listas lineares com cabea e outra para listas lineares sem cabea.
18.8 Escreva uma funo que conte o nmero de clulas de uma lista linear encadeada.
18.9 Seja lista uma lista linear com seus contedos dispostos em ordem crescente. Escreva
funes para realizao das operaes bsicas de busca, insero e remoo, respectivamente, em uma lista linear com essa caracterstica. Escreva conjuntos de funes distintas
para listas lineares com cabea e sem cabea. As operaes de insero e remoo devem
manter a lista em ordem crescente.
18.10 Sejam duas listas lineares lst1 e lst2 , com seus contedos dispostos em ordem crescente. Escreva uma funo concatena que receba lst1 e lst2 e construa uma lista
R resultante da intercalao dessas duas listas, de tal forma que a lista construda tambm esteja ordenada. A funo concatena deve destruir as listas lst1 e lst2 e deve
devolver R . Escreva duas funes para os casos em que as listas lineares encadeadas so
com cabea e sem cabea.
18.11 Seja lst uma lista linear encadeada composta por clulas contendo os valores c1 , c2 , . . . ,
cn , nessa ordem. Para cada item abaixo, escreva duas funes considerando que lst
com cabea e sem cabea.
(a) Escreva uma funo roda1 que receba uma lista e modifique e devolva essa lista
de tal forma que a lista resultante contenha as chaves c2 , c3 , . . . , cn , c1 , nessa ordem.
(b) Escreva uma funo inverte que receba uma lista e modifique e devolva essa lista
de tal forma que a lista resultante contenha as chaves cn , cn1 , . . . , c2 , c1 , nessa ordem.
(c) Escreva uma funo soma que receba uma lista e modifique e devolva essa lista de
tal forma que a lista resultante contenha as chaves c1 +cn , c2 +cn1 , . . . , cn/2 +cn/2+1 ,
nessa ordem. Considere n par.
FACOM
UFMS
18.3 L ISTAS
167
18.12 Sejam S1 e S2 dois conjuntos disjuntos de nmeros inteiros. Suponha que S1 e S2 esto implementados em duas listas lineares em alocao encadeada. Escreva uma funo uniao
que receba as listas representando os conjuntos S1 e S2 e devolva uma lista resultante que
representa a unio dos conjuntos, isto , uma lista linear encadeada que representa o conjunto S = S1 S2 . Considere os casos em que as listas lineares encadeadas so com cabea
e sem cabea.
18.13 Seja um polinmio p(x) = a0 xn + a1 xn1 + . . . + an , com coeficientes de ponto flutuante.
Represente p(x) adequadamente por uma lista linear encadeada e escreva as seguintes
funes. Para cada item a seguir, considere o caso em que a lista linear encadeada com
cabea e sem cabea.
(a) Escreva uma funo pponto que receba uma lista p e um nmero de ponto flutuante x0 e calcule e devolva p(x0 ).
(b) Escreva uma funo psoma que receba as listas lineares p e q , que representam
dois polinmios, e calcule e devolva o polinmio resultante da operao p(x) + q(x).
(c) Escreva uma funo pprod que receba as listas lineares p e q , que representam
dois polinmios, e calcule e devolva o polinmio resultante da operao p(x) q(x).
18.14 Escreva uma funo que aplique a funo free a todas as clulas de uma lista linear
encadeada, supondo que todas as suas clulas foram alocadas com a funao malloc .
Faa verses considerando listas lineares encadeadas com cabea e sem cabea.
FACOM
UFMS
168
FACOM
L ISTAS
LINEARES
UFMS
AULA
19
P ILHAS
19.1 Definio
Uma pilha uma lista linear tal que as operaes de insero e remoo so realizadas em
um nico extremo dessa estrutura de dados.
O funcionamento dessa lista linear pode ser comparado a qualquer pilha de objetos que
usamos com freqncia como, por exemplo, uma pilha de pratos de um restaurante. Em geral,
os clientes do restaurante retiram pratos do incio da pilha, isto , do primeiro prato mais alto
na pilha. Os funcionrios colocam pratos limpos tambm nesse mesmo ponto da pilha. Seria
estranho ter de movimentar pratos, por exemplo no meio ou no final da pilha, sempre que uma
dessas dessas operaes fosse realizada.
O indicador do extremo onde ocorrem as operaes de insero e remoo chamado de
topo da pilha. Essas duas operaes so tambm chamadas de empilhamento e desempilhamento de elementos. Nenhuma outra operao realizada sobre uma pilha, a no ser em casos
especficos. Observe, por exemplo, que a operao de busca no foi mencionada e no faz parte
do conjunto de operaes bsicas de uma pilha.
169
170
P ILHAS
MAX-1
t
2
MAX-1
-1
MAX-1
MAX-1
(a)
(b)
int t, P[MAX];
t = -1;
Suponha agora que queremos inserir um elemento de valor 7 na pilha P da figura 19.1.
Como resultado desta operao, esperamos que a pilha tenha a configurao mostrada na figura 19.3 aps sua execuo.
MAX-1
t
3
UFMS
19.2 O PERAES
171
Suponha agora que queremos remover um elemento de uma pilha. Observe que, assim
como na insero, a remoo tambm deve ser feita em um dos extremos da pilha, isto , no
topo da pilha. Assim, a remoo de um elemento da pilha P que tem sua configurao ilustrada na figura 19.1 tem como resultado a pilha com a configurao mostrada na figura 19.4
aps a sua execuo.
MAX-1
t
2
Diversas so as aplicaes que necessitam de uma ou mais pilhas nas suas solues. Dentre
elas, podemos citar a converso de notao de expresses aritmticas (notao prefixa, infixa
e posfixa), a implementao da pilha de execuo de um programa no sistema operacional,
a anlise lxica de um programa realizada pelo compilador, etc. Algumas dessas aplicaes
sero vistas nos exerccios desta aula.
FACOM
UFMS
172
P ILHAS
Como exemplo, suponha que queremos saber se uma seqncia de caracteres da forma
aZb, onde a uma seqncia de caracteres e b o reverso da a. Observe que se a = ABCDEFG
e b = GFEDCBA ento a cadeia aZb satisfaz a propriedade. Suponh ainda que as seqncias
a e b possuem a mesma quantidade de caracteres. A funo aZb_seq abaixo verifica essa
propriedade para uma seqncia de caracteres usando uma pilha em alocao seqencial.
/* Recebe, via entrada padro, uma seqncia de caracteres e verifica se a mesma da forma aZb, onde b o reverso de a */
int aZb_seq(void)
{
char c, P[MAX];
int t;
t = -1;
c = getchar();
while (c != Z) {
t++;
P[t] = c;
c = getchar();
}
c = getchar();
while (t != -1 && c == P[t]) {
t--;
c = getchar();
}
if (t == -1)
return 1;
while (t > -1) {
c = getchar();
t--;
}
return 0;
}
Uma pilha vazia com cabea pode ser criada da seguinte forma:
celula *t;
t = (celula *) malloc(sizeof (celula));
t->prox = NULL;
FACOM
UFMS
19.3 O PERAES
173
Uma pilha vazia sem cabea pode ser criada da seguinte forma:
celula *t;
t = NULL;
Uma ilustrao de uma pilha em alocao encadeada vazia mostrada na figura 19.5.
t
(a)
(b)
Figura 19.5: Pilha vazia em alocao encadeada (a) com cabea e (b) sem cabea.
Considere uma pilha em alocao encadeada com cabea. A operao de empilhar uma
nova chave y nessa pilha descrita na funo empilha_enc_C a seguir.
UFMS
174
P ILHAS
A funo aZb_enc a verso que usa uma pilha em alocao encadeada para solucionar o
mesmo problema que a funo aZb_seq soluciona usando uma pilha em alocao seqencial.
/* Recebe, via entrada padro, uma seqncia de caracteres e verifica se a mesma da forma aZb, onde b o reverso de a */
int aZb_enc(void)
{
char c;
celula *t, *p;
t = (celula *) malloc(sizeof (celula));
t->prox = NULL;
c = getchar();
while (c != Z) {
p = (celula *) malloc(sizeof (celula));
p->chave = c;
p->prox = t->prox;
t->prox = p;
c = getchar();
}
c = getchar();
while (t->prox != NULL && c == t->chave) {
p = t->prox;
t->prox = p->prox;
free(p);
c = getchar();
}
if (t->prox == NULL)
return 1;
while (t->prox != NULL) {
p = t->prox;
t->prox = p->prox;
free(p);
c = getchar();
}
return 0;
}
Exerccios
19.1 Considere uma pilha em alocao encadeada sem cabea. Escreva as funes para empilhar um elemento na pilha e desempilhar um elemento da pilha.
19.2 Uma palavra um palndromo se a seqncia de caracteres que a constitui a mesma
quer seja lida da esquerda para a direita ou da direita para a esquerda. Por exemplo, as
palavras RADAR e MIRIM so palndromos. Escreva um programa eficiente para reconhecer se uma dada palavra palndromo.
19.3 Um estacionamento possui um nico corredor que permite dispor 10 carros. Existe somente uma nica entrada/sada do estacionamento em um dos extremos do corredor. Se
um cliente quer retirar um carro que no est prximo sada, todos os carros impedindo
sua passagem so retirados, o cliente retira seu carro e os outros carros so recolocados
na mesma ordem que estavam originalmente. Escreva um algoritmo que processa o fluxo
FACOM
UFMS
19.3 O PERAES
175
de chegada/sada deste estacionamento. Cada entrada para o algoritmo contm uma letra E para entrada ou S para sada, e o nmero da placa do carro. Considere que os
carros chegam e saem pela ordem especificada na entrada. O algoritmo deve imprimir
uma mensagem sempre que um carro chega ou sai. Quando um carro chega, a mensagem
deve especificar se existe ou no vaga para o carro no estacionamento. Se no existe vaga,
o carro no entra no estacionamento e vai embora. Quando um carro sai do estacionamento, a mensagem deve incluir o nmero de vezes que o carro foi movimentado para
fora da garagem, para permitir que outros carros pudessem sair.
19.4 Considere o problema de decidir se uma dada seqncia de parnteses e chaves bemformada. Por exemplo, a seqncia abaixo:
(
malformada.
Suponha que a seqncia de parnteses e chaves est armazenada em uma cadeia de
caracteres s . Escreva uma funo bem_formada que receba a cadeia de caracteres s e
devolva 1 se s contm uma seqncia bem-formada de parnteses e chaves e devolva
0 se a seqncia est malformada.
19.5 Como mencionado na seo 19.2, as expresses aritmticas podem ser representadas
usando notaes diferentes. Costumeiramente, trabalhamos com expresses aritmticas
em notao infixa, isto , na notao em que os operadores binrios aparecem entre os operandos. Outra notao freqentemente usada em compiladores a notao posfixa, aquela
em que os operadores aparecem depois dos operandos. Essa notao mais econmica
por dispensar o uso de parnteses para alterao da prioridade das operaes. Exemplos
de expresses nas duas notaes so apresentados abaixo.
notao infixa
(A + B * C)
(A * (B + C) / D + E)
(A + B * C / D * E - F)
notao posfixa
A B C * +
A B C + * D / E A B C * D / E * + F -
Escreva uma funo que receba um cadeia de caracteres contendo uma expresso aritmtica em notao infixa e devolva uma cadeia de caracteres contendo a mesma expresso
aritmtica em notao posfixa. Considere que a cadeia de caracteres da expresso infixa
contm apenas letras, parnteses e os smbolos +, -, * e /. Considere tambm que cada
varivel tem apenas uma letra e que a cadeia de caracteres infixa sempre est envolvida
por um par de parnteses.
19.6 Suponha que exista um nico vetor M de cluas de um tipo pilha pr-definido, com um
total de MAX posies. Este vetor far o papel da memria do computador. Este vetor M
ser compartilhado por duas pilhas em alocao seqencial. Implemente eficientemente
as operaes de empilhamento e desempilhamento para as duas pilhas de modo que
nenhuma das pilhas estoure sua capacidade de armazenamento, a menos que o total de
elementos em ambas as pilhas seja MAX.
FACOM
UFMS
176
FACOM
P ILHAS
UFMS
AULA
20
F ILAS
Uma fila , assim como uma pilha, uma lista linear especial, onde h uma poltica de inseres e remoes bem definida. As nicas operaes realizadas sobre as filas so exatamente
insero e remoo. H dois extremos em uma fila e a operao de insero sempre realizada
em um dos extremos e de remoo sempre no outro extremo. Em uma pilha, como vimos na
aula 19, a insero e a remoo so realizadas em um mesmo extremo. Nesta aula, baseada
nas referncias [2, 13], veremos uma definio formal para essas estruturas de dados e como
implementar as operaes bsicas sobre elas, considerando sua implementao em alocao
seqencial e encadeada.
20.1 Definio
Uma fila uma lista linear com dois extremos destacados e tal que as operaes de insero
so realizadas em um dos extremos da lista e a remoo realizada no outro extremo. O funcionamento dessa estrutura pode ser comparado a qualquer fila que usamos com freqncia
como, por exemplo, uma fila de um banco. Em geral, as pessoas entram, ou so inseridas, no
final da fila e as pessoas saem, ou so removidas, do incio da fila.
Note que uma fila tem sempre um indicador de onde comea e de onde termina. Quando
queremos inserir um elemento na fila, esta operao realizada no final da estrutura. Quando
queremos remover um elemento da fila, essa operao realizada no incio da estrutura. Se a
fila implementada em alocao seqencial ento esses extremos so ndices de um vetor. Caso
contrrio, se for implementada em alocao encadeada, ento esses extremos so ponteiros
para a primeira e a ltima clulas da fila.
Assim como nas pilhas, duas operaes bsicas so realizadas sobre uma fila: insero e
remoo. Essas duas operaes so tambm chamadas de enfileiramento e desenfileiramento
de elementos. Observe que a operao de busca no foi mencionada e no faz parte do conjunto
de operaes bsicas de uma fila.
178
F ILAS
MAX-1
f
0
i
MAX-1
(a)
MAX-1
f MAX
(b)
Figura 20.2: Representao de uma fila em alocao seqencial (a) vazia e (b) cheia.
A declarao de uma fila e sua inicializao so mostradas abaixo:
int i, f, F[MAX];
i = 0;
f = 0;
Suponha que queremos inserir um elemento de chave 8 na fila F da figura 20.1. A insero
de uma nova chave em uma fila sempre realizada no fim da estrutura. Como resultado desta
operao, esperamos que a fila tenha a configurao mostrada na figura 20.3 aps a execuo
dessa operao.
MAX-1
f
UFMS
20.2 O PERAES
179
A operao de inserir, ou enfileirar, uma chave em uma fila dada pela funo a seguir.
Suponha agora que queremos remover um elemento da fila F da figura 20.3. A remoo
de um elemento ou de uma chave sempre realizada no incio da fila. Como resultado desta
operao, esperamos que a fila tenha a configurao ilustrada na figura 20.3 aps terminada
sua execuo.
0
i
MAX-1
f
De acordo com as implementaes das operaes sobre a fila, observe que o ndice i movese somente quando da remoo de uma clula da fila e o ndice f move-se somente quando
da insero de uma clula na fila. Esses incrementos fazem com que a fila movimente-se
da esquerda para direita, o que pode ocasionar a falsa impresso de falta de capacidade de
armazenamento na fila, como mostramos na figura 20.5.
FACOM
UFMS
180
F ILAS
0
i
f 10
Consideramos daqui por diante que as clulas so alocadas seqencialmente como se estivessem em um crculo, onde o compartimento F[MAX-1] seguido pelo compartimento
F[0] . Assim, os elementos da fila esto dispostos no vetor F[0..MAX-1] em F[i..f-1] ou
em F[i..MAX-1]F[0..f-1] . Temos tambm que 0 6 i < MAX e 0 6 f 6 MAX . Com essas
suposies, dizemos que uma fila est vazia se i = INT_MIN e f = INT_MAX e est cheia se
f = i.
As operaes bsicas em uma fila circular so descritas a seguir.
FACOM
UFMS
20.3 O PERAES
181
A inicializao de uma fila vazia em alocao encadeada com cabea dada a seguir:
UFMS
182
F ILAS
Exemplos de filas vazias com cabea e sem cabea so mostrados na figura 20.7.
i
(b)
(a)
Figura 20.7: Representao de uma fila em alocao encadeada (a) com cabea e (b) sem cabea.
As operaes bsicas em uma fila com cabea so dadas abaixo.
Exerccios
20.1 Implemente as funes de enfileiramento e desenfileiramento em uma fila em alocao
encadaeda sem cabea.
FACOM
UFMS
20.3 O PERAES
183
20.2 Um estacionamento possui um nico corredor que permite dispor 10 carros. Os carros
chegam pelo sul do estacionamento e saem pelo norte. Se um cliente quer retirar um
carro que no est prximo do extremo norte, todos os carros impedindo sua passagem
so retirados, o cliente retira seu carro e os outros carros so recolocados na mesma ordem que estavam originalmente. Sempre que um carro sai, todos os carros do sul so
movidos para frente, de modo que as vagas fiquem disponveis sempre no extremo sul
do estacionamento. Escreva um algoritmo que processa o fluxo de chegada/sada deste
estacionamento. Cada entrada para o algoritmo contm uma letra E para entrada ou S
para sada, e o nmero da placa do carro. Considere que os carros chegam e saem pela
ordem especificada na entrada. O algoritmo deve imprimir uma mensagem sempre que
um carro chega ou sai. Quando um carro chega, a mensagem deve especificar se existe
ou no vaga para o carro no estacionamento. Se no existe vaga, o carro deve esperar
at que exista uma vaga, ou at que uma instruo fornecida pelo usurio indique que
o carro deve partir sem que entre no estacionamento. Quando uma vaga torna-se disponvel, outra mensagem deve ser impressa. Quando um carro sai do estacionamento, a
mensagem deve incluir o nmero de vezes que o carro foi movimentado dentro da garagem, incluindo a sada mas no a chegada. Este nmero 0 se o carro partiu da linha de
espera, isto , se o carro esperava uma vaga, mas partiu sem entrar no estacionamento.
20.3 Implemente uma fila usando duas pilhas.
20.4 Implemente uma pilha usando duas filas.
20.5 Suponha que temos n cidades numeradas de 0 a n 1 e interligadas por estradas de
mo nica. As ligaes entre as cidades so representadas por uma matriz A definida da
seguinte forma: A[x][y] vale 1 se existe estrada da cidade x para a cidade y e vale 0 em caso
contrrio. A figura 20.8 ilustra um exemplo. A distncia de uma cidade o a uma cidade
x o menor nmero de estradas que preciso percorrer para ir de o a x. O problema
que queremos resolver o seguinte: determinar a distncia de uma dada cidade o a cada
uma das outras cidades da rede. As distncias so armazenadas em um vetor d de tal
modo que d[x] seja a distncia de o a x. Se for impossvel chegar de o a x, podemos dizer
que d[x] vale . Usamos ainda 1 para representar uma vez que nenhuma distncia
real pode ter valor 1.
0
1
2
3
4
5
0
0
0
0
0
1
0
1
1
0
0
0
0
1
2
0
1
0
1
0
0
3
0
0
0
0
0
0
4
0
0
1
1
0
0
5
0
0
0
0
0
0
4
0 1 2 3 4 5
d 2 3 1 0 1 6
UFMS
184
F ILAS
uma formiga que est na casa (1, 1) a chegar casa (10, 10). Em cada passo, a formiga s
pode se deslocar para uma casa livre que esteja direita, esquerda, acima ou abaixo da
casa em que est.
20.7 Um deque uma lista linear que permite a insero e a remoo de elementos em ambos
os seus extremos. Escreva quatro funes para manipular um deque: uma que realiza
a insero de um novo elemento no incio do deque, uma que realiza a insero de um
novo elemento no fim do deque, uma que realiza a remoo de um elemento no incio do
deque e uma que realiza a remoo de um elemento no fim do deque.
20.8 Suponha que exista um nico vetor M de clulas de um tipo fila pr-definido, com um
total de MAX posies. Este vetor far o papel da memria do computador. Este vetor M
ser compartilhado por duas filas em alocao seqencial. Implemente eficientemente as
operaes de enfileiramento e desenfileiramento para as duas filas de modo que nenhuma
delas estoure sua capacidade de armazenamento, a menos que o total de elementos em
ambas as filas seja MAX.
FACOM
UFMS
AULA
L ISTAS
21
LINEARES CIRCULARES
Algumas operaes bsicas em listas lineares, em especial aquelas implementadas em alocao encadeada, no so muito eficientes. Um exemplo emblemtico desse tipo de operao
a busca, mais eficiente e mais econmica em listas lineares em alocao seqencial como pudemos perceber em aulas anteriores. As listas lineares circulares realizam algumas operaes
ainda mais eficientemente, j que possuem uma informao a mais que as listas lineares ordinrias.
Uma lista linear circular , em geral, implementada em alocao encadeada e difere de uma
lista linear por no conter um ponteiro para nulo em qualquer de suas clulas, o que no permite que se identifique um fim, ou mesmo um comeo, da lista. Caso exista uma clula cabea
da lista, ento possvel identificar facilmente um incio e um fim da lista. Nesta aula, baseada
nas referncias [7, 13], veremos a implementao das operaes bsicas de busca, insero e
remoo em listas lineares circulares com cabea em alocao encadeada.
Figura 21.1: Representao de uma lista linear circular com cabea em alocao encadeada.
185
186
L ISTAS
LINEARES CIRCULARES
A declarao e inicializao de uma lista linear circular com cabea em alocao encadeada
realizada da seguinte forma:
celula *lista;
lista = (celula *) malloc(sizeof (celula));
lista->prox = lista;
ou ainda:
celula c, *lista;
c.prox = &c;
lista = &c;
e sem cabea:
celula *lista;
lista = NULL;
A figura 21.2 mostra uma lista linear circular vazia (a) com cabea e (b) sem cabea.
lista
lista
(a)
(b)
Figura 21.2: Representao de uma lista linear circular vazia (a) com cabea e (b) sem cabea.
A seguir vamos implementar as operaes bsicas de busca, insero e remoo sobre listas
lineares circulares com cabea em alocao encadeada. A implementao das operaes bsicas
em uma lista linear circular sem cabea em alocao encadeada deixada como exerccio.
FACOM
UFMS
21.1 O PERAES
187
21.1.1 Busca
Imagine que queremos verificar se uma chave est presente em uma lista linear circular
em alocao encadeada. A funo busca_circular recebe um ponteiro c para a lista linear
circular e um valor x e devolve uma clula contendo a chave procurada x , caso x ocorra na
lista. Caso contrrio, a funo devolve NULL .
Observe que a busca em uma lista linear circular em alocao encadeada muito semelhante busca em uma lista linear ordinria da aula 18. Em particular, seu tempo de execuo
de pior caso proporcional ao nmero de clulas da lista, assim como o tempo de execuo
de pior caso da busca em uma lista linear ordinria. Observe, no entanto, que as constantes so
diferentes: enquanto na busca em uma lista linear ordinria a estrutura de repetio sempre realiza duas comparaes, a estrutura de repetio da busca em uma lista circular realiza apenas
uma comparao.
21.1.2 Insero
A insero em uma lista linear circular com cabea em alocao encadeada simples e
semelhante insero em uma lista linear ordinria. Temos apenas de prestar ateno para que
a insero sempre se mantenha circular, o que se d de modo natural se inserimos a clula que
contm a nova chave sempre como a clula seguinte cabea.
UFMS
188
L ISTAS
LINEARES CIRCULARES
A figura 21.3 mostra a execuo da funo insere_circular para uma chave 2 e uma
lista circular c .
c
(a)
c
nova
(b)
c
nova
(c)
Figura 21.3: Insero da chave 2 em uma lista circular c .
21.1.3 Remoo
A funo de remoo de um elemento em uma lista linear circular em alocao encadeada
tambm muito semelhante remoo de um elemento realizada em uma lista linear ordinria.
Buscamos a clula a ser removida como sendo aquela que possui a chave x , fornecida como
parmetro para a funo.
FACOM
UFMS
21.1 O PERAES
189
Exerccios
21.1 Escreva funes que implementem as operaes bsicas de busca, insero e remoo
sobre uma lista linear circular sem cabea em alocao encadeada.
21.2 Escreva uma funo para liberar todas as clulas de uma lista linear circular em alocao encadeada para a lista de espaos disponveis da memria. Escreva duas verses
dessa funo, uma para uma lista linear circular com cabea e outra para uma lista linear
circular sem cabea.
21.3 Suponha que queremos implementar uma lista linear circular em alocao encadeada de
tal forma que as chaves de suas clulas sempre permaneam em ordem crescente. Escreva as funes para as operaes bsicas de busca, insero e remoo para listas lineares circulares em alocao encadeada com as chaves em ordem crescente. Escreva dois
conjuntos de funes, considerando listas lineares circulares com cabea e listas lineares
circulares sem cabea.
21.4 O problema de Josephus foi assim descrito atravs do relato de Flavius Josephus, um
historiador judeu que viveu no primeiro sculo, sobre o cerco de Yodfat. Ele e mais 40
soldados aliados estavam encurralados em uma caverna rodeada por soldados romanos
e, no havendo sada, optaram ento por suas prprias mortes antes da captura. Decidiram que formariam um crculo e, a cada contagem de 3, um soldado seria morto pelo
grupo. Josephus foi o soldado restante e decidiu ento no se suicidar, deixando essa estria como legado. Podemos descrever um problema mais geral como segue. Imagine n
pessoas dispostas em crculo. Suponha que as pessoas so numeradas de 1 a n no sentido
horrio e que um nmero inteiro m, com 1 6 m < n, seja fornecido. Comeando com a
pessoa de nmero 1, percorra o crculo no sentido horrio e elimine cada m-sima pessoa
enquanto o crculo tiver duas ou mais pessoas. Escreva e teste uma funo que resolva o
problema, imprimindo na sada o nmero do sobrevivente.
FACOM
UFMS
190
L ISTAS
LINEARES CIRCULARES
(a)
(b)
(c)
(d)
Figura 21.4: Remoo da chave 4 de uma lista circular c .
FACOM
UFMS
21.1 O PERAES
191
21.5 Um computador permite representar nmeros inteiros at um valor mximo, que depende da sua organizao e arquitetura interna. Suponha que seja necessrio, em uma
dada aplicao, o uso de nmeros inteiros com preciso muito grande, digamos ilimitada. Uma forma de representar nmeros inteiros com preciso ilimitada mostrada na
figura 21.5.
n
89615
79730
19428
82
Figura 21.5: Uma lista linear circular com cabea em alocao encadeada que armazena o nmero inteiro 82194287973089615.
O tipo clula, que define as clulas da lista linear circular, no sofre alteraes na sua
definio, sendo o mesmo o tipo celula previamente definido. Observe apenas que
uma chave contm um nmero inteiro de at 5 algarismos.
Escreva uma funo que receba duas listas lineares circulares contendo dois nmeros
inteiros de preciso ilimitada e devolva uma lista linear circular contendo o resultado da
soma desses dois nmeros.
FACOM
UFMS
192
FACOM
L ISTAS
LINEARES CIRCULARES
UFMS
AULA
L ISTAS
22
LINEARES DUPLAMENTE
ENCADEADAS
Quando trabalhamos com listas lineares encadeadas, muitas vezes precisamos de manter
um ponteiro para uma clula e tambm para a clula anterior para poder realizar algumas operaes sobre a lista, evitando percursos adicionais desnecessrios. Esse o caso, por exemplo,
da busca e da insero. No entanto, algumas vezes isso no suficiente, j que podemos precisar percorrer a lista linear nos dois sentidos. Neste caso, para solucionar eficientemente esse
problema, adicionamos um novo campo ponteiro nas clulas da lista, que aponta para a clula
anterior da lista. O gasto de memria imposto pela adio deste novo campo se justifica pela
economia em no ter de reprocessar a lista linear inteira nas operaes de interesse. Esta aula
baseada nas referncias [7, 13].
22.1 Definio
As clulas de uma lista linear duplamente encadeada so ligadas por ponteiros que indicam
a posio da clula anterior e da prxima clula da lista. Assim, um outro campo acrescentado
cada clula da lista indicando, alm do endereo da prxima clula, o endereo da clula
anterior da lista. Veja a figura 22.1.
chave
ant
prox
Figura 22.1: Representao de uma clula de uma lista linear duplamente encadeada.
A definio de uma clula de uma lista linear duplamente encadeada ento descrita como
um tipo, como mostramos a seguir:
193
194
L ISTAS
Uma representao grfica de uma lista linear em alocao encadeada mostrada na figura 22.2.
lista
Figura 22.2: Representao de uma lista linear duplamente encadeada com cabea lista. A
primeira clula tem seu campo ant apontando para NULL e a ltima tem seu campo prox tambm apontando para NULL.
Uma lista linear duplamente encadeada com cabea pode ser criada e inicializada da seguinte forma:
celula *lista;
lista = (celula *) malloc(sizeof (celula));
lista->ant = NULL;
lista->prox = NULL;
celula *lista;
lista = NULL;
A figura 22.3 ilustra a declarao e inicializao de uma lista linear duplamente encadeada
(a) com cabea e (b) sem cabea.
lista
lista
(a)
(b)
Figura 22.3: Lista linear duplamente encadeada vazia (a) com cabea e (b) sem cabea.
A seguir vamos discutir e implementar as operaes bsicas sobre listas lineares duplamente encadeadas com cabea, deixando as liestas lineares sem cabea para os exerccios.
FACOM
UFMS
22.2 B USCA
195
22.2 Busca
A funo busca_dup_C recebe uma lista linear duplamente encadeada lst e um valor
x e devolve uma clula que contm a chave procurada x , caso x ocorra em lst . Caso
contrrio, a funo devolve NULL .
/* Recebe um nmero inteiro x e uma lista duplamente encadeada lst e remove da lista a primeira clula que contiver x, se tal clula existir */
void busca_remove_dup_C(int x, celula *lst)
{
celula *p;
p = lst->prox;
while (p != NULL && p->chave != x)
p = p->prox;
if (p != NULL) {
p->ant->prox = p->prox;
if (p->prox != NULL)
p->prox->ant = p->ant;
free(p);
}
}
UFMS
196
L ISTAS
Exerccios
22.1 Escreva funes para implementar as operaes bsicas de busca, insero e remoo em
uma lista linear duplamente encadeada sem cabea.
22.2 Escreva uma funo que receba um ponteiro para o incio de uma lista linear em alocao duplamente encadeada, um ponteiro para o fim dessa lista e uma chave, e realize a
insero dessa chave no final da lista.
22.3 Listas lineares duplamente encadeadas tambm podem ser listas circulares. Basta olhar
para uma lista linear duplamente encadeada e fazer o ponteiro para a cula anterior da
clula cabea apontar para a ltima clula e a ponteiro para a prxima clula da ltima
FACOM
UFMS
22.4 B USCA
lista
197
SEGUIDA DE INSERO
x
8
(a)
lista
x
8
(b)
lista
x
8
(c)
lista
x
8
(d)
Figura 22.4: Remoo em uma lista linear duplamente encadeada com cabea.
FACOM
UFMS
198
L ISTAS
lista
(a)
lista
nova
4
(b)
lista
nova
p
4
(c)
lista
nova
p
4
(d)
lista
(e)
Figura 22.5: Insero em uma lista linear duplamente encadeada com cabea.
FACOM
UFMS
22.4 B USCA
199
SEGUIDA DE INSERO
clula apontar para a clula cabea. Escreva funes para implementar as operaes bsicas de busca, insero e remoo em uma lista linear duplamente encadeada circular com
cabea.
22.4 Considere uma lista linear duplamente encadeada contendo as chaves c1 , c2 , . . . , cn , como
na figura 22.6.
lista
c1
c2
cn
cn
cn1
c1
FACOM
UFMS
200
FACOM
L ISTAS
UFMS
AULA
TABELAS
23
DE ESPALHAMENTO
Donald E. Knuth, em seu livro The Art of Computer Programming, observou que o cientista
da computao Hans P. Luhn parece ter sido o primeiro a usar o conceito de espalhamento1
em janeiro de 1953. Tambm observou que Robert H. Morris, outro cientista da computao,
usou-o em um artigo da Communications of the ACM de 1968, tornando o termo espalhamento
formal e conhecido na academia. O termo com o sentido que enxergamos na Computao vem
da analogia com a mesma palavra da lngua inglesa, um termo no-tcnico que significa corte
e misture ou pique e misture.
Muitos problemas podem ser resolvidos atravs do uso das operaes bsicas de busca, insero e remoo sobre as suas entradas. Por exemplo, um compilador para uma linguagem
de programao mantm uma tabela de smbolos em memria onde as chaves so cadeias de
caracteres que correspondem s palavras-chaves da linguagem. Outro exemplo menos abstrato
o de armazenamento das correspondncias em papel dos funcionrios de uma empresa. Se a
empresa grande, em geral no possvel manter um compartimento para cada funcionrio(a),
sendo mais razovel manter, por exemplo, 26 compartimentos rotulados com as letras do alfabeto. As correspondncias de um dado funcionrio(a), cujo nome inicia com uma dada letra,
esto armazenadas no compartimento rotulado com aquela letra. A implementao de uma
tabela de espalhamento para tratar das operaes bsicas de busca, insero e remoo uma
maneira eficiente de solucionar problemas como esses. De fato, uma tabela de espalhamento
uma generalizao da noo de vetor, como veremos.
Nesta aula, baseada nas referncias [1, 13], estudaremos essas estruturas de dados.
201
202
TABELAS
DE ESPALHAMENTO
Ento, usamos uma tabela de acesso direto para representar esse conjunto de informaes,
que nada mais que um vetor T [0..m 1] do tipo inteiro, onde o ndice de cada compartimento
de T corresponde a uma chave do conjunto C . Em um dado momento durante a execuo da
aplicao, um determinado conjunto de chaves C C est representado na tabela T . Veja a
figura 23.1 para uma ilustrao.
0
1
C
8
2
3
9
C
8
5
9
4
0
6
4
7
(a)
(b)
Figura 23.1: Dois exemplos de tabela de acesso direto. (a) Cada compartimento da tabela armazena um ponteiro para um registro contendo a chave e outras informaes relevantes. (b)
Cada compartimento da tabela armazena um bit indicando se a chave representada no ndice
do vetor est presente.
fcil ver que as operaes bsicas de busca, remoo e insero so realizadas em tempo
constante em uma tabela de acesso direto, considerando que as chaves so todas distintas. O
problema com esta estratgia a impossibilidade evidente de alocao de espao na memria
quando o conjunto de todas as possveis chaves C contm alguma chave que um nmero
inteiro muito grande. Ademais, se h poucas chaves representadas no conjunto C em um dado
instante da aplicao, a tabela de acesso direto ter, nesse instante, muito espao alocado mas
no usado.
UFMS
23.2 I NTRODUO
203
S TABELAS DE ESPALHAMENTO
A figura 23.2 mostra um exemplo de uma tabela de espalhamento associada a uma funo
de espalhamento.
x2
x4
x2
C
x3
x4
x3
x5
x6
x1
x5
x1
x6
Figura 23.2: Exemplo de uma tabela de espalhamento T associada a uma funo de espalhamento h. Observe que ocorrem as colises h(x5 ) = h(x1 ) = h(x6 ) e h(x2 ) = h(x4 ) e que tais
colises so resolvidas com listas lineares encadeadas com cabea.
A principal estratgia de uma tabela de espalhamento fazer uso de uma boa funo de
espalhamento que permita distribuir bem as chaves pela tabela. Em geral, como |C | > m, isto
, como o nmero de elementos do conjunto de chaves possveis maior que o tamanho da
tabela T , duas ou mais chaves certamente tero o mesmo ndice. Ou seja, devem existir pelo
menos duas chaves, digamos xi e xj , no conjunto das chaves possveis C tais que h(xi ) = h(xj ),
como vimos na figura 23.2. Quando duas chaves xi e xj possuem essa propriedade, dizemos
que h, ou pode haver, uma coliso entre xi e xj na tabela de espalhamento T . Uma maneira
bastante evidente de solucionar o problema das colises manter uma lista linear encadeada
com cabea para cada subconjunto de colises.
Observe que se a funo de espalhamento muito ruim, podemos ter um caso degenerado
em que todas as chaves tm o mesmo ndice, isto , h(x1 ) = h(x2 ) = = h(xn ) para toda
chave xi C, com 1 6 i 6 n e |C| = n. Isso significa que temos, na verdade, uma tabela
implementada como lista linear encadeada. Observe que esse caso muito ruim especialmente
porque todas as operaes bsicas, de busca, remoo e insero, gastam tempo proporcional
ao nmero de chaves contidas na lista, isto , tempo proporcional a n. Veja a figura 23.3 para
um exemplo desse caso.
A situao ideal quando projetamos uma tabela de espalhamento usar uma funo de
espalhamento que distribua bem as chaves por todos os m compartimentos da tabela T . Dessa
forma, uma tal funo maximiza o nmero de compartimentos usados em T e minimiza os
comprimentos das listas lineares encadeadas que armazenam as colises. Um exemplo de uma
situao como essa ilustrado na figura 23.4.
FACOM
UFMS
204
TABELAS
x2
DE ESPALHAMENTO
x1
x4
x6
x2
C
x4
x1 x
Figura 23.3: Exemplo de uma funo de espalhamento ruim, isto , uma funo que produz
uma tabela de espalhamento degenerada.
b
x3
x9
x2
C
x4
x3
x9
C
x8
x2
x4
x10 x6
x5
x7 x1
x10
x6
x5
x1
x7
x8
Figura 23.4: Exemplo de uma funo de espalhamento prxima do ideal, que espalha bem as
chaves pela tabela T , havendo poucas colises e poucos compartimentos apontando para listas
lineares vazias.
FACOM
UFMS
23.3 T RATAMENTO
205
UFMS
206
TABELAS
DE ESPALHAMENTO
transform-las em nmeros naturais. Por exemplo, se as chaves so cadeias de caracteres, podemos somar o valor de cada caractere que compe a cadeia, obtendo assim um nmero natural
que representa esta chave.
Nesta aula, estudamos as heursticas mencionadas acima, deixando que os leitores interessados no mtodo universal de espalhamento consultem as referncias desta aula.
Mtodo da diviso
O mtodo da diviso projeta uma funo de espalhamento mapeando o valor x da chave
em um dos m compartimentos da tabela T . O mtodo divide ento x por m e toma o resto
dessa diviso. Dessa forma, a funo de espalhamento h dada por:
h(x) = x mod m .
Para que a funo obtida seja o mais efetiva possvel, devemos evitar certos valores de m
que podem tornar a funo menos uniforme. Em geral, escolhemos um nmero primo grande
no muito prximo a uma potncia de 2.
Por exemplo, suponha que desejamos alocar uma tabela de espalhamento com colises resolvidas por listas lineares encadeadas, que armazena aproximadamente n = 1000 cadeias de
caracteres. Se no nos importa examinar em mdia 5 chaves em uma busca sem sucesso, ento
fazemos m = 199, j que 199 um nmero primo prximo a 1000/5 e distante de uma potncia
de 2.
Mtodo da multiplicao
O mtodo da multiplicao projeta funes de espalhamento da seguinte forma. Primeiro,
multiplicamos a chave x por uma constante de ponto flutuante A, com 0 < A < 1, e tomamos
a parte fracionria de xA. Depois, multiplicamos esse valor por m e tomamos o piso do valor
resultante. Ou seja, uma funo de espalhamento h dada por:
h(x) = m xA xA .
UFMS
23.4 E NDEREAMENTO
207
ABERTO
m 2 1
m 1 1
Figura 23.5: Uma tabela de espalhamento com endereamento aberto vazia.
Na funo insere_aberto a seguir, uma tabela de espalhamento T com endereamento
aberto, contendo m compartimentos, e uma chave x so fornecidas como entrada. Consideramos que as chaves esto armazenadas diretamente na tabela de espalhamento T e que um
compartimento vazio de T contm o valor 1. Aps o processamento, a funo devolve um
nmero inteiro entre 0 e m 1 indicando que a insero obteve sucesso ou o valor m indicando
o contrrio.
FACOM
UFMS
208
TABELAS
DE ESPALHAMENTO
A busca em uma tabela com endereamento aberto muito semelhante insero que vimos acima. A busca tambm termina se a chave foi encontrada na tabela ou quando encontra
um compartimento vazio.
A remoo de uma chave em um compartimento i da tabela de espalhamento com endereamento aberto T no permite que o compartimento i seja marcado com 1, j que isso tem
implicao direta em seqncias de tentativas posteriores, afetando negativamente operaes
bsicas subseqentes, especialmente a insero. Uma soluo possvel fazer com que cada
compartimento da tabela seja uma clulas com dois campos: uma chave e um estado. O estado
de uma clula pode ser um dos trs: VAZIA, OCUPADA ou REMOVIDA. Dessa forma, uma tabela
de espalhamento com endereamento aberto T um vetor do tipo celula, definido abaixo:
FACOM
UFMS
23.4 E NDEREAMENTO
209
ABERTO
struct cel {
int chave;
int estado;
};
typedef struct cel celula;
chave estado
VAZIA
VAZIA
m2
VAZIA
m1
VAZIA
Figura 23.6: Uma tabela de espalhamento com endereamento aberto vazia, onde cada compartimento contm uma chave e seu estado.
As trs operaes bsicas sobre essa nova tabela de espalhamento com endereamento
aberto so implementadas nas funes a seguir.
FACOM
UFMS
210
TABELAS
DE ESPALHAMENTO
UFMS
23.4 E NDEREAMENTO
211
ABERTO
chave estado
VAZIA
41 OCUPADA
22 OCUPADA
VAZIA
4 104 OCUPADA
5
VAZIA
96 OCUPADA
37 OCUPADA
16 OCUPADA
VAZIA
Figura 23.7: Um exemplo de uma tabela de espalhamento com endereamento aberto. A busca
da chave 16 usando a funo de espalhamento h segue a seqncia de tentativas ilustrada pela
linha pontilhada.
O papel da funo de espalhamento h gerar uma seqncia de compartimentos onde uma
chave de interesse pode ocorrer. Observe que, para a funo h particular que vimos acima,
se a remoo da chave 37 realizada na tabela T , a busca subseqente pela chave 16 ocorre
corretamente.
Em uma tabela de espalhamento com endereamento aberto temos sempre no mximo uma
chave por compartimento. Isso significa que n 6 m, onde n o nmero de chaves armazenadas
e m o total de compartimentos da tabela. Portanto, o fator de carga da tabela sempre satisfaz 6 1. Se consideramos a hiptese que a tabela de espalhamento uniforme, isto , que a
seqncia de tentativas hh(x, 0), h(x, 1), . . . , h(x, m1)i usada em uma operao bsica igualmente provvel a qualquer permutao de h0, 1, . . . , m 1i, ento temos duas conseqncias
importantes:
o nmero esperado de tentativas em uma busca sem sucesso no mximo
1
;
1
isso significa, por exemplo, que se a tabela est preenchida pela metade, ento o nmero
mdio de tentativas em uma busca sem sucesso no mximo 1/(1 0.5) = 2; se a tabela
est 90% cheia, ento o nmero mdio de tentativas no mximo 1/(1 0.9) = 10;
o nmero esperado de tentativas em uma busca com sucesso no mximo
1
1
ln
;
1
FACOM
UFMS
212
TABELAS
DE ESPALHAMENTO
isso significa, por exemplo, que se a tabela est preenchida pela metade, ento o nmero
mdio de tentativas em uma busca sem sucesso menor que 1.387; se a tabela est 90%
cheia, ento o nmero mdio de tentativas menor que 2.559.
para i = 0, 1, . . . , m1. Para uma chave x, o primeiro compartimento examinado T [h (x)], que
o compartimento obtido pela funo de espalhamento auxiliar. Em seguida, o compartimento
T [h (x) + 1] examinado e assim por diante, at o compartimento T [m 1]. Depois disso, a
seqncia de tentativas continua a partir de T [0], seguindo para T [1] e assim por diante, at
T [h (x) 1].
UFMS
23.4 E NDEREAMENTO
ABERTO
213
onde h1 e h2 so funes de espalhamento auxiliares. O compartimento inicialmente examinado T [h1 (x)]. Os compartimentos examinados em seguida so obtidos do deslocamento dos
compartimentos anteriores da quantidade de h2 (x) mdulo m.
A recomendao que o valor h2 (x) seja um primo relativo ao tamanho da tabela m. Podemos assegurar essa condio fazendo m uma potncia de 2 e projetando h2 de tal forma que
sempre produza um nmero mpar. Outra forma fazer m primo e projetar h2 de tal forma
que sempre devolva um nmero inteiro positivo menor que m.
O mtodo do espalhamento duplo um dos melhores mtodos disponveis para o endereamento aberto j que as permutaes produzidas tm muitas das caractersticas de permutaes
aleatrias. O desempenho do mtodo do espalhamento duplo , assim, prximo ao desempenho do esquema ideal de espalhamento universal.
Exerccios
23.1 Suponha que temos um conjunto de chaves C armazenado em uma tabela de acesso direto T de tamanho m. Escreva uma funo que encontra a maior chave de C. Qual o
tempo de execuo de pior caso da sua funo?
23.2 Suponha um conjunto de n chaves formado pelos n primeiros mltiplos de 7. Quantas
colises ocorrem mediante a aplicao das funes de espalhamento abaixo?
(a) x mod 7
(b) x mod 14
(c) x mod 5
23.3 Ilustre a insero das chaves 5, 28, 19, 15, 20, 33, 12, 17, 10 em uma tabela de espalhamento
com colises resolvidas por listas lineares encadeadas. Suponha que a tabela tenha 9
compartimentos e que a funo de espalhamento seja h(x) = x mod 9.
23.4 Considere uma tabela de espalhamento
de
tamanho m = 1000 e uma funo de espalha
mento h(x) = m Ax Ax para A = ( 5 1)/2. Compute as posies para as quais
as chaves 61, 62, 63, 64 e 65 so mapeadas.
23.5 Considere a insero das chaves 10, 22, 31, 4, 15, 28, 17, 88, 59 em uma tabela de espalhamento com endereamento aberto de tamanho m = 11 com funo de espalhamento
auxiliar h (x) = x mod m. Ilustre o resultado da insero dessas chaves usando tentativa linear, tentativa quadrtica com c1 = 1 e c2 = 3 e espalhamento duplo com
h2 (x) = 1 + (x mod (m 1)).
FACOM
UFMS
214
TABELAS
DE ESPALHAMENTO
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
long
switch
typedef
union
unsigned
void
volatile
while
FACOM
UFMS
AULA
O PERAES
24
SOBRE BITS
A linguagem C possui muitas caractersticas de uma linguagem de programao de alto nvel e isso significa que podemos escrever programas que so independentes das mquinas que
iro execut-los. A maioria das aplicaes possui naturalmente essa propriedade, isto , podemos propor solues computacionais programas escritos na linguagem C que no tm de se
adaptar s mquinas em que sero executados. Por outro lado, alguns programas mais especficos, como compiladores, sistemas operacionais, cifradores, processadores grficos e programas
cujo tempo de execuo e/ou uso do espao de armazenamento so crticos, precisam executar
operaes no nvel dos bits. Outra caracterstica importante da linguagem C, que a diferencia
de outras linguagens de programao alto nvel, que ela permite o uso de operaes sobre
bits especficos e sobre trechos de bits, e ainda permite o uso de estruturas de armazenamento
para auxiliar nas operaes de baixo nvel.
Nesta aula, baseada na referncia [7], tratamos com algum detalhe de cada um desses aspectos da linguagem C.
Significado
complemento
E
exclusivo
inclusivo
deslocamento esquerda
deslocamento direita
OU
OU
216
O PERAES
SOBRE BITS
1. Os operadores de deslocamento << e >> so operadores binrios que deslocam os bits de seu
operando esquerda um nmero de vezes representado pelo operando direita. Por exemplo,
i << j produz como resultado o valor de i deslocado de j posies esquerda. Para cada
bit que deslocado esquerda para fora do nmero, um bit 0 adicionado direita desse
nmero. O mesmo vale inversamente para o operador de deslocamento direita >>.
O programa 24.1 mostra um exemplo simples do uso de todos os operadores sobre bits.
k = ~i;
printf("
~i
k = i & j;
printf(" i & j
k = i ^ j;
printf(" i ^ j
k = i | j;
printf(" i | j
k = i << 4;
printf("i << 4
k = j >> 2;
printf("j >> 2
return 0;
}
UFMS
24.1 O PERADORES
i =
j =
217
BIT A BIT
51 (0000000000110011)
15 (0000000000001111)
~i = 65484 (1111111111001100)
i & j =
3 (0000000000000011)
i ^ j =
60 (0000000000111100)
i | j =
63 (0000000000111111)
i << 4 =
816 (0000001100110000)
j >> 2 =
3 (0000000000000011)
Os operadores sobre bits tm precedncias distintas uns sobre os outros, como mostra a
tabela abaixo:
Operador
~
& << >>
^
|
Precedncia
1 (maior)
2
3
4 (menor)
Observe ainda que a precedncia dos operadores sobre bits menor que precedncia dos
operadores relacionais. Isso significa que um sentena como a seguir
o que muito provavelmente no a inteno do(a) programador(a) neste caso. Dessa forma,
para que a inteno se concretize, devemos escrever:
Muitas vezes em programao de baixo nvel, queremos acessar bits especficos de uma
seqncia de bits. Por exemplo, quando trabalhamos com computao grfica, queremos colocar dois ou mais pixeis em um nico byte. Podemos extrair e/ou modificar dados que so
armazenados em um nmero pequeno de bits com o uso dos operadores sobre bits.
Suponha que i uma varivel do tipo unsigned short int , ou seja, um compartimento
de memria de 16 bits que armazena um nmero inteiro. As operaes mais freqentes sobre
um nico bit que podem ser realizadas sobre a varivel i so as seguintes:
Ligar um bit: suponha que queremos ligar o bit 4 de i , isto o quinto bit menos significativo, o quinto da direita para a esquerda. A forma mais fcil de ligar o bit 4 executar
um OU inclusivo entre i e a constante 0x0010 :
FACOM
UFMS
218
O PERAES
SOBRE BITS
i = i | 0x0010;
Mais geralmente, se a posio do bit que queremos ligar est armazenada na varivel j ,
podemos usar o operador de deslocamento esquerda e executar a seguinte expresso,
seguida da atribuio:
i = i | 1 << j;
Desligar um bit: para desligar o bit 4 de i , devemos usar uma mscara com um bit 0 na
posio 4 e o bit 1 em todas as outras posies:
Usando a mesma idia, podemos escrever uma sentena que desliga um bit cuja posio
est armazenada em uma varivel j :
Testando um bit: o trecho de cdigo a seguir verifica se o bit 4 da varivel i est ligado:
if (i & 0x0010)
if (i & 1 << j)
Para que o trabalho com bits torne-se um pouco mais fcil, freqentemente damos nomes
s posies dos bits de interesse. Por exemplo, se os bits das posies 1, 2 e 4 correspondem s
cores azul, verde e vermelho, podemos definir macros que representam essas trs posies dos
bits:
#define AZUL
1
#define VERDE
2
#define VERMELHO 4
Ligar, desligar e testar o bit AZUL pode ser feito como abaixo:
FACOM
UFMS
24.1 O PERADORES
BIT A BIT
219
i = i | AZUL;
i = i & ~AZUL;
if (i & AZUL) ...
i = i | AZUL | VERDE;
i = i & ~(AZUL | VERMELHO);
if (i & (VERDE | VERMELHO)) ...
Suponha ainda que a varivel j contm o valor a ser armazenado nos bits 46 de i .
Ento, podemos executar a seguinte sentena:
i = i & ~0x0070 | j 4;
Recuperar um trecho de bits: quando o trecho de bits atinge o bit 0 da varivel, a recuperao de seu valor pode ser facilmente obtida da seguinte forma. Suponha que queremos
recuperar os bits 02 da varivel i . Ento, podemos fazer:
j = i & 0x0007;
Se o trecho de bits no tem essa propriedade, ento podemos deslocar o trecho de bits
direita para depois extra-lo usando a operao E. Para extrair, por exemplo, os bits 46
de i , podemos usar a seguinte sentena:
FACOM
UFMS
220
O PERAES
SOBRE BITS
14
13
12
11
10
ano
ms
dia
struct data
unisgned
unisgned
unisgned
};
{
int dia: 5;
int mes: 4;
int ano: 7;
O nmero aps cada campo indica o comprimento em bits desse campo. O tipo de um
trecho de bits pode ser um de dois possveis: int ( signed int ) e unsigned int . O melhor declarar todos os trechos de bits que so campos de um registro como signed int ou
unsigned int .
Podemos usar os trechos de bits exatamente como usamos outros campos de um registro,
como mostra o exemplo a seguir:
Podemos considerar que um valor armazenado no campo ano seja relativo ao ano de
19802 . Ou seja a atribuio do valor 8 ao campo ano , como feita acima, tem significado 1988.
Depois dessas atribuies, a varivel data_arquivo tem a aparncia mostrada na figura 24.2.
Poderamos usar operadores sobre bits para obter o mesmo resultado, talvez at mais rapidamente. No entanto, escrever um programa legvel usualmente mais importante que ganhar
alguns microssegundos.
2
1980 o ano em que o mundo comeou, segundo a Micro$oft. O registro data a forma como o sistema
operacional MSDOS armazena a data que um arquivo foi criado ou modificado.
FACOM
UFMS
24.2 T RECHOS
221
DE BITS EM REGISTROS
15
14
13
12
11
10
Exerccios
24.1 Uma das formas mais simples de criptografar dados usar a operao de OU exclusivo
sobre cada caractere com uma chave secreta. Suponha, por exemplo, que a chave secreta
o caractere &. Supondo que usamos a tabela ASCII, se fizermos um OU exclusivo do
caractere z com a chave, obtemos o caractere \:
XOR
00100110
01111010
01011100
Para decifrar uma mensagem, aplicamos a mesma idia. Cifrando uma mensagem previamente cifrada, obtemos a mensagem original. Se executamos um OU exclusivo entre o
caractere & e o caractere \, por exemplo, obtemos o caractere original z:
XOR
00100110
01011100
01111010
Escreva um programa que receba uma cadeia de caracteres e cifre essa mensagem usando
a tcnica previamente descrita.
A mensagem original pode ser digitada pelo(a) usurio ou lida a partir de um arquivo
com redirecionamento de entrada. A mensagem cifrada pode ser vista na sada padro
(monitor) ou pode ser armazenada em um arquivo usando redirecionamento de sada.
FACOM
UFMS
222
FACOM
O PERAES
SOBRE BITS
UFMS
AULA
U NIES
25
E ENUMERAES
25.1 Unies
Como os registros, as unies tambm so compostas por um ou mais campos, definidos
possivelmente por tipos diferentes de dados. No entanto, o compilador aloca espao suficiente
apenas para o maior dos campos de uma unio, em termos de quantidade de bytes alocados.
Esses campos compartilham ento o mesmo espao na memria. Dessa forma, a atribuio de
um valor a um dos campos da unio tambm altera os valores dos outros campos nessa mesma
estrutura.
Vamos declarar a unio u com dois campos, um do tipo caractere com sinal e outro do tipo
inteiro com sinal, como abaixo:
union {
char c;
int i;
} u;
struct {
char c;
int i;
} r;
223
224
U NIES
E ENUMERAES
c
u
u.c = a;
Tambm podemos fazer a atribuio de um nmero inteiro para o outro campo da unio
como apresentado abaixo:
u.i = 3894;
Como o compilador superpe valores nos campos de uma unio, importante notar que a
alterao de um dos campos implica na alterao de qualquer valor previamente armazenado
nos outros campos. Portanto, se armazenamos, por exemplo, um valor no campo u.c , qualquer valor previamente armazenado no campo u.i ser perdido. Do mesmo modo, alterar o
valor do campo u.i altera o valor do campo u.c . Assim, podemos pensar na unio u como
um local na memria para armazenar um caractere no campo u.c ou um nmero no campo
u.i , mas no ambos. Diferentemente, o registro r pode armazenar valores no campo r.c e
no campo r.i .
FACOM
UFMS
25.1 U NIES
225
Como o que ocorre com registros, unies podem ser copiadas diretamente usando o comando de atribuio = , sem a necessidade de faz-las campo a campo. Declaraes e inicializaes simultneas tambm so feitas similarmente. No entanto, observe que somente o
primeiro campo de uma unio pode ter um valor atribudo no inicializador. Por exemplo, podemos fazer:
union {
char c;
int i;
} u = \0;
Usamos unies freqentemente como uma forma de economizar espao alocado desnecessariamente em registros. Ademais, usamos unies quando queremos criar estruturas de
armazenamento de dados que contenham uma mistura de diferentes tipos de dados, yambm
visando economia de espao alocado em memria.
Como um exemplo do segundo caso acima mencionado, suponha que temos uma coleo
de nmeros inteiros e de nmeros de ponto flutuante que queremos armazenar em um nico
vetor. Como os elementos de um vetor tm de ser de um mesmo tipo, devemos usar unies
para implementar um vetor com essa caracterstica. A declarao de um vetor como esse
dada a seguir:
union {
int i;
double d;
} vetor[100];
Cada compartimento do vetor acima declarado pode armazenar um valor do tipo int
ou um valor do tipo double , o que possibilita armazenar uma mistura de valores do tipo int
e double nos diferentes compartimentos do vetor . Por exemplo, para as atribuies abaixo
atribuem um nmero inteiro e um nmero de ponto flutuante para as posies 0 e 1 do vetor ,
respectivamente:
vetor[0].i = 5;
vetor[1].d = 7.647;
Suponha agora que queremos resolver o seguinte problema: dada uma seqncia n de
nmeros, de qualquer dos tipos inteiro ou real, com 1 6 n 6 100, computar a adio de todos os
nmeros inteiros e o produto de todos os nmeros reais, mostrando os respectivos resultados
na sada padro.
O programa 25.1 mostra um exemplo do uso de um vetor de registros onde um de seus
campos uma unio.
FACOM
UFMS
226
U NIES
E ENUMERAES
UFMS
25.2 E NUMERAES
227
25.2 Enumeraes
Muitas vezes precisamos de variveis que contero, durante a execuo de um programa,
somente um pequeno conjunto de valores. Por exemplo, variveis lgicas ou booleanas devero conter somente dois valores possveis: verdadeiro e falso. Uma varivel que armazena
os naipes das cartas de um baralho dever conter apenas quatro valores potenciais: paus,
copas, espadas e ouros. Uma maneira natural de tratar esses valores atravs da declarao de uma varivel do tipo inteiro que pode armazenar valores que representam esses
naipes. Por exemplo, 0 representa o naipe paus, 1 o naipe copas e assim por diante. Assim, podemos por exemplo declarar uma varivel naipe e atribuir valores a essa varivel da
seguinte forma:
int n;
n = 0;
Apesar de essa tcnica funcionar, algum que precisa compreender um programa que contm esse trecho de cdigo potencialmente incapaz de saber que a varivel n pode assumir
apenas quatro valores durante a execuo do programa e, ademais, o significado do valor 0 no
imediatamente aparente.
Uma estratgia melhor que a anterior o uso de macros para definir um tipo naipe e
tambm para definir nomes para os diversos naipes existentes. Ento, podemos fazer:
#define
#define
#define
#define
#define
NAIPE
PAUS
COPAS
ESPADAS
OUROS
int
0
1
2
3
NAIPE n;
n = PAUS;
Apesar de melhor, essa estratgia ainda possui restries. Por exemplo, algum que necessita compreender o programa no tem a viso imediata que as macros representam valores do
mesmo tipo. Alm disso, se o nmero de valores possveis um pouco maior, a definio de
uma macro para cada valor pode se tornar uma tarefa tediosa. Por fim, os identificadores das
macros sero removidos pelo pr-processador e substitudos pelos seus valores respectivos e
isso significa que no estaro disponveis para a fase de depurao do programa. Depurao
uma atividade muito til para programao a medida que nossos programas ficam maiores e
mais complexos.
FACOM
UFMS
228
U NIES
E ENUMERAES
A linguagem C possui um tipo especial especfico para variveis que armazenam um conjunto pequeno de possveis valores. Um tipo enumerado um tipo cujos valores so listados
ou enumerados pelo(a) programador(a) que deve criar um nome ou identificador, chamado
de uma constante da enumerao, para cada um dos valores possveis. O exemplo a seguir
enumera os valores PAUS , COPAS , ESPADAS e OUROS que podem ser atribudos s variveis
n1 e n2 :
Os valores das constantes da enumerao podem ser valores inteiros arbitrrios, listados
sem um ordem particular, como por exemplo:
enum FACOM = 17, CCET = 19, DEL = 11, DHT = 2, DEC = 21, DMT = 6 unidade;
FACOM
UFMS
25.2 E NUMERAES
229
int i;
enum PAUS, COPAS, ESPADAS, OUROS n;
i = COPAS;
n = 0;
n++;
i = n + 2;
Exerccios
25.1 Escreva um programa que receba uma coleo de n nmeros, com 1 6 n 6 100, que
podem ser inteiros pequenos de 1 (um) byte, inteiros maiores de 4 (quatro) bytes ou nmeros reais, e receba ainda um nmero x e verifique se x pertence a esse conjunto. Use
uma forma de armazenamento que minimize a quantidade de memria utilizada. Use
um vetor de registros tal que cada clula contm um campo que uma enumerao, para
indicar o tipo do nmero armazenado nessa clula, e um campo que uma unio, para
armazenar um nmero.
25.2 Dados os nmeros inteiros m e n e uma matriz A de nmeros inteiros, com 1 6 m, n 6
100, calcular a quantidade de linhas que contm o elemento 0 (zero) e a quantidade de
colunas. Use uma varivel indicadora de passagem, lgica, declarada como uma enumerao.
FACOM
UFMS
230
U NIES
E ENUMERAES
FACOM
UFMS
R EFERNCIAS B IBLIOGRFICAS