Sie sind auf Seite 1von 238

Algoritmos e Programao de Computadores II

Fbio Henrique Viduani Martinez


Faculdade de Computao UFMS
2011
SUMRIO
1 Recurso 1
1.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Ecincia de algoritmos e programas 9
2.1 Algoritmos e programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Anlise de algoritmos e programas . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.1 Ordem de crescimento de funes matemticas . . . . . . . . . . . . . . . 13
2.3 Anlise da ordenao por trocas sucessivas . . . . . . . . . . . . . . . . . . . . . . 15
2.4 Moral da histria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3 Correo de algoritmos e programas 21
3.1 Correo de funes recursivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Correo de funes no-recursivas e invariantes . . . . . . . . . . . . . . . . . . . 22
3.2.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.2 Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4 Busca 29
4.1 Busca seqencial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2 Busca em um vetor ordenado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5 Ordenao: mtodos elementares 37
5.1 Problema da ordenao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.2 Mtodo das trocas sucessivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.3 Mtodo da seleo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.4 Mtodo da insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6 Ordenao por intercalao 41
i
6.1 Dividir para conquistar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.2 Problema da intercalao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.3 Ordenao por intercalao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7 Ordenao por separao 47
7.1 Problema da separao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.2 Ordenao por separao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
8 Listas de prioridades 53
8.1 Heaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.1.1 Manuteno da propriedade max-heap . . . . . . . . . . . . . . . . . . . . 55
8.1.2 Construo de um max-heap . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.1.3 Alterao de uma prioridade em um max-heap . . . . . . . . . . . . . . . 59
8.2 Listas de prioridades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
8.3 Ordenao usando um max-heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
9 Introduo aos ponteiros 65
9.1 Variveis ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
9.2 Operadores de endereamento e de indireo . . . . . . . . . . . . . . . . . . . . . 67
9.3 Ponteiros em expresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
10 Programas extensos 75
10.1 Arquivos-fontes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
10.2 Arquivos-cabealhos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
10.3 Diviso de programas em arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
11 Ponteiros e funes 83
11.1 Parmetros de entrada e sada? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
11.2 Devoluo de ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
12 Ponteiros e vetores 89
12.1 Aritmtica com ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
12.2 Uso de ponteiros para processamento de vetores . . . . . . . . . . . . . . . . . . . 93
12.3 Uso do identicador de um vetor como ponteiro . . . . . . . . . . . . . . . . . . . 94
13 Ponteiros e matrizes 101
13.1 Ponteiros para elementos de uma matriz . . . . . . . . . . . . . . . . . . . . . . . . 101
ii
13.2 Processamento das linhas de uma matriz . . . . . . . . . . . . . . . . . . . . . . . 102
13.3 Processamento das colunas de uma matriz . . . . . . . . . . . . . . . . . . . . . . 103
13.4 Identicadores de matrizes como ponteiros . . . . . . . . . . . . . . . . . . . . . . 103
14 Ponteiros e cadeias 107
14.1 Literais e ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
14.2 Vetores de cadeias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
14.3 Argumentos na linha de comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
15 Ponteiros e registros 119
15.1 Ponteiros para registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
15.2 Registros contendo ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
16 Uso avanado de ponteiros 125
16.1 Ponteiros para ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
16.2 Alocao dinmica de memria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
16.3 Ponteiros para funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
17 Arquivos 137
17.1 Seqncias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
17.2 Redirecionamento de entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . 138
17.3 Funes de entrada e sada da linguagem C . . . . . . . . . . . . . . . . . . . . . . 140
17.3.1 Funes de abertura e fechamento . . . . . . . . . . . . . . . . . . . . . . . 140
17.3.2 Funes de entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
17.3.3 Funes de controle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
17.3.4 Funes sobre arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.3.5 Arquivos do sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.4 Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
18 Listas lineares 151
18.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
18.2 Listas lineares com cabea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
18.2.1 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
18.2.2 Remoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
18.2.3 Insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
18.2.4 Busca com remoo ou insero . . . . . . . . . . . . . . . . . . . . . . . . 159
iii
18.3 Listas lineares sem cabea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
18.3.1 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
18.3.2 Busca com remoo ou insero . . . . . . . . . . . . . . . . . . . . . . . . 162
19 Pilhas 169
19.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
19.2 Operaes bsicas em alocao seqencial . . . . . . . . . . . . . . . . . . . . . . . 170
19.3 Operaes bsicas em alocao encadeada . . . . . . . . . . . . . . . . . . . . . . . 172
20 Filas 177
20.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
20.2 Operaes bsicas em alocao seqencial . . . . . . . . . . . . . . . . . . . . . . . 177
20.3 Operaes bsicas em alocao encadeada . . . . . . . . . . . . . . . . . . . . . . . 181
21 Listas lineares circulares 185
21.1 Operaes bsicas em alocao encadeada . . . . . . . . . . . . . . . . . . . . . . . 185
21.1.1 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
21.1.2 Insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
21.1.3 Remoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
22 Listas lineares duplamente encadeadas 193
22.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
22.2 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
22.3 Busca seguida de remoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
22.4 Busca seguida de insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
23 Tabelas de espalhamento 201
23.1 Tabelas de acesso direto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
23.2 Introduo s tabelas de espalhamento . . . . . . . . . . . . . . . . . . . . . . . . . 202
23.3 Tratamento de colises com listas lineares encadeadas . . . . . . . . . . . . . . . . 205
23.3.1 Funes de espalhamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
23.4 Endereamento aberto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
23.4.1 Funes de espalhamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
24 Operaes sobre bits 215
24.1 Operadores bit a bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
iv
24.2 Trechos de bits em registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
25 Unies e enumeraes 223
25.1 Unies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
25.2 Enumeraes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
v
AULA 1
RECURSO
Recurso um conceito fundamental em computao. Sua compreenso nos permite cons-
truir 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 pro-
veniente 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 Denio
De acordo com [2], em muitos problemas computacionais encontramos a seguinte propri-
edade: 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 umalgoritmo recursivo, que implementado na lingua-
gem 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 no-
recursiva. No semestre anterior, construimos apenas funes no-recursivas.
1
2 RECURSO
Em geral, a toda funo recursiva corresponde uma outra no-recursiva que executa exa-
tamente a mesma computao. Como alguns problemas possuem estrutura recursiva natural,
funes recursivas so facilmente construdas a partir de suas denies. Alm disso, a de-
monstrao da correo de um algoritmo recursivo facilitada pela relao direta entre sua
estrutura e a induo matemtica. Outras vezes, no entanto, a implementao de umalgoritmo
recursivo demanda umgasto maior de memria, j que durante seu processo de execuo mui-
tas 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 n-
mero inteiro n 0. A idia da soluo atravs de uma funo recursiva baseada na frmula
mostrada a seguir:
n! =
_
1 , se n 1 ,
n (n 1)! , caso contrrio .
A funo recursiva fat , que calcula o fatorial de um dado nmero inteiro no negativo n,
mostrada a seguir:
/
*
Recebe um nmero inteiro n >= 0 e devolve o fatorial de n
*
/
int fat(int n)
{
int result;
if (n <= 1)
result = 1;
else
result = n
*
fat(n-1);
return result;
}
A sentena return pode aparecer em qualquer ponto do corpo de uma funo e por isso
podemos escrever a funo fat como a seguir:
/
*
Recebe um nmero inteiro n >= 0 e devolve o fatorial de n
*
/
int fat(int n)
{
if (n <= 1)
return 1;
else
return n
*
fat(n-1);
}
Geralmente, preferimos a primeira verso implementada acima, onde existe apenas uma
sentena com a palavra reservada return posicionada no nal do corpo da funo fat . Essa
maneira de escrever funes recursivas evita confuso e nos permite seguir o uxo de execuo
FACOM UFMS
1.2 EXEMPLOS 3
dessas funes mais naturalmente. No entanto, a segunda verso da funo fat apresentada
acima equivalente primeira e isso signica 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)

fat(2)

fat(1)

devolve 1

devolve 2 1 = 2 fat(1)

devolve 3 2 = 3 fat(2)

Repare nas indentaes que ilustram as chamadas recursivas funo.


Vamos ver um prximo exemplo. Considere o problema de determinar um valor mximo
de um vetor v com n elementos. O tamanho de uma entrada do problema n 1. Se n = 1
ento v[0] o nico elemento do vetor e portanto v[0] mximo. Se n > 1 ento o valor que
procuramos o maior dentre o mximo do vetor v[0..n 2] e o valor armazenado em v[n 1].
Dessa forma, a entrada v[0..n 1] do problema ca reduzida entrada v[0..n 2]. A funo
maximo a seguir implementa essa idia.
/
*
Recebe um nmero inteiro n > 0 e um vetor v de nmeros in-
teiros 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. Feoloff [2], para vericar que uma funo recursiva est correta, devemos se-
guir o seguinte roteiro, que signica mostrar por induo a correo de um algoritmo ou pro-
grama:
FACOM UFMS
4 RECURSO
Passo 1: escreva o que a funo deve fazer;
Passo 2: verique se a funo de fato faz o que deveria fazer quando a entrada pequena;
Passo 3: imagine que a entrada grande e suponha que a funo far a coisa certa para entra-
das menores; sob essa hiptese, verique que a funo faz o que dela se espera.
Isso posto, vamos provar ento a seguinte propriedade sobre a funo maximo descrita
acima.
Proposio 1.1. A funo maximo encontra um maior elemento em um vetor v com n 1 nmeros
inteiros.
Demonstrao. Vamos provar a armao usando induo na quantidade n de elementos do
vetor v.
Se n = 1 o vetor v contm exatamente um elemento, um nmero inteiro, armazenado em
v[0]. A funo maximo devolve, neste caso, o valor armazenado em v[0], que o maior ele-
mento no vetor v.
Suponha que para qualquer valor inteiro positivo mmenor que n, a chamada externa fun-
o maximo(m, v) devolva corretamente o valor de um maior elemento no vetor v contendo
m elementos.
Suponha agora que temos um vetor v contendo n > 1 nmeros inteiros. Suponha que
fazemos a chamada externa maximo(n, v) . Como n > 1, o programa executa a sentena
descrita abaixo:
aux = maximo(n-1, v);
Ento, por hiptese de induo, sabemos que a funo maximo devolve ummaior 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 x
n
, pode ser computada recursiva-
mente observando a seguinte a frmula:
x
n
=
_
1 , se n = 0 ,
x x
n1
, se n > 1 .
Considere neste exerccio que x e n so nmeros inteiros.
FACOM UFMS
1.2 EXEMPLOS 5
(a) Escreva uma funo no-recursiva com a seguinte interface:
int pot(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva x
n
.
(b) Escreva uma funo recursiva com a seguinte interface:
int potR(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva x
n
.
(c) Escreva um programa que receba dois nmeros inteiros x e n, com n 0, e devolva
x
n
. Use as funes em (a) e (b) para mostrar os dois resultados.
Programa 1.1: Soluo do exerccio 1.1.
#include <stdio.h>
/
*
Recebe um dois nmeros inteiros x e n e devolve x a n-sima potncia
*
/
int pot(int x, int n)
{
int i, result;
result = 1;
for (i = 1; i <= n; i++)
result = result
*
x;
return result;
}
/
*
Recebe um dois nmeros inteiros x e n e devolve x a n-sima potncia
*
/
int potR(int x, int n)
{
if (n == 0)
return 1;
else
return x
*
potR(x, n-1);
}
/
*
Recebe dois nmeros inteiros x e n e imprime x a n-sima potn-
cia chamando duas funes: uma no-recursiva e uma recursiva
*
/
int main(void)
{
int x, n;
scanf("%d%d", &x, &n);
printf("No-resursiva: %d^%d = %d\n", x, n, pot(x, n));
printf("Resursiva : %d^%d = %d\n", x, n, potR(x, n));
return 0;
}
FACOM UFMS
6 RECURSO
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);
}
}
Escreva um programa para testar a funo imprime_alguma_coisa .
1.3 (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 (a) Escreva uma funo recursiva com a seguinte interface:
float soma(int n, float v[MAX])
que receba um nmero inteiro n > 0 e um vetor v de nmeros com ponto utuante
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 (a) Escreva uma funo recursiva com a seguinte interface:
int soma_digitos(int n)
que receba um nmero inteiro positivo n e devolva a soma de seus dgitos.
(b) Escreva um programa que receba um nmero inteiro n e imprima a soma de seus
dgitos. Use a funo do item (a).
1.6 A seqncia de Fibonacci uma seqncia de nmeros inteiros positivos dada pela se-
guinte frmula:
_
_
_
F
1
= 1
F
2
= 1
F
i
= F
i1
+ F
i2
, para i 3.
(a) Escreva uma funo recursiva com a seguinte interface:
int Fib(int i)
que receba um nmero inteiro positivo i e devolva o i-simo termo da seqncia de
Fibonacci, isto , F
i
.
FACOM UFMS
1.2 EXEMPLOS 7
(b) Escreva um programa que receba um nmero inteiro i 1 e imprima o termo F
i
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 x < i + 1. O piso
de x denotado por x.
Segue uma amostra de valores da funo log
2
n:
n 15 16 31 32 63 64 127 128 255 256 511 512
log
2
n 3 4 4 5 5 6 6 7 7 8 8 9
(a) Escreva uma funo recursiva com a seguinte interface:
int piso_log2(int n)
que receba um nmero inteiro positivo n e devolva log
2
n.
(b) Escreva um programa que receba um nmero inteiro n 1 e imprima log
2
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 5 16 8 4 2 1
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
8 RECURSO
1.9 Podemos calcular a potncia x
n
de uma maneira mais eciente. Observe primeiro que se
n uma potncia de 2 ento x
n
pode ser computada usando seqncias de quadrados.
Por exemplo, x
4
o quadrado de x
2
e assimx
4
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:
x
n
=
_
_
_
1 , se n = 0,
(x
n/2
)
2
, se n par,
x x
n1
, se n mpar.
(1.1)
(a) Escreva uma funo com interface
int potencia(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva x
n
usando a frmula (1.1).
(b) Escreva um programa que receba dois nmeros inteiros a e b e imprima o valor de
a
b
.
FACOM UFMS
AULA 2
EFICINCIA DE ALGORITMOS E
PROGRAMAS
Medir a ecincia de umalgoritmo ou programa signica tentar predizer os recursos neces-
srios 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 tambmpodemser de
interesse. Na anlise de algoritmos e/ou programas alternativos para soluo de um mesmo
problema, aqueles mais ecientes de acordo com algum desses critrios so em geral escolhi-
dos como melhores. Nesta aula faremos uma discusso inicial sobre ecincia de algoritmos e
programas tendo como base as referncias [1, 2, 8].
2.1 Algoritmos e programas
Nesta aula, usaremos os termos algoritmo e programa como sinnimos. Dessa forma, po-
demos dizer que um algoritmo ou programa, como j sabemos, uma seqncia bem denida
de passos descritos em uma linguagem de programao especca que transforma um con-
junto de valores, chamado de entrada, e produz um conjunto de valores, chamado de sada.
Assim, um algoritmo ou programa tambm uma ferramenta para solucionar um problema
computacional bem denido.
Suponha que temos um problema computacional bem denido, conhecido como o pro-
blema da busca, que certamente j nos deparamos antes. A descrio mais formal do problema
dada a seguir:
Dado um nmero inteiro n, com 1 n 100, um conjunto C de n nmeros inteiros
e um nmero inteiro x, vericar se x encontra-se no conjunto C.
O problema da busca um problema computacional bsico que surge em diversas apli-
caes prticas. A busca uma operao bsica em computao e, por isso, vrios bons pro-
gramas que a realizam foram desenvolvidos. A escolha do melhor programa para uma dada
aplicao depende de alguns fatores como a quantidade de elementos no conjunto C e da com-
plexidade da estrutura em que C est armazenado.
Se para qualquer entrada um programa pra com a resposta correta, ento dizemos que o
mesmo est correto. Assim, umprograma correto soluciona o problema computacional associ-
ado. Um programa incorreto pode sequer parar, para alguma entrada, ou pode parar mas com
uma resposta indesejada.
9
10 EFICINCIA DE ALGORITMOS E PROGRAMAS
O programa 2.1 implementa uma busca de um nmero inteiro x em um conjunto de n
nmeros inteiros C armazenado na memria como um vetor. A busca se d seqencialmente
no vetor C, da esquerda para direita, at que um elemento com ndice i no vetor C contenha o
valor x, quando o programa responde que o elemento foi encontrado, mostrando sua posio.
Caso contrrio, o vetor C todo percorrido e o programa informa que o elemento x no se
encontra no vetor C.
Programa 2.1: Busca de x em C.
#include <stdio.h>
#define MAX 100
/
*
Recebe um nmero inteiro n, com 1 <= n <= 100, um conjunto C de n
nmeros inteiros e um nmero inteiro x, e verifica se x est em C
*
/
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;
}
2.2 Anlise de algoritmos e programas
Antes de analisar umalgoritmo ou programa, devemos conhecer o modelo da tecnologia de
computao usada na mquina emque implementamos o programa, para estabelecer os custos
associados aos recursos que o programa usa. Na anlise de algoritmos e programas, considera-
mos regularmente um modelo de computao genrico chamado de mquina de acesso alea-
trio (do ingls random acess machine RAM) comumprocessador. Nesse modelo, as instrues
so executadas uma aps outra, sem concorrncia. Modelos de computao paralela e distri-
buda, que usam concorrncia de instrues, so modelos investigativos que vm se tornando
realidade recentemente. No entanto, nosso modelo para anlise de algoritmos e programas no
leva em conta essas premissas.
FACOM UFMS
2.2 ANLISE DE ALGORITMOS E PROGRAMAS 11
A anlise de um programa pode ser uma tarefa desaadora envolvendo diversas ferra-
mentas matemticas tais como combinatria discreta, teoria das probabilidades, lgebra e etc.
Como o comportamento de um programa pode ser diferente para cada entrada possvel, preci-
samos de uma maneira que nos possibilite resumir esse comportamento em frmulas matem-
ticas simples e de fcil compreenso.
Mesmo com a conveno de um modelo xo 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 forne-
cidos na entrada. Isso signica 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 quan-
tidades 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. Otempo de execuo de umprograma sobre uma entrada particular
o nmero de operaes primitivas, ou passos, executados por ele. Quanto mais independente
da mquina a denio 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 c
i
, onde c
i

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 naliza 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 vericar a seguir o custo de cada linha no
programa 2.1 e tambmo nmero de vezes que cada linha executada. Atabela a seguir ilustra
esses elementos.
FACOM UFMS
12 EFICINCIA DE ALGORITMOS E PROGRAMAS
Custo Vezes
#include <stdio.h> c
1
1
#define MAX 100 c
2
1
int main(void) c
3
1
{ 0 1
int n, i, C[MAX], x; c
4
1
printf("Informe n: "); c
5
1
scanf("%d", &n); c
6
1
for (i = 0; i < n; i++) { c
7
n + 1
printf("Informe um elemento: "); c
8
n
scanf("%d", &C[i]); c
9
n
} 0 n
printf("Informe x: "); c
10
1
scanf("%d", &x); c
11
1
for (i = 0; i < n && C[i] != x; i++) c
12
t
i
; 0 t
i
1
if (i < n) c
13
1
printf("%d est na posio %d de C\n", x, i); c
14
1
else c
15
1
printf("%d no pertence ao conjunto C\n", x); c
16
1
return 0; c
17
1
} 0 1
O tempo de execuo do programa 2.1 dado pela soma dos tempos para cada sentena
executada. Uma sentena que gasta c
i
passos e executada n vezes contribui comc
i
n no tempo
de execuo do programa. Para computar T(n), o tempo de execuo do programa 2.1, deve-
mos somar os produtos das colunas Custo e Vezes, obtendo:
T(n) = c
1
+ c
2
+ c
3
+ c
4
+ c
5
+ c
6
+ c
7
(n + 1) + c
8
n + c
9
n+
c
10
+ c
11
+ c
12
t
i
+ c
13
+ c
14
+ c
15
+ c
16
+ c
17
.
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 pro-
grama 2.1, o melhor caso ocorre se o elemento x encontra-se na primeira posio do vetor C.
Assim, t
i
= 1 e o tempo de execuo do melhor caso dado por:
T(n) = c
1
+ c
2
+ c
3
+ c
4
+ c
5
+ c
6
+ c
7
(n + 1) + c
8
n + c
9
n+
c
10
+ c
11
+ c
12
+ c
13
+ c
14
+ c
15
+ c
16
+ c
17
= (c
7
+ c
8
+ c
9
)n+
(c
1
+ c
2
+ c
3
+ c
4
+ c
5
+ c
6
+ c
7
+ c
10
+ c
11
+ c
12
+ c
13
+ c
14
+ c
15
+ c
16
+ c
17
) .
Esse tempo de execuo pode ser expresso como uma funo linear an + b para constantes
a e b, que dependem dos custos c
i
das sentenas do programa. Assim, o tempo de execuo
dado por uma funo linear em n. No entanto, observe que as constantes c
7
, c
8
e c
9
que
multiplicam n na frmula de T(n) so aquelas que tratam somente da entrada de dados. Evi-
dentemente 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 c
12
na fr-
mula, que trata propriamente da busca, o tempo de execuo de melhor caso, quando t
i
= 1,
FACOM UFMS
2.2 ANLISE DE ALGORITMOS E PROGRAMAS 13
dado por:
T(n) = c
12
t
i
= c
12
.
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 i n 1. Uma ltima comparao ainda realizada quando i atinge o valor
n. Assim, t
i
= n + 1 e o tempo de execuo de pior caso dado por:
T(n) = c
12
t
i
= c
12
(n + 1) = c
12
n + c
12
.
Esse tempo de execuo de pior caso pode ser expresso como uma funo linear an+b para
constantes a e b que dependemsomente da constante c
12
, 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 con-
junto, 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 freqente-
mente nos programas em geral, como no caso do problema da busca.
2.2.1 Ordem de crescimento de funes matemticas
Acabamos de usar algumas convenes que simplicam a anlise do programa 2.1. A pri-
meira abstrao que zemos foi ignorar o custo real de uma sentena do programa, usando as
constantes c
i
para representar esses custos. Depois, observamos que mesmo essas constantes
nos do mais detalhes do que realmente precisamos: o tempo de execuo de pior caso do
programa 2.1 an + b, para constantes a e b que dependem dos custos c
i
das sentenas. Dessa
forma, ignoramos no apenas o custo real das sentenas mas tambm os custos abstratos c
i
.
Na direo de realizar mais uma simplicao, estamos interessados na taxa de cresci-
mento, ou ordem de crescimento, da funo que descreve o tempo de execuo de um al-
goritmo ou programa. Portanto, consideramos apenas o maior termo da frmula, como por
exemplo an, j que os termos menores so relativamente insignicantes quando n umnmero
grande. Tambm ignoramos o coeciente constante do maior termo, j que fatores constantes
so menos signicativos que a taxa de crescimento no clculo da ecincia computacional para
entradas grandes. Dessa forma, dizemos que o programa 2.1, por exemplo, tem tempo de
execuo de pior caso O(n).
Usualmente, consideramos um programa mais eciente que outro se seu tempo de exe-
cuo de pior caso tem ordem de crescimento menor. Essa avaliao pode ser errnea para
pequenas entradas mas, para entradas sucientemente grandes, um programa que tem tempo
de execuo de pior caso O(n) executar mais rapidamente no pior caso que um programa que
tem tempo de execuo de pior caso O(n
2
).
Quando olhamos para entradas cujos tamanhos so grandes o suciente para fazer comque
somente a taxa de crescimento da funo que descreve o tempo de execuo de um programa
FACOM UFMS
14 EFICINCIA DE ALGORITMOS E PROGRAMAS
seja relevante, estamos estudando na verdade a ecincia assinttica de um algoritmo ou pro-
grama. 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 eciente 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 denidas 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 denido 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 denir formalmente o que essa notao signica. 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 n
0
tais que
0 f(n) cg(n) para todo n n
0
} .
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 sucientemente grande. Dessa forma, usamos a notao
O para fornecer um limitante assinttico superior sobre uma funo, dentro de um fator cons-
tante. 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 gura 2.1 mostra a intuio por trs da notao O. Para todos os valores de n direita de
n
0
, o valor da funo f(n) est sobre ou abaixo do valor da funo g(n).
cg(n)
f(n)
n
0
n
Figura 2.1: f(n) = O(g(n)).
Dessa forma, podemos dizer, por exemplo, que 4n + 1 = O(n). Isso porque existem cons-
tantes positivas c e n
0
tais que
4n + 1 cn
FACOM UFMS
2.3 ANLISE DA ORDENAO POR TROCAS SUCESSIVAS 15
para todo n n
0
. Tomando, por exemplo, c = 5 temos
4n + 1 5n
1 n ,
ou seja, para n
0
= 1, a desigualdade 4n + 1 5n satisfeita para todo n n
0
. Certamente,
existem outras escolhas para as constantes c e n
0
, mas o mais importante que existe alguma
escolha. Observe que as constantes dependem da funo 4n + 1. Uma funo diferente que
pertence a O(n) provavelmente necessita de outras constantes.
A denio de O(g(n)) requer que toda funo pertencente a O(g(n)) seja assintoticamente
no-negativa, isto , que f(n) seja no-negativa sempre que n seja sucientemente grande.
Conseqentemente, a prpria funo g(n) deve ser assintoticamente no-negativa, caso contr-
rio o conjunto O(g(n)) vazio. Portanto, vamos considerar que toda funo usada na notao
O assintoticamente no-negativa.
A ordem de crescimento do tempo de execuo de um programa ou algoritmo pode ser
denotada atravs de outras notaes assintticas, tais como as notaes , , o e . Essas
notaes so especcas para anlise do tempo de execuo de um programa atravs de outros
pontos de vista. Nesta aula, caremos restritos apenas notao O. Em uma disciplina mais
avanada, como Anlise de Algoritmos, esse estudo se aprofunda e ateno especial dada a
este assunto.
2.3 Anlise da ordenao por trocas sucessivas
Vamos fazer a anlise agora no de um programa, mas de uma funo que j conhecemos
bem e que soluciona o problema da ordenao. A funo trocas_sucessivas faz a ordena-
o de um vetor v de n nmeros inteiros fornecidos como parmetros usando o mtodo das
trocas sucessivas ou o mtodo da bolha.
/
*
Recebe um nmero inteiro n > 0 e um vetor v com n nmeros intei-
ros e rearranja o vetor v em ordem crescente de seus elementos
*
/
void trocas_sucessivas(int n, int v[MAX])
{
int i, j, aux;
for (i = n-1; i > 0; i--)
for (j = 0; j < i; j++)
if (v[j] > v[j+1]) {
aux = v[j];
v[j] = v[j+1];
v[j+1] = aux;
}
}
Conforme j zemos antes, a anlise linha a linha da funo trocas_sucessivas des-
crita abaixo.
FACOM UFMS
16 EFICINCIA DE ALGORITMOS E PROGRAMAS
Custo Vezes
void trocas_sucessivas(int n, int v[MAX] c
1
1
{ 0 1
int i, j, aux; c
2
1
for (i = n-1; i > 0; i--) c
3
n
for (j = 0; j < i; j++) c
4

n1
i=1
(i + 1)
if (v[j] > v[j+1]) { c
5

n1
i=1
i
aux = v[j]; c
6

n1
i=1
t
i
v[j] = v[j+1]; c
7

n1
i=1
t
i
v[j+1] = aux; c
8

n1
i=1
t
i
} 0

n1
i=1
i
} 0 1
Podemos ir um passo adiante na simplicao da anlise de um algoritmo ou programa se
consideramos que cada linha temcusto de processamento 1. Na prtica, isso no bemverdade
j que h sentenas mais custosas que outras. Por exemplo, o custo para executar uma sentena
que contma multiplicao de dois nmeros de ponto utuante de preciso dupla maior que
o custo de comparar dois nmeros inteiros. No entanto, ainda assim vamos considerar que o
custo para processar qualquer linha de um programa constante e, mais ainda, que tem custo
1. Dessa forma, a coluna com rtulo Vezes nas anlises acima representam ento o custo de
execuo de cada linha do programa.
O melhor caso para a funo trocas_sucessivas ocorre quando a seqncia de entrada
com n nmeros inteiros fornecida em ordem crescente. Observe que, nesse caso, uma troca
nunca ocorrer. Ou seja, t
i
= 0 para todo i. Ento, o tempo de execuo no melhor caso dado
pela seguinte expresso:
T(n) = n +
n1

i=1
(i + 1) +
n1

i=1
i +
n1

i=1
t
i
+
n1

i=1
t
i
+
n1

i=1
t
i
= n + 2
n1

i=1
i +
n1

i=1
1 + 3
n1

i=1
t
i
= n + 2
n1

i=1
i +
n1

i=1
1 + 3
n1

i=1
0
= n + 2
n(n 1)
2
+ n 1
= n
2
+ n 1 .
Ento, sabemos que o tempo de execuo da funo trocas_sucessivas dado pela ex-
presso T(n) = n
2
+n1. Para mostrar que T(n) = O(n
2
) devemos encontrar duas constantes
positivas c e n
0
e mostrar que
n
2
+ n 1 cn
2
,
para todo n n
0
. Ento, escolhendo c = 2 temos:
n
2
+ n 1 2n
2
n 1 n
2
FACOM UFMS
2.3 ANLISE DA ORDENAO POR TROCAS SUCESSIVAS 17
ou seja,
n
2
n + 1 0 .
Observe ento que a inequao n
2
n + 1 0 sempre vlida para todo n 1. Assim,
escolhendo c = 2 e n
0
= 1, temos que
n
2
+ n 1 cn
2
para todo n n
0
, onde c = 2 e n
0
= 1. Portanto, T(n) = O(n
2
), ou seja, o tempo de execuo
do melhor caso da funo trocas_sucessivas quadrtico no tamanho da entrada.
Para denir a entrada que determina o pior caso para a funo trocas_sucessivas deve-
mos notar que quando o vetor contm os n elementos em ordem decrescente, o maior nmero
possvel de trocas realizado. Assim, t
i
= i para todo i e o tempo de execuo de pior caso
dado pela seguinte expresso:
T(n) = n +
n1

i=1
(i + 1) +
n1

i=1
i + 3
n1

i=1
t
i
= n +
n1

i=1
(i + 1) +
n1

i=1
i + 3
n1

i=1
i
= n + 5
n1

i=1
i +
n1

i=1
1
= n + 5
n(n 1)
2
+ n 1
=
5
2
n
2

5
2
n + 2n 1
=
5
2
n
2

1
2
n 1 .
Agora, para mostrar que o tempo de execuo de pior caso T(n) = O(n
2
), escolhemos
c = 5/2 e temos ento que
5
2
n
2

1
2
n 1
5
2
n
2

1
2
n 1 0
ou seja,
1
2
n + 1 0
e, assim, a inequao (1/2)n +1 0 para todo n 1. Logo, escolhendo c = 5/2 e n
0
= 1 temos
que
5
2
n
2

1
2
n 1 cn
2
para todo n n
0
, onde c = 5/2 e n
0
= 1. Assim, T(n) = O(n
2
), 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 EFICINCIA DE ALGORITMOS E PROGRAMAS
2.4 Moral da histria
Esta seo basicamente uma cpia traduzida da seo correspondente do livro de Cormen
et. al [1] e descreve um breve resumo a moral da histria sobre ecincia de algortimos e
programas.
Um bom algoritmo ou programa como uma faca aada: faz o que suposto fazer com a
menor quantidade de esforo aplicada. Usar um programa errado para resolver um problema
como tentar cortar um bife com uma chave de fenda: podemos eventualmente obter um
resultado digervel, mas gastaremos muito mais energia que o necessrio e o resultado no
dever ser muito agradvel esteticamente.
Programas alternativos projetados para resolver um mesmo problema em geral diferem
dramaticamente em ecincia. Essas diferenas podem ser muito mais signicativas que a di-
ferena de tempos de execuo em um supercomputador e em um computador pessoal. Como
um exemplo, suponha que temos um supercomputador executando o programa que imple-
menta o mtodo da bolha de ordenao, comtempo de execuo de pior caso O(n
2
), contra um
pequeno computador pessoal executando o mtodo da intercalao. Esse ltimo mtodo de
ordenao tem tempo de execuo de pior caso O(nlog n) para qualquer entrada de tamanho
n e ser visto na aula 6. Suponha que cada um desses programas deve ordenar um vetor de
um milho de nmeros. Suponha tambm que o supercomputador capaz de executar 100
milhes de operaes por segundo enquanto que o computador pessoal capaz de executar
apenas um milho de operaes por segundo. Para tornar a diferena ainda mais dramtica,
suponha que o mais habilidoso dos programadores do mundo codicou o mtodo da bolha na
linguagem de mquina do supercomputador e o programa usa 2n
2
operaes para ordenar n
nmeros inteiros. Por outro lado, o mtodo da intercalao foi programado por um progra-
mador mediano usando uma linguagem de programao de alto nvel com um compilador
ineciente e o cdigo resultante gasta 50nlog n operaes para ordenar os n nmeros. Assim,
para ordenar um milho de nmeros o supercomputador gasta
2 (10
6
)
2
operaes
10
8
operaes/segundo
= 20.000 segundos 5,56 horas,
enquanto que o computador pessoal gasta
50 10
6
log 10
6
operaes
10
6
operaes/segundo
1.000 segundos 16,67 minutos.
Usando umprograma cujo tempo de execuo temmenor 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 progra-
mas ecientes tanto quanto da escolha de computadores rpidos. Assimcomo rpidos avanos
esto sendo feitos em outras tecnologias computacionais, eles esto sendo feitos em algoritmos
e programas tambm.
FACOM UFMS
2.4 MORAL DA HISTRIA 19
Exerccios
2.1 Qual o menor valor de n tal que um programa com tempo de execuo 100n
2
mais
rpido que um programa cujo tempo de execuo 2
n
, 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 tama-
nho n, o mtodo das trocas sucessivas gasta 8n
2
passos enquanto que o mtodo da inter-
calao gasta 64nlog n passos. Para quais valores de n o mtodo das trocas sucessivas
melhor que o mtodo da intercalao?
2.3 Expresse a funo n
3
/1000 100n
2
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 1 1 1 1 1 1
segundo minuto hora dia ms ano sculo
log n

n
n
nlog n
n
2
n
3
2
n
n!
2.5 verdade que 2
n+1
= O(2
n
)? E verdade que 2
2n
= O(2
n
)?
2.6 Suponha que voc tenha algoritmos com os cinco tempos de execuo listados abaixo.
Quo mais lento cada um dos algoritmos ca quando voc (i) duplica o tamanho da
entrada, ou (ii) incrementa em uma unidade o tamanho da entrada?
(a) n
2
(b) n
3
(c) 100n
2
(d) nlog
2
n
(e) 2
n
2.7 Suponha que voc tenha algoritmos com os seis tempos de execuo listados abaixo. Su-
ponha que voc tenha um computador capaz de executar 10
10
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) n
2
FACOM UFMS
20 EFICINCIA DE ALGORITMOS E PROGRAMAS
(b) n
3
(c) 100n
2
(d) nlog
2
n
(e) 2
n
(f) 2
2
n
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)).
f
1
(n) = n
2.5
f
2
(n) =

2n
f
3
(n) = n + 10
f
4
(n) = 10
n
f
5
(n) = 100
n
f
6
(n) = n
2
log
2
n
2.9 Considere o problema de computar o valor de um polinmio em um ponto. Dados n
coecientes a
0
, a
1
, . . . , a
n1
e um nmero real x, queremos computar

n1
i=0
a
i
x
i
.
(a) Escreva um programa simples com tempo de execuo de pior caso O(n
2
) para so-
lucionar 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 po-
linmio:
n1

i=1
a
i
x
i
= ( (a
n1
x + a
n2
)x + + a
1
)x + a
0
.
2.10 Seja A[0..n1] umvetor 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 = 2, 3, 8, 6, 1.
(b) Qual vetor com elementos no conjunto {1, 2, . . . , n} tem a maior quantidade de in-
verses? Quantas so?
(c) Escreva um programa que determine o nmero de inverses em qualquer permuta-
o de n elementos em tempo de execuo de pior caso O(nlog n).
FACOM UFMS
AULA 3
CORREO DE ALGORITMOS E
PROGRAMAS
fato que estamos muito interessados emconstruir algoritmos e programas ecientes, con-
forme vimos na aula 2. No entanto, de nada vale um algoritmo eciente mas incorreto. Por
correto, queremos dizer que o algoritmo sempre pra coma resposta correta para todo entrada.
Devemos, assim, ser capazes de mostrar que nossos algoritmos so ecientes e corretos.
H duas estratgias bsicas para mostrar que um algoritmo, programa ou funo est cor-
reto, 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 itera-
tivos 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].
3.1 Correo de funes recursivas
Mostrar a correo de uma funo recursiva um processo quase que imediato. Usando
induo matemtca como ferramenta, a correo de uma funo recursiva dada naturalmente,
visto que sua estrutura intrnseca nos fornece muitas informaes teis para uma prova de
correo. Na aula 1, mostramos a correo da funo recursiva maximo usando induo.
Vamos mostrar agora que a funo potR descrita no exerccio 1.1 est correta. Reproduzi-
mos novamente a funo potR a seguir.
/
*
Recebe um dois nmeros inteiros x e n e devolve x a n-sima potncia
*
/
int potR(int x, int n)
{
if (n == 0)
return 1;
else
return x
*
potR(x, n-1);
}
21
22 CORREO DE ALGORITMOS E PROGRAMAS
Proposio 3.1. A funo potR recebe dois nmeros inteiros x e n e devolve corretamente o valor de
x
n
.
Demonstrao.
Vamos mostrar a proposio por induo em n.
Se n = 0 ento a funo devolve 1 = x
0
= x
n
.
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 x
k
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 li-
nha devolve corretamente o valor x
n1
. Logo, a chamada de potR(x, n) devolve
x x
n1
= x
n
.
3.2 Correo de funes no-recursivas e invariantes
Funes no-recursivas, em geral, possuem uma ou mais estruturas de repetio. Essas
estruturas, usadas para processar um conjunto de informaes de entrada e obter um outro
conjunto de informaes de sada, tambm so conhecidas como processos iterativos da fun-
o. Como veremos daqui por diante, mostrar a correo de uma funo no-recursiva um
trabalho mais rduo do que de uma funo recursiva. Isso porque devemos, neste caso, ex-
trair informaes teis desta funo que explicam o funcionamento do processo iterativo e que
nos permitam usar induo matemtica para, por m, mostrar que o processo est correto, isto
, que a funo est correta. Essas informaes so denominadas invariantes de um processo
iterativo.
3.2.1 Denio
Um invariante de um processo iterativo uma relao entre os valores das variveis envol-
vidas 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 CORREO DE FUNES NO-RECURSIVAS E INVARIANTES 23
Quando as duas primeiras propriedades so satisfeitas, o invariante verdadeiro antes de
toda iterao da estrutura de repetio. Como usamos invariantes para mostrar a correo de
umalgoritmo e/ou programa, a terceira propriedade a mais importante, aquela que permite
mostrar de fato a sua correo.
Dessa forma, os invariantes explicam o funcionamento dos processos iterativos e permitem
provar, por induo, que esses processos tm o efeito desejado.
3.2.2 Exemplos
Nesta seo veremos exemplos do uso dos invariantes para mostrar a correo de progra-
mas. 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, apre-
sentado 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 re-
petio for do programa 3.1: este o invariante desse processo iterativo. E como Feoloff
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 CORREO 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 numao 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 num
i
.
Provar que o programa 3.1 est correto signica mostrar que para qualquer valor de
n e qualquer seqncia de n nmeros, a varivel soma conter, ao nal do processo
iterativo, o valor
soma =
n

i=1
num
i
.
Vamos agora mostrar que o invariante vale no incio da primeira iterao do pro-
cesso iterativo. Como a varivel soma contmo valor 0 (zero) e i contm1, 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

i=1
num
i
.
Dessa forma, no decorrer da n-sima iterao, o(a) usurio(a) deve informar um
nmero que ser armazenado na varivel num
n
e, ento, a varivel soma conter o
valor
soma = + num
n
=
_
n1

i=1
num
i
_
+ num
n
=
n

i=1
num
i
.
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 in-
teiros 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 CORREO DE FUNES NO-RECURSIVAS E INVARIANTES 25
Programa 3.2: Mostra um maior valor em um vetor com n nmeros inteiros.
#include <stdio.h>
#define MAX 100
/
*
Recebe um nmero inteiro n > 0 e uma seqncia de n
nmeros inteiros e mostra um maior valor da seqncia
*
/
int main(void)
{
int n, vet[MAX], i, max;
scanf("%d", &n);
for (i = 0; i < n; i++)
scanf("%d", &vet[i]);
max = vet[0];
for (i = 1; i < n; i++) {
/
*
max um maior elemento em vet[0..i-1]
*
/
if (vet[i] > max)
max = vet[i];
}
printf("%d\n", max);
return 0;
}
Vamos mostrar agora a correo do programa 3.2.
Proposio 3.3. O programa 3.2 encontra um elemento mximo de um conjunto de n nmeros forne-
cidos pelo(a) usurio(a).
Demonstrao.
Novamente, por convenincia na demonstrao usaremos o modo matemtico para
expressar as variveis do programa: trocaremos i por i, vet por vet e max por
max.
Provar que o programa 3.2 est correto signica mostrar que para qualquer valor de
n e qualquer seqncia de n nmeros fornecidos pelo(a) usurio(a) e armazenados
em um vetor vet, a varivel max conter, ao nal do processo iterativo, o valor do
elemento mximo em vet[0..n 1].
Vamos mostrar que o invariante vale no incio da primeira iterao do processo
iterativo. Como max contmo valor armazenado emvet[0] e, emseguida, a varivel
i inicializada como valor 1, ento verdade que a varivel max contmo elemento
mximo em vet[0..i 1].
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 contmo
valor n1. 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 CORREO DE ALGORITMOS E PROGRAMAS
(i) vet[n 1] > max
Isso signica que o valor vet[n 1] maior que qualquer valor armazenado
em vet[0..n 2]. Assim, na linha 15 a varivel max atualizada com vet[n 1]
e portanto a varivel max conter, ao nal desta ltima iterao, o elemento
mximo da seqncia em vet[0..n 1].
(ii) vet[n 1] max
Isso signica que existe pelo menos um valor em vet[0..n 2] que maior ou
igual a vet[n 1]. Por hiptese de induo, esse valor est armazenado em
max. Assim, ao nal desta ltima iterao, a varivel max conter o elemento
mximo da seqncia em vet[0..n 1].
Portanto, isso mostra que o programa 3.2 de fato encontra o elemento mximo em
uma seqncia de n nmeros inteiros armazenados em um vetor.
Exerccios
3.1 O programa 3.3 recebe um nmero inteiro n > 0, uma seqncia de n nmeros inteiros,
um nmero inteiro x e verica se x pertence seqncia de nmeros.
Programa 3.3: Verica 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;
}
Mostre que o programa 3.3 est correto.
3.2 Escreva uma funo com a seguinte interface:
FACOM UFMS
3.2 CORREO DE FUNES NO-RECURSIVAS E INVARIANTES 27
void inverte(int n, int v[MAX])
que receba umnmero 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 CORREO DE ALGORITMOS E PROGRAMAS
FACOM UFMS
AULA 4
BUSCA
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 arma-
zenado 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 proble-
mas prticos. A busca ser xada, 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 eciente de busca em um vetor ordenado, chamada de busca binria. Esta aula est
completamente baseada no livro de P. Feoloff [2], captulos 3 e 7.
4.1 Busca seqencial
Vamos xar o conjunto e o elemento onde a busca se dar como sendo constitudos de
nmeros inteiros, observando que o problema da busca no se modica essencialmente se esse
tipo de dados for alterado. Assim, dado um nmero inteiro n 0, um vetor de nmeros
inteiros v[0..n 1] e um nmero inteiro x, considere o problema de encontrar um ndice k tal
que v[k] = x. O problema faz sentido com qualquer n 0. Observe que se n = 0 ento o vetor
vazio e portanto essa entrada do problema no tem soluo.
Como em [2], adotamos a conveno de devolver 1 caso o elemento x no pertena ao
vetor v. A conveno satisfatria j que 1 no pertence ao conjunto {0, . . . , n1} de ndices
vlidos do vetor v. Dessa forma, basta percorrer o vetor do m para o comeo, como mostra a
funo busca_sequencial a seguir.
/
*
Recebe um nmero inteiro n >= 0, um vetor v[0..n-1] com n n-
meros 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 BUSCA
Observe como a funo eciente e elegante, funcionando corretamente mesmo quando o
vetor est vazio, isto , quando n vale 0.
Um exemplo de uma chamada funo busca_sequencial apresentado abaixo:
i = busca_sequencial(v, n, x);
if (i >= 0)
printf("Encontrei %d!\n", v[i]);
A funo busca_sequencial pode ser escrita em verso recursiva. A idia do cdigo
simples: se n = 0 ento o vetor vazio e portanto x no est em v[0..n 1]; se n > 0 ento x
est em v[0..n1] se e somente se x = v[n1] ou x est no vetor v[0..n2]. A verso recursiva
ento mostrada abaixo:
/
*
Recebe um nmero inteiro n >= 0, um vetor de nmeros in-
teiros v[0..n-1] e um nmero x e devolve k tal que 0 <= k
< n e v[k] == x. Se tal k no existe, devolve -1.
*
/
int busca_sequencial_R(int n, int v[MAX], int x)
{
if (n == 0)
return -1;
else
if (x == v[n - 1])
return n - 1;
else
return busca_sequencial_R(n - 1, v, x);
}
Acorreo da funo busca_sequencial mostrada de forma semelhante quela do exer-
ccio 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 an-
lise de ecincia equivalentes.
Vale a pena consultar o captulo 3, pginas 12 e 13, do livro de P. Feoloff [2], para veri-
car uma srie de maus exemplos para soluo do problema da busca, dentre deselegantes,
inecientes e at incorretos.
4.2 Busca em um vetor ordenado
Como em[2], adotamos as seguintes denies. Dizemos que umvetor de nmeros inteiros
v[0..n1] crescente se v[0] v[1] v[n1] e decrescente se v[0] v[1] v[n1].
Dizemos ainda que o vetor ordenado se crescente ou decrescente. Nesta seo, vamos focar
no problema da busca de um elemento x em um vetor ordenado v[0..n 1].
No caso de vetores ordenados, uma pergunta equivalente mas ligeiramente diferente for-
mulada: 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 BUSCA EM UM VETOR ORDENADO 31
nmeros inteiros crescente v[0..n 1] e um nmero inteiro x, encontrar um ndice k tal que
v[k 1] < x v[k] . (4.1)
Encontrar um ndice k como o da equao (4.1) signica ento praticamente resolver o pro-
blema da busca, bastando comparar x com v[k].
Observe ainda que qualquer valor de k no intervalo [0, n] pode ser uma soluo do pro-
blema da busca. Nos dois extremos do intervalo, a condio (4.1) deve ser interpretada ade-
quadamente. Isto , se k = 0, a condio se reduz apenas a x 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 simplicar 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 in-
teiros crescente v[0..n-1] e um nmero inteiro x e devol-
ve 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;
}
Uma chamada funo buscaOrd mostrada a seguir:
i = busca_ordenada(n, v, x);
Se aplicamos a estratgia de busca seqencial em um vetor ordenado, ento certamente ob-
temos uma resposta correta, porm ineciente 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 pro-
priedade 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 eciente. A
busca binria se baseia no mtodo que usamos de modo automtico para encontrar uma pala-
vra no dicionrio: abrimos o dicionrio ao meio e comparamos a primeira palavra desta pgina
coma 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 BUSCA
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 in-
teiros crescente v[0..n-1] e um nmero inteiro x e devol-
ve 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;
}
Um exemplo de chamada funo busca_binaria mostrado abaixo:
k = busca_binaria(n, v, x);
Para provar a correo da funo busca_binaria , basta vericar o seguinte invariante:
no incio de cada repetio while , imediatamente antes da comparao de esq
com dir - 1 , vale a relao v[esq] < x <= v[dir] .
Com esse invariante em mos, podemos usar a estratgia que aprendemos na aula 3 para mos-
trar nalmente que essa funo est de fato correta.
Quantas iteraes a funo busca_binaria executa? O total de iteraes revela o valor
aproximado que representa o consumo de tempo dessa funo em um dado vetor de entrada.
Observe que emcada iterao, o tamanho do vetor v dado por dir - esq - 1 . No incio da
primeira iterao, o tamanho do vetor n. No incio da segunda iterao, o tamanho do vetor
aproximadamente n/2. No incio da terceira, aproximadamente n/4. No incio da (k+1)-sima,
aproximadamente n/2
k
. Quando k > log
2
n, temos n/2
k
< 1 e a execuo da funo termina.
Assim, o nmero de iteraes aproximadamente log
2
n. O consumo de tempo da funo
proporcional ao nmero de iteraes e portanto proporcional a log
2
n. Esse consumo de tempo
cresce com n muito mais lentamente que o consumo da busca seqencial.
Uma soluo recursiva para o problema da busca em um vetor ordenado apresen-
tada a seguir. Antes, necessrio reformular ligeiramente o problema. A funo recursiva
busca_binaria_R procura o elemento x no vetor crescente v[esq..dir] , supondo que o
FACOM UFMS
4.2 BUSCA EM UM VETOR ORDENADO 33
valor x est entre os extremos v[esq] e v[dir] .
/
*
Recebe dois nmeros inteiros esq e dir, um vetor de nmeros
inteiros crescente v[esq..dir] e um nmero inteiro x tais
que v[esq] < x <= v[dir] e devolve um ndice k em
[esq+1, dir] tal que v[k-1] < x <= v[k]
*
/
int busca_binaria_R(int esq, int dir, int v[MAX], int x)
{
int meio;
if (esq == dir - 1)
return dir;
else {
meio = (esq + dir) / 2;
if (v[meio] < x)
return busca_binaria_R(meio, dir, v, x);
else
return busca_binaria_R(esq, meio, v, x);
}
}
Uma chamada da funo busca_binaria_R pode ser realizada da seguinte forma:
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 log
2
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 emv[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 intei-
ros 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 BUSCA
4.2 Considere o problema de determinar o valor de um elemento mximo de um vetor
v[0..n 1]. Considere a funo maximo abaixo.
int maximo(int n, int v[MAX])
{
int i, x;
x = v[0];
for (i = 1; i < n; i++)
if (x < v[i])
x = v[i];
return x;
}
(a) A funo maximo acima resolve o problema?
(b) Faz sentido trocar x = v[0] por x = 0 ?
(c) Faz sentido trocar x = v[0] por x = INT_MIN
1
?
(d) Faz sentido trocar x < v[i] por x <= v[i] ?
4.3 O autor da funo abaixo arma que ela decide se x est no vetor v[0..n1]. Critique seu
cdigo.
int buscaR2(int n, int v[MAX], int x)
{
if (v[n-1] == x)
return 1;
else
return buscaR2(n-1, v, x);
}
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 k < n.
(a) Escreva uma funo no-recursiva com a seguinte interface:
int remove(int n, int v[MAX], int k)
que remove o elemento de ndice k do vetor v[0..n 1] e devolve o novo valor de n,
supondo que 0 k < n.
(b) Escreva uma funo recursiva para a remoo com a seguinte interface:
int removeR(int n, int v[MAX], int k)
1
INT_MIN o valor do menor nmero do tipo int .
FACOM UFMS
4.2 BUSCA EM UM VETOR ORDENADO 35
4.5 A operao de insero consiste em introduzir um novo elemento y entre a posio de
ndice k 1 e a posio de ndice k no vetor v[0..n 1], com 0 k n.
(a) Escreva uma funo no-recursiva com a seguinte interface:
int insere(int n, int v[MAX], int k, int y)
que insere o elemento y entre as posies k 1 e k do vetor v[0..n 1] e devolve o
novo valor de n, supondo que 0 k n.
(b) Escreva uma funo recursiva para a insero com a seguinte interface:
int insereR(int n, int v[MAX], int k, int x)
4.6 Na busca binria, suponha que v[i] = i para todo i.
(a) Execute a funo busca_binaria com n = 9 e x = 3;
(b) Execute a funo busca_binaria com n = 14 e x = 7;
(c) Execute a funo busca_binaria com n = 15 e x = 7.
4.7 Execute a funo busca_binaria com n = 16. Quais os possveis valores de m na
primeira iterao? Quais os possveis valores de m na segunda iterao? Na terceira? Na
quarta?
4.8 Conra a validade da seguinte armao: quando n + 1 uma potncia de 2, o va-
lor da expresso (esq + dir) divisvel por 2 em todas as iteraes da funo
busca_binaria , quaisquer que sejam v e x.
4.9 Responda as seguintes perguntas sobre a funo busca_binaria .
(a) Que acontece se a sentena while (esq < dir - 1) for substituda pela sentena
while (esq < dir) ?
(b) Que acontece se a sentena if (v[meio] < x) for substituda pela sentena
if (v[meio] <= x) ?
(c) Que acontece se esq = meio for substitudo por esq = meio + 1 ou ento por
esq = meio - 1 ?
(d) Que acontece se dir = meio for substitudo por dir = meio + 1 ou ento por
dir = meio - 1 ?
4.10 Se t segundos so necessrios para fazer uma busca binria em um vetor com n elemen-
tos, quantos segundos sero necessrios para fazer uma busca em n
2
elementos?
4.11 Escreva uma verso da busca binria para resolver o seguinte problema: dado um inteiro
x e um vetor decrescente v[0..n 1], encontrar k tal que v[k 1] > x v[k].
FACOM UFMS
36 BUSCA
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 ordemlexicogr-
ca. Escreva uma funo eciente, 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 cres-
cente 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 de-
volva umndice i entre 0 e n1 tal que v[i] = i. Se tal i no existe, a funo deve devolver
1. Asua funo no deve fazer mais que log
2
n comparaes envolvendo os elementos
de v.
FACOM UFMS
AULA 5
ORDENAO: MTODOS ELEMENTARES
Alm da busca, a ordenao outra operao elementar em computao. Nesta aula re-
visaremos 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. Feoloff [2].
5.1 Problema da ordenao
Lembrando da aula 4, um vetor v[0..n 1] crescente se v[0] v[1] v[n 1]. O
problema da ordenao de um vetor consiste em rearranjar, ou permutar, os elementos de um
vetor v[0..n 1] de tal forma que se torne crescente. Nesta aula ns discutimos trs funes
simples para soluo desse problema. Nas aulas 6 e 7 examinaremos funes mais sosticadas
e ecientes.
5.2 Mtodo das trocas sucessivas
O mtodo das trocas sucessivas, popularmente conhecido como mtodo da bolha, um
mtodo simples de ordenao que, a cada passo, posiciona o maior elemento de um sub-
conjunto de elementos do vetor de entrada na sua localizao correta neste vetor. A funo
trocas_sucessivas implementa esse mtodo.
/
*
Recebe um nmero inteiro n >= 0 e um vetor v de nmeros inteiros
com n elementos e rearranja o vetor v de modo que fique crescente
*
/
void trocas_sucessivas(int n, int v[MAX])
{
int i, j;
for (i = n - 1; i > 0; i--)
for (j = 0; j < i; j++)
if (v[j] > v[j+1])
troca(&v[j], &v[j+1]);
}
Um exemplo de chamada da funo trocas_sucessivas dado a seguir:
37
38 ORDENAO: MTODOS ELEMENTARES
trocas_sucessivas(n, v);
Para entender a funo trocas_sucessivas basta observar que no incio de cada repeti-
o 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] v[i + 1] para j = 0, 1, . . . , i.
Almdisso, o consumo de tempo da funo trocas_sucesivas proporcional ao nmero
de execues da comparao v[j] > v[j + 1], que proporcional a n
2
no pior caso.
5.3 Mtodo da seleo
O mtodo de ordenao por seleo baseado na idia de escolher um menor elemento do
vetor, depois umsegundo menor elemento e assimpor diante. Afuno selecao implementa
esse mtodo.
/
*
Recebe um nmero inteiro n >= 0 e um vetor v de nmeros inteiros
com n elementos e rearranja o vetor v de modo que fique crescente
*
/
void selecao(int n, int v[MAX])
{
int i, j, min;
for (i = 0; i < n - 1; i++) {
min = i;
for (j = i+1; j < n; j++)
if (v[j] < v[min])
min = j;
troca(&v[i], &v[min]);
}
}
Um exemplo de chamada da funo selecao dado a seguir:
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 MTODO DA INSERO 39
v[i 1] v[j] para j = i, i + 1, . . . , n 1.
O terceiro invariante pode ser assim interpretado: v[0..i 1] contm todos os elementos
pequenos do vetor original e v[i..n 1] contm todos os elementos grandes. Os trs inva-
riantes garantem que no incio de cada iterao os elementos v[0], . . . , v[i 1] j esto em suas
posies denitivas.
Uma anlise semelhante que zemos para a funo trocas_sucessivas mostra que o
mtodo da insero implementado pela funo selecao consome n
2
unidades de tempo no
pior caso.
5.4 Mtodo da insero
O mtodo da ordenao por insero muito popular. freqentemente usado quando
algum joga baralho e quer manter as cartas de sua mo em ordem. A funo insercao
implementa esse mtodo.
/
*
Recebe um nmero inteiro n >= 0 e um vetor v de nmeros inteiros
com n elementos e rearranja o vetor v de modo que fique crescente
*
/
void insercao(int n, int v[MAX])
{
int i, j, x;
for (i = 1; i < n; i++) {
x = v[i];
for (j = i - 1; j >= 0 && v[j] > x; j--)
v[j+1] = v[j];
v[j+1] = x;
}
}
Um exemplo de chamada da funo insercao dado a seguir:
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 n
2
.
FACOM UFMS
40 ORDENAO: MTODOS ELEMENTARES
Exerccios
5.1 Escreva uma funo que verique 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 fun-
o trocas_sucessivas . A nova funo continua produzindo uma ordenao cres-
cente 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 que em ordem
estritamente crescente.
5.12 Escreva uma funo que permute os elementos de um vetor v[0..n 1] de modo que eles
quem em ordem decrescente.
FACOM UFMS
AULA 6
ORDENAO 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 n
2
, onde n o tamanho da entrada.
Mtodos mais ecientes de ordenao so baseados emrecurso, 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 ecientes 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 combi-
namos essas solues para produzir uma soluo para o problema original. Esa aula baseada
no livro de P. Feoloff [2] e no livro de Cormen et. al [1].
6.1 Dividir para conquistar
A tcnica de dividir para conquistar uma tcnica geral de construo de algoritmos e
programas, tendo a recurso como base, que envolve trs passos em cada nvel da recurso:
Dividir o problema em um nmero de subproblemas;
Conquistar os subproblemas solucionando-os recursivamente. No entanto, se os tamanhos
dos subproblemas so sucientemente pequenos, resolva os subproblemas de uma ma-
neira simples;
Combinar as solues dos subproblemas na soluo do problema original.
Como mencionamos na aula 4, o algoritmo da busca binria um mtodo de busca que usa
a tcnica de dividir para conquistar na soluo do problema da busca. O mtodo de ordenao
por intercalao, que veremos nesta aula, e o mtodo da ordenao por separao, que veremos
na aula 7, tambm so algoritmos baseados nessa tcnica.
6.2 Problema da intercalao
Antes de apresentar o mtodo da ordenao por intercalao, precisamos resolver um pro-
blema anterior, que auxilia esse mtodo, chamado de problema da intercalao. Oproblema da
intercalao pode ser descrito de forma mais geral como a seguir: dados dois conjuntos cres-
centes A e B, com m e n elementos respectivamente, obter um conjunto crescente C a partir
41
42 ORDENAO 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 = ou A B = .
O problema da intercalao que queremos resolver aqui mais especco 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 signica que queremos de alguma forma intercalar os vetores v[0..q1] 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, temconsumo de tempo de pior caso proporcional ao quadrado do nmero
de elementos do vetor e ineciente por desconsiderar as caractersticas dos vetores v[p..q 1]
e v[q..r 1]. Uma soluo mais eciente, 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];
}
A funo intercala tem tempo de execuo de pior caso proporcional ao nmero de
comparaes entre os elementos do vetor, isto , r p. Assim, podemos dizer que o consumo
de tempo no pior caso da funo intercala proporcional ao nmero de elementos do vetor
de entrada.
FACOM UFMS
6.3 ORDENAO POR INTERCALAO 43
6.3 Ordenao por intercalao
Com o problema da intercalao resolvido, podemos agora descrever uma funo que im-
plementa o mtodo da ordenao por intercalao. Nesse mtodo, dividimos ao meio umvetor
v com r p elementos, ordenamos recursivamente essas duas metades de v e ento as interca-
lamos. A funo mergesort a seguir recursiva e a base da recurso ocorre quando p r 1,
quando no necessrio qualquer processamento.
/
*
Recebe um vetor v[p..r-1] e o rearranja em ordem crescente
*
/
void mergesort(int p, int r, int v[MAX])
{
int q;
if (p < r - 1) {
q = (p + r) / 2;
mergesort(p, q, v);
mergesort(q, r, v);
intercala(p, q, r, v);
}
}
Como a expresso (p +q)/2 da funo mergesort do tipo inteiro, observe que seu resul-
tado , 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);
Vejamos um exemplo de execuo da funo mergesort na gura 6.1, para um vetor de
entrada v[0..7] = {4, 6, 7, 3, 5, 1, 2, 8} e chamada
mergesort(0, 8, v);
Observe que as chamadas recursivas so realizadas at a linha divisria imaginria ilus-
trada na gura, quando p r 1. A partir desse ponto, a cada volta de um nvel de recurso,
uma intercalao realizada. No nal, 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 umvetor v[0..n1]?
Suponha, para efeito de simplicao, que n uma potncia de 2. Se esse no o caso, pode-
mos examinar duas potncias de 2 consecutivas, justamente aquelas tais que 2
k1
< n 2
k
,
para algum k 0. Observe ento que o nmero de elementos do vetor diminudo a aproxi-
madamente metade a cada chamada da funo mergesort . Ou seja, o nmero aproximado de
chamadas proporcional a log
2
n. Na primeira vez, o problema original reduzido a dois sub-
problemas onde necessrio ordenar os vetores v[0..
n
2
1] e v[
n
2
..n 1]. Na segunda vez, cada
FACOM UFMS
44 ORDENAO POR INTERCALAO
1 2 4 3 6 7 5 8
4 6 3 7 1 5 2 8
4 6 7 3 5 1 2 8
4 6 7 3 5 1 2 8
4 6 7 3 5 1 2 8
4 6 7 3 5 1 2 8
1 2 3 4 5 6 7 8
Figura 6.1: Exemplo de execuo da ordenao por intercalao.
um dos subproblemas so ainda divididos em mais dois subproblemas cada, gerando quatro
subproblemas no total, onde necessrio ordenar os vetores v[0..
n
4
1], v[
n
4
..
n
2
1], v[
n
2
..
3n
4
1]
e v[
3n
4
..n 1]. E assim por diante. Alm disso, como j vimos, o tempo total que a funo
intercala gasta proporcional ao nmero de elementos do vetor v, isto , r p. Portanto, a
funo mergesort consome tempo proporcional a nlog
2
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 ORDENAO POR INTERCALAO 45
intercala estvel? Se a comparao v[i] < v[j] for trocada por v[i] <= v[j] a
funo ca estvel?
6.4 O que acontece se trocarmos (p + r)/2 por (p + r - 1)/2 no cdigo da funo
mergesort ? Que acontece se trocarmos (p + r)/2 por (p + r + 1)/2 ?
6.5 Escreva uma verso da ordenao por intercalao que rearranje um vetor v[p..r 1] em
ordem decrescente.
6.6 Escreva uma funo eciente que receba um conjunto S de n nmeros reais e um nmero
real x e determine se existe um par de elementos em S cuja soma exatamente x.
6.7 Agora que voc aprendeu o mtodo da ordenao por intercalao, o problema a seguir,
que j vimos na aula 2, exerccio 2.10, ca bem mais fcil de ser resolvido.
Seja A um vetor de n nmeros inteiros distintos. 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 {2, 3, 8, 6, 1}.
(b) Qual vetor com elementos do conjunto {1, 2, . . . , n} tem o maior nmero de inver-
ses? 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? Justique sua resposta.
(d) Modicando a ordenao por intercalao, escreva uma funo eciente, comtempo
de execuo O(nlog 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;
Sorting Algorithms de J. Harrison;
Sorting Algorithms de P. Morin;
Sorting Algorithms Animations de D. R. Martin.
FACOM UFMS
46 ORDENAO POR INTERCALAO
FACOM UFMS
AULA 7
ORDENAO POR SEPARAO
O mtodo de ordenao por separao resolve o problema da ordenao descrito na aula 5,
onde se deve rearranjar um vetor v[0..n 1] de modo que se torne crescente. Em geral, este
mtodo muito mais rpido que os mtodos elementares vistos na aula 5, no entanto pode ser
lento, tanto quanto os mtodos elementares para algumas entradas do problema.
O mtodo da ordenao por separao recursivo e tambm se baseia na estratgia de
dividir para conquistar. O mtodo comumente conhecido como quisksort. freqentemente
usado na prtica para ordenao j que rpido na mdia e apenas para algumas entradas
especiais o mtodo lento como os mtodos elementares de ordenao.
Esta aula baseada nos livros de P. Feoloff [2] e Cormen et. al [1].
7.1 Problema da separao
O problema da separao e sua soluo computacional so o ponto-chave do mtodo da
ordenao por separao. Informalmente, no problema da separao queremos rearranjar um
vetor v[p..r] de modo que os elementos pequenos quem todos do lado esquerdo e os grandes
quem todos do lado direito de v. Note que, dessa descrio, nosso desejo que os dois lados
tenham aproximadamente o mesmo nmero de elementos, salvo alguns casos, que podem ser
menos equilibrados. Ademais, importante que a separao no seja degenerada, isto , que
deixe umdos lados vazio. Seguindo [2], a diculdade est emconstruir uma funo que resolva
o problema de maneira rpida e no use um vetor auxiliar.
O problema da separao que estamos interessados pode ento ser formulado da seguinte
maneira:
rearranjar o vetor v[p..r] de modo que tenhamos
v[p..q] v[q + 1..r]
para algum q em p..r 1. Note que a expresso v[p..q] v[q + 1..r] signica que
v[i] v[j] para todo i, p i q, e todo j, q < j r.
A funo separa abaixo soluciona o problema da separao de forma eciente. No incio,
umelemento de referncia x, chamado de piv, escolhido e, a partir de ento, o signicado de
pequeno e grande passa a ser associado a esse piv: os elementos do vetor que forem maiores
que x sero considerados grandes e os demais sero considerados pequenos.
47
48 ORDENAO POR SEPARAO
/
*
Recebe um par de nmeros inteiros p e r, com p <= r e um vetor v[p..r]
de nmeros inteiros e rearranja seus elementos e devolve um nmero in-
teiro j em p..r tal que v[p..j-1] <= v[j] < v[j+1..r]
*
/
int separa(int p, int r, int v[MAX])
{
int x, i, j;
x = v[p];
i = p - 1;
j = r + 1;
while (1) {
do {
j--;
} while (v[j] > x);
do {
i++;
} while (v[i] < x);
if (i < j)
troca(&v[i], &v[j]);
else
return j;
}
}
Um exemplo de execuo da funo separa apresentado na gura 7.1.
3 2 6 4 1 7 3 5 6 4 1 7 3 3 2 5
5 3 2 6 4 1 3 7 5 3 2 6 4 1 3 7
5
3 2 4 7 3 6 5 1
x
i
i i
i i
j
j j
j j
(a) (b)
(c) (d)
(e)
v[p..r]
v[p..q] v[q + 1..r]
Figura 7.1: Uma execuo da funo separa .
FACOM UFMS
7.2 ORDENAO POR SEPARAO 49
O corpo da estrutura de repetio while da funo separa repetido at que i j e,
neste ponto, o vetor v[p..r] acabou de ser separado emv[p..q] e v[q +1..r], com p q < r, tal que
nenhum elemento de v[p..q] maior que um elemento de v[q + 1..r]. O valor q = j devolvido
ento pela funo separa .
Em outras palavras, podemos dizer que no incio de cada iterao valem os seguintes inva-
riantes:
v[p..r] uma permutao do vetor original,
v[p..i] x v[j..r] e
p i j r.
O nmero de iteraes que a funo realiza proporcional a r p + 1, isto , proporcional
ao nmero de elementos do vetor.
7.2 Ordenao por separao
Com uma funo que soluciona o problema da separao, podemos descrever agora o m-
todo da ordenao por separao. Esse mtodo tambm usa a estratgia de dividir para con-
quistar e o faz de maneira semelhante ao mtodo da ordenao por intercalao, mas com
chamadas invertidas.
/
*
Recebe um vetor v[p..r-1] e o rearranja em ordem crescente
*
/
void quicksort(int p, int r, int v[MAX])
{
int q;
if (p < r) {
q = separa(p, r, v);
quicksort(p, q, v);
quicksort(q+1, r, v);
}
}
Uma chamada da funo quicksort para ordenao de um vetor v[0..n 1] deve ser feita
como a seguir:
quicksort(0, n-1, v);
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 ORDENAO POR SEPARAO
ento o nmero de comparaes ser aproximadamente nlog
2
n. Caso contrrio, o nmero de
comparaes ser da ordem de n
2
. Observe que isso ocorre, por exemplo, quando o vetor j
estiver ordenado ou quase-ordenado. Portanto, o consumo de tempo de pior caso da ordenao
por separao no melhor que o dos mtodos elementares vistos na aula 5. Felizmente, o pior
caso para a ordenao por separao raro. Dessa forma, o consumo de tempo mdio da
funo quicksort proporcional a nlog
2
n.
Exerccios
7.1 Ilustre a operao da funo separa sobre o vetor v que contm os elementos do con-
junto {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 quem esquerda e os positivos quem direita. Em outras
palavras, rearranje o vetor de modo que tenhamos v[p..q 1] 0 e v[q..r] > 0 para algum
q em p..r + 1. Procure escrever uma funo eciente 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] v[q] < v[q + 1..r] .
Escreva uma funo que decida se v[p..r] est arrumado. Em caso armativo, 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 modicaria a funo quicksort para ordenar elementos em ordem decres-
cente?
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 che-
ques 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 orde-
nar 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 ORDENAO 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 umvetor de cadeias de caracteres
em ordem lexicogrca.
7.12 Considere a seguinte soluo do problema da separao, devido a N. Lomuto. Para sepa-
rar 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.
/
*
Recebe um par de nmeros inteiros p e r, com p <= r e um vetor v[p..r]
de nmeros inteiros e rearranja seus elementos e devolve um nmero in-
teiro i em p..r tal que v[p..i] <= v[r] e v[r] < v[i+1..r]
*
/
int separa_Lomuto(int p, int r, int v[MAX])
{
int x, i, j;
x = v[r];
i = p - 1;
for (j = p; j <= r; j++)
if (v[j] <= x) {
i++;
troca(&v[i], &v[j]);
}
if (i < r)
return i;
else
return i - 1;
}
(a) Ilustre a operao da funo separa_Lomuto sobre o vetor v que contm os ele-
mentos {13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21}.
(b) Argumente que separa_Lomuto est correta.
(c) Qual o nmero mximo de vezes que um elemento pode ser movido pelas funes
separa e separa_Lomuto ? Justique 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 exe-
cuo do mtodo da ordenao por separao quando todos os valores de entrada
so iguais?
7.13 Afuno quicksort contmduas chamadas recursivas para ela prpria. Depois da cha-
mada 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 ORDENAO 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 da funo quicksort2 sobre o vetor v que contm os elementos
{21, 7, 5, 11, 6, 42, 13, 2}. Use a chamada quicksort2(0, n-1, v) . Argumente que a
funo quicksort2 ordena corretamente o vetor v.
7.14 Um famoso programador props o seguinte mtodo de ordenao de umvetor v[0..n1]
de nmeros inteiros:
/
*
Recebe um vetor v[i..j] e o rearranja em ordem crescente
*
/
void silly_sort(int i, int j, int v[MAX])
{
int k;
if (v[i] > v[j])
troca(&v[i], &v[j]);
if (i + 1 < j) {
k = (j - i + 1) / 3;
silly_sort(i, j-k, v);
silly_sort(i+k, j, v);
silly_sort(i, j-k, v);
}
}
Ilustre a operao desse novo mtodo de ordenao sobre o vetor v que contm os ele-
mentos {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;
Sorting Algorithms Animations de D. R. Martin.
7.16 Familiarize-se com a funo qsort da biblioteca stdlib da linguagem C.
FACOM UFMS
AULA 8
LISTAS 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 ope-
raes de insero e remoo de elementos deste conjunto, ou alterao de prioridades de ele-
mentos da lista, so tarefas associadas a estruturas como esta. Listas de prioridades so estru-
turas simples e ecientes 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 es-
trutura de dados, que base para aquelas, chamada heap
1
. 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
supruos 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 identicados por suas prioridades armazenadas em um
vetor numrico S satisfazendo a seguinte propriedade:
S[(i 1)/2] S[i] , (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 -
gura 8.1 um max-heap.
14 11
0 1 2 3 4 5 6 7 8 9
6 9 8 16 18 26 4 12 S
Figura 8.1: Um max-heap com 10 elementos.
1
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 LISTAS DE PRIORIDADES
Se o vetor S tem a propriedade
S[(i 1)/2] S[i] , (8.2)
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 mo-
mento, estamos interessados apenas em max-heaps.
Um max-heap pode tambm ser visto como uma rvore binria
2
com seu ltimo nvel
preenchido da esquerda para direita. Uma ilustrao do max-heap da gura 8.1 mostrada na
gura 8.2.
26
18 14
9 11 8 16
6
1 2
3 4 5 6
7
8 9
0
12 4
Figura 8.2: Representao do max-heap da gura 8.1 como uma rvore binria, com seu ltimo
nvel preenchido da esquerda para diereita.
Observe que a visualizao de ummax-heap como uma rvore binria nos permite vericar
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 lhos. Por conta dessa semelhana, podemos
descrever operaes bsicas que nos permitem percorrer o max-heap facilmente. As operaes
so implementadas como funes.
/
*
Recebe um ndice i em um max-heap e devolve o pai de i
*
/
int pai(int i)
{
if (i == 0)
return 0;
else
return (i - 1) / 2;
}
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. Arvore binria porque todo n tem, no mximo,
dois lhos. A denio formal de uma rvore binria foge ao escopo de Algoritmos e Programao II e ser vista
mais adiante.
FACOM UFMS
8.1 Heaps 55
/
*
Recebe um ndice i em um max-heap e devolve o filho esquerdo de i
*
/
int esquerdo(int i)
{
return 2
*
(i + 1) - 1;
}
/
*
Recebe um ndice i em um max-heap e devolve o filho direito de i
*
/
int direito(int i)
{
return 2
*
(i + 1);
}
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, denimos 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 log
2
n. Como veremos a seguir, as
operaes bsicas sobre heaps tm tempo de execuo proporcional altura da rvore binria,
ou seja, O(log
2
n).
8.1.1 Manuteno da propriedade max-heap
Seja um vetor de nmeros inteiros S com n > 0 elementos. Ainda que o vetor S no seja
um max-heap, vamos enxerg-lo do mesmo modo como zemos anteriormente, como uma
rvore binria com o ltimo nvel preenchido da esquerda para direita. Nesta seo, queremos
resolver o seguinte problema:
Seja um vetor de nmeros inteiros S com n > 0 elementos e um ndice i. Se S
visto como uma rvore binria, estabelea a propriedade max-heap (8.3) para a
sub-rvore de S com raiz S[i], supondo que as sub-rvores esquerda e direita do n
i de S so max-heaps.
A funo desce recebe um conjunto S de nmeros inteiros com n > 0 elementos e um n-
dice i. Se enxergamos S como uma rvore binria comseu ltimo nvel preenchido da esquerda
para direita, ento a funo desce verica a propriedade max-heap para a sub-rvore de S
com raiz S[i], dado que as sub-rvores com raiz S[ esquerdo(i) ] e S[ direito(i) ] em S so
max-heaps. Se a propriedade no satisfeita para S[i], a funo desce troca S[i] com o lho
que possui maior prioridade. Assim, a propriedade max-heap estabelecida para o n de n-
dice i. No entanto, essa troca pode ocasionar a violao da propriedade max-heap para um dos
lhos do n de ndice i. Ento, esse processo repetido recursivamente at que a propriedade
max-heap no seja mais violada. Dessa forma, a funo desce rearranja S adequadamente
de maneira que a sub-rvore com raiz S[i] torne-se um max-heap, descendo o elemento S[i]
para a sua posio correta em S.
FACOM UFMS
56 LISTAS DE PRIORIDADES
/
*
Recebe um nmero inteiro n > 0, um vetor S de nmeros in-
teiros com n elementos e um ndice i e estabelece a pro-
priedade 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);
}
}
A gura 8.3 ilustra um exemplo de execuo da funo desce .
26
14
9 11 8
6
1 2
3 4 5 6
7 8 9
0
4
26
14
9 11 8 16
6
1 2
3 4 5 6
7 8 9
0
12 4
7
26
14
9 11 8
6
1 2
3 4 5 6
7 8 9
0
12 4
16
7
16
12
7
(a) (b)
(c)
Figura 8.3: Um exemplo de execuo da funo desce com argumentos (10, S, 1).
FACOM UFMS
8.1 Heaps 57
O tempo de execuo de pior caso da funo desce proporcional altura da rvore
binria correspondente S, isto , proporcional log
2
n.
8.1.2 Construo de um max-heap
Seja um vetor S de nmeros inteiros com n > 0 elementos. Usando a funo desce ,
fcil transformar o vetor S em um max-heap. Se enxergamos S como uma rvore binria,
basta ento percorrer as sub-rvores de S com alturas crescentes, usando a funo desce para
garantir a propriedade max-heap para cada uma de suas razes. Podemos excluir as folhas de S,
j que toda rvore com altura 0 um max-heap. A funo constroi_max_heap apresentada
a seguir.
/
*
Recebe um nmero inteiro n > 0 e um vetor de nmeros
inteiros S com n elementos e rearranja o vetor S de
modo que o novo vetor S possua a propriedade max-heap
*
/
void constroi_max_heap(int n, int S[MAX])
{
int i;
for (i = n/2 - 1; i >= 0; i--)
desce(n, S, i);
}
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 log
2
n, onde n o nmero de elementos no vetor S. Deve-
mos 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 nlog
2
n. Apesar de correto, esse limitante superior para o tempo de execuo
da funo constroi_max_heap no muito justo. Isso signica que correto armar que a
funo T(n) que descreve o tempo da funo limitada superiormente pela funo cnlog
2
n
para todo n n
0
, onde c e n
0
so constantes positivas. Porm, existe outra funo, que tam-
bm limita superiormente a funo T(n), que menor que nlog
2
n. Podemos mostrar que
um limitante superior melhor pode ser obtido observando a variao nas alturas dos ns da r-
vore 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 gura 8.4 ilustra um exemplo de execuo da funo constroi_max_heap .
FACOM UFMS
58 LISTAS DE PRIORIDADES
1 2
3 4 5 6
7 8 9
0
9
4
26 14
12 11
18
6
8
16
1 2
3 4 5 6
7 8 9
0
9
4 8
26 16 14
12 11
18
6
1 2
3 4 5 6
7 8 9
0
9
4 8
6 26 16 14
18 12 11
1 2
3 4 5 6
7 8 9
0
9
14
12
18
6
8
16 26
11
4
0
6
1 2 3 4 5 6 7 8 9
4 8 26 16 14 18 12 11 9
1 2
3 5 6
7 8 9
0
9
4 8
6 26 16 14
18 12 11
4
1 2
3 4 5 6
7 8 9
0
14
6
8
16
11
4
12
18
9
26
(a)
(b)
(c) (d)
(e) (f)
i
i
i
i
i
S
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 cons-
trudas para obtermos um min-heap. Veja o exerccio 8.7.
8.1.3 Alterao de uma prioridade em um max-heap
Vamos retomar o vetor S de nmeros inteiros com n > 0 elementos. Suponha que S seja
um max-heap. Suponha, no entanto, que a prioridade S[i] seja modicada. Uma operao
como esta pode resultar em um novo vetor que deixa de satisfazer a propriedade max-heap
justamente na posio i de S. A gura 8.5 mostra os trs casos possveis quando uma alterao
de prioridade realizada em um max-heap S.
26
14
9 11 8 16
6
1 2
3 4 5 6
7 8 9
0
12 4
47
2 1 0 3 4 5 6 7 8 9
26
14
9 11 8 16
6
1 2
3 4 5 6
7 8 9
0
12 4
26
14
9 11 8 16
6
1 2
3 4 5 6
7 8 9
0
12 4
3 21
26 18 14 16 8 9 11 4 12 6
S
(a) (b)
(c)
Figura 8.5: Alterao na prioridade S[1] de um max-heap. (a) Alterao mantm a proprie-
dade 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 gura 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 lhos, ento essa nova prioridade deve descer na
rvore. Esse caso mostrado na gura 8.5(b) e resolvido facilmente usando a funo desce
FACOM UFMS
60 LISTAS DE PRIORIDADES
descrita na seo 8.1.1. Por m, 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 gura 8.5(c). Nesse caso, precisamos de uma funo eciente que resolva esse problema. A
funo sobe apresentada a seguir soluciona esse problema.
/
*
Recebe um nmero inteiro n > 0, um vetor S de n-
meros inteiros com n elementos e um ndice i e es-
tabelece 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);
}
}
A gura 8.6 ilustra um exemplo de execuo da funo sobe .
26
14
9 11 8
6
1 2
3 4 5 6
7 8 9
0
4
18
21
16
26
14
9 11 8 16
6
1 2
3 4 5 6
7 8 9
0
12 4
26
14
9 11 8
6
1 2
3 4 5 6
7 8 9
0
4
18 18
16
21
26
14
9 11 8
6
1 2
3 4 5 6
7 8 9
0
4 16
18
21
(a) (b)
(c) (d)
Figura 8.6: Um exemplo de execuo da funo sobe com argumentos (10, S, 8).
FACOM UFMS
8.2 LISTAS DE PRIORIDADES 61
Otempo de execuo de pior caso da funo sobe proporcional altura da rvore binria
correspondente S, isto , proporcional log
2
n.
8.2 Listas de prioridades
Uma lista de prioridades uma estrutura de dados que ocorre muito freqentemente em
diversas aplicaes. Por exemplo, no escalonamento de tarefas em um computador ou em um
simulador de eventos, as listas de prioridades so empregadas commuito sucesso. No primeiro
exemplo, devemos manter uma lista dos processos a serem executados pelo computador com
suas respectivas prioridades, selecionando sempre aquele de mais alta prioridade assim que
um processador se torna ocioso ou disponvel. No segundo exemplo, uma lista de eventos a
serem simulados so colocados em uma lista de prioridades, onde seus tempos de ocorrncia
so as prioridades na lista, e os eventos devem ser simulados na ordem de seus tempos de
ocorrncia.
Do mesmo modo como com os heaps, existem dois tipos de listas de prioridades: listas
de max-prioridades e listas de min-prioridades. Os exemplos que acabamos de descrever so
associados a uma lista de max-prioridades e uma lista de min-prioridades, respectivamente.
Assim como antes, focaremos ateno nas listas de max-prioridades, deixando as outras para
os exerccios.
Uma lista de max-prioridades uma estrutura de dados para manuteno de um conjunto
de elementos S, onde a cada elemento est associada uma prioridade. As seguintes operaes
so associadas a uma lista de max-prioridades:
(1) insero de um elemento no conjunto S;
(2) consulta da maior prioridade em S;
(3) remoo do elemento de maior prioridade em S;
(4) aumento da prioridade de um elemento de S.
fcil ver que um max-heap pode ser usado para implementar uma lista de max-
prioridades. A funo consulta_maxima implementa a operao (2) em tempo de execuo
constante, isto , O(1).
/
*
Recebe uma lista de max-prioridades S e devolve a maior prioridade em S
*
/
int consulta_maxima(int S[MAX])
{
return S[0];
}
A funo extrai_maxima implementa a operao (3) usando a funo desce , devol-
vendo 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 LISTAS DE PRIORIDADES
/
*
Recebe um nmero inteiro n > 0 e uma lista de max-priorida-
des 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 ;
}
O tempo de execuo da funo extrai_maxima na verdade o tempo gasto pela funo
desce . Portanto, seu tempo de execuo proporcional a log
2
n.
A funo aumenta_prioridade implementa a operao (4), recebendo uma lista de max-
prioridades, uma nova prioridade e um ndice e devolve a lista de max-prioridades com a
prioridade alterada.
/
*
Recebe um nmero inteiro n > 0, uma lista de max-priorida-
des 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);
}
}
O tempo de execuo da funo aumenta_prioridade o tempo gasto pela chamada
funo sobe e, portanto, proporcional a log
2
n.
Por m, a operao (1) implementada pela funo insere_lista que recebe uma lista de
max-prioridades e uma nova prioridade e insere essa prioridade na lista de max-prioridades.
/
*
Recebe um nmero inteiro n > 0, uma lista de max-prioridades S e uma prio-
ridade 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 ORDENAO USANDO UM MAX-HEAP 63
O tempo de execuo da funo insere_lista o tempo gasto pela chamada funo
sobe e, portanto, proporcional a log
2
n.
8.3 Ordenao usando um max-heap
Um max-heap pode ser naturalmente usado para descrever um algoritmo de ordenao
eciente. Esse algoritmo conhecido como heapsort e tem o mesmo tempo de execuo de pior
caso da ordenao por intercalao e do caso mdio da ordenao por separao.
/
*
Recebe um nmero inteiro n > 0 e um vetor S de nmeros inteiros com
n elementos e rearranja S em ordem crescente usando um max-heap
*
/
void heapsort(int n, int S[MAX])
{
int i;
constroi_max_heap(n, S);
for (i = n - 1; i > 0; i--) {
troca(&S[0], &S[i]);
n--;
desce(n, S, 0);
}
}
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..n1] contmos ni maiores elementos de S[0..n1] em ordemcrescente.
O tempo de execuo da funo heapsort proporcional a nlog
2
n. Note que a chamada
da funo constroi_max_heap gasta tempo proporcional a n e cada uma das n1 chamadas
funo desce gasta tempo proporcional a log
2
n.
Exerccios
Os exerccios foram extrados do livro de Cormen et. al [1].
8.1 A seqncia 23, 17, 14, 6, 13, 10, 1, 5, 7, 12 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, consi-
derando que todos os elementos so distintos?
FACOM UFMS
64 LISTAS DE PRIORIDADES
8.5 Um vetor em ordem crescente um min-heap?
8.6 Use como base a gura 8.3 e ilustre a execuo da funo desce(14, S, 2) sobre o
vetor S = 27, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0.
8.7 Suponha que voc deseja manter ummin-heap. Escreva uma funo equivalente funo
desce para um max-heap, que mantm a propriedade min-heap (8.2).
8.8 Qual o efeito de chamar desce(n, S, i) quando a prioridade S[i] maior que as prio-
ridades de seus lhos?
8.9 Qual o efeito de chamar desce(n, S, i) para i n/2?
8.10 O cdigo da funo desce muito eciente em termos de fatores constantes, exceto
possivelmente pela chamada recursiva que pode fazer comque alguns compiladores pro-
duzam um cdigo ineciente. Escreva uma funo no-recursiva eciente equivalente
funo desce .
8.11 Use como base a gura 8.4 e ilustre a operao da funo constroi_max_heap sobre o
vetor S = 5, 3, 17, 10, 84, 19, 6, 22, 9.
8.12 Por que fazemos com que a estrutura de repetio da funo constroi_max_heap con-
trolada por i seja decrescente de n/2 1 at 0 ao invs de crescente de 0 at n/2 1?
8.13 Use como exemplo a gura 8.3 e ilustre a operao da funo extrai_maximo sobre o
vetor S = 15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1.
8.14 Ilustre a operao da funo insere_lista(12, S, 9) sobre a lista de prioridades
S = 15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1.
8.15 Escreva cdigos ecientes e corretos para as funes que implementam as operaes
consulta_minimo , extrai_minimo , diminui_prioridade e insere_lista_min .
Essas funes devem implementar uma lista de min-prioridades com um min-heap.
8.16 A operao remove_lista(&n, S, i) remove a prioridade do n i de uma lista de
max-prioridades. Escreva uma funo eciente para remove_lista .
8.17 Ilustre a execuo da funo heapsort sobre o vetor S = 5, 13, 2, 25, 7, 17, 20, 8, 4.
FACOM UFMS
AULA 9
INTRODUO AOS PONTEIROS
Ponteiros ou apontadores, do ingls pointers, so certamente uma das caractersticas mais
destacveis da linguagem de programao C. Os ponteiros agregam poder e exibilidade
linguagem de maneira a diferenci-la de outras linguagens de programao de alto nvel, per-
mitindo a representao de estruturas de dados complexas, a modicao de valores passados
como argumentos a funes, alocao dinmica de espaos na memria, entre outros desta-
ques. Nesta aula iniciaremos o contato com esses elementos.
Esta aula baseada especialmente nas referncias [2, 7].
9.1 Variveis ponteiros
Para bem compreender os ponteiros, precisamos inicialmente compreender a idia de in-
direo. Conceitualmente, um ponteiro permite acesso indireto a um valor armazenado em
algum ponto da memria. Esse acesso realizado justamente porque um ponteiro uma va-
rivel que armazena um valor especial, que um endereo de memria, e por isso nos permite
acessar indiretamente o valor armazenado nesse endereo.
Amemria de umcomputador pode ser vista como constituda de muitas posies (ou com-
partimentos ou clulas), dispostas continuamente, cada qual podendo armazenar um valor, na
base binria. Ou seja, a memria nada mais do que um grande vetor que pode armazenar
valores na base binria e que, por sua vez, esses valores podem ser interpretados como valores
de diversos tipos. Os ndices desse grande vetor, numerados seqencialmente a partir de 0
(zero), so chamados de endereos de memria.
Quando escrevemos um programa em uma linguagem de programao de alto nvel, o
nome ou identicador de uma varivel associado diretamente a um ndice desse vetor, isto
, a um endereo da memria. A traduo do nome da varivel para um endereo de mem-
ria, e vice-versa, feita de forma automtica e transparente pelo compilador da linguagem de
programao. Essa uma caracterstica marcante de uma linguagem de programao de alto
nvel, que se diferencia das linguagens de programao de baixo nvel, j que no h neces-
sidade de um(a) programador(a) preocupar-se com os endereos de memria quando arma-
zena/recupera dados na memria.
Em geral, a memria de um computador dividida em bytes, com cada byte sendo capaz
de armazenar 8 bits de informao. Cada byte tem um nico endereo que o distingue de
outros bytes da memria. Se existem n bytes na memria, podemos pensar nos endereos
como nmeros de intervalo de 0 a n 1, como mostra a gura 9.1.
65
66 INTRODUO AOS PONTEIROS
endereo contedo
0
1
2
3
n 1
00010011
11010101
00111000
10010010
00001111
Figura 9.1: Uma ilustrao da memria e de seus endereos.
Um programa executvel constitudo por trechos de cdigo e dados, ou seja, por ins-
trues de mquina que correspondem s sentenas no programa original na linguagem C e
tambm de variveis. Cada varivel do programa ocupa um ou mais bytes na memria. O
endereo do primeiro byte de uma varivel dito ser o endereo da varivel. Na gura 9.2, a
varivel i ocupa os bytes dos endereos 2000 e 2001. Logo, o endereo da varivel i 2000.
i
2000
2001
Figura 9.2: Endereo da varivel i.
Os ponteiros tmumpapel importante emtoda essa histria. Apesar de os endereos serem
representados por nmeros, como ilustrado nas guras 9.1 e 9.2, o intervalo de valores que
esses objetos podem assumir diferente do intervalo que os nmeros inteiros, por exemplo,
podem assumir. Isso signica, entre outras coisas, que no podemos armazenar endereos em
variveis do tipo inteiro. Endereos so armazenados em variveis especiais, chamadas de
variveis ponteiros.
Quando armazenamos o endereo de uma varivel i em uma varivel ponteiro p, dizemos
que p aponta para i. Em outras palavras, um ponteiro nada mais que um endereo e uma
varivel ponteiro uma varivel que pode armazenar endereos.
Ao invs de mostrar endereos como nmeros, usaremos uma notao simplicada de tal
forma que, para indicar que uma varivel ponteiro p armazena o endereo de uma varivel
i, mostraremos o contedo de p um endereo como uma echa orientada na direo de i,
como mostra a gura 9.3.
FACOM UFMS
9.2 OPERADORES DE ENDEREAMENTO E DE INDIREO 67
p i
Figura 9.3: Representao de uma varivel ponteiro.
Uma varivel ponteiro pode ser declarada da mesma maneira que uma varivel qualquer
de qualquer tipo, como sempre temos feito, mas com um asterisco precedendo seu identica-
dor. Por exemplo,
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
particular, chamado de tipo referenciado.
Diante do exposto at este ponto, daqui para frente no mais distinguiremos os termos
ponteiro e varivel ponteiro, cando ento subentendido o seu valor (contedo) e a prpria
varivel.
9.2 Operadores de endereamento e de indireo
A linguagem C possui dois operadores para uso especco com ponteiros. Para obter o
endereo de uma varivel, usamos o operador de endereamento (ou de endereo) &. Se v
uma varivel, ento &v seu endereo na memria. Para ter acesso ao objeto que um ponteiro
aponta, temos de usar o operador de indireo
*
. Se p um ponteiro, ento
*
p representa o
objeto para o qual p aponta no momento.
A declarao de uma varivel ponteiro reserva um espao na memria para um ponteiro
mas no a faz apontar para um objeto. Assim, crucial inicializar um ponteiro antes de us-lo.
Uma forma de inicializar um ponteiro atribuir-lhe o endereo de alguma varivel usando o
operador &:
int i,
*
p;
p = &i;
Atribuir o endereo da varivel i para a varivel p faz comque p aponte para i, como ilustra-
a gura 9.4.
? p i
Figura 9.4: Varivel ponteiro p contendo o endereo da varivel i.
possvel inicializar uma varivel ponteiro no momento de sua declarao, como abaixo:
FACOM UFMS
68 INTRODUO 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, pode-
mos 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 opera-
dor
*
para um ponteiro retoma o valor original da varivel:
j =
*
&i;
Na verdade, a atribuio acima idntica seguinte:
j = i;
Enquanto dizemos que p aponta para i, dizemos tambm que
*
p um apelido para i. Ade-
mais, 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 traduzi-
los desta forma para ns 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 umponteiro na linguagem C no temsignicado at que contenha, ou aponte,
para algum endereo vlido.
Aprimeira 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 especicador 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
tambmaquele 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 OPERADORES DE ENDEREAMENTO E DE INDIREO 69
Programa 9.1: Um exemplo do uso de ponteiros.
#include <stdio.h>
int main(void)
{
char c,
*
p;
p = &c;
c = a;
printf("&c = %p c = %c\n", &c, c);
printf("&p = %p p = %p
*
p = %c\n\n", &p, p,
*
p);
c = /;
printf("&c = %p c = %c\n", &c, c);
printf("&p = %p p = %p
*
p = %c\n\n", &p, p,
*
p);
*
p = Z;
printf("&c = %p c = %c\n", &c, c);
printf("&p = %p p = %p
*
p = %c\n\n", &p, p,
*
p);
return 0;
}
apresentado na sada quando solicitamos a impresso de
*
p . importante notar que, a menos
que o contedo da varivel p seja modicado, a expresso
*
p sempre acessa o contedo da
varivel c. Por m, o ltimo conjunto de chamadas funo printf precedido de uma
atribuio que modica o contedo da varivel apontada por p e conseqentemente da varivel
c. Ou seja, a atribuio a seguir:
*
p = Z;
faz tambm com que a varivel c receba o caractere Z .
A execuo do programa 9.1 emumcomputador comprocessador de 64 bits temo seguinte
resultado na sada:
&c = 0x7fffffffc76f c = a
&p = 0x7fffffffc760 p = 0x7fffffffc76f
*
p = a
&c = 0x7fffffffc76f c = /
&p = 0x7fffffffc760 p = 0x7fffffffc76f
*
p = /
&c = 0x7fffffffc76f c = Z
&p = 0x7fffffffc760 p = 0x7fffffffc76f
*
p = Z
Observe que um endereo de memria impresso na base hexadecimal com o especicador de
tipo %p . A gura 9.5 ilustra o incio da execuo do programa 9.1.
FACOM UFMS
70 INTRODUO AOS PONTEIROS
p p i i a a
0x7fffffffc76f
0x7fffffffc76f
0x7fffffffc760
(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:
int i, j,
*
p,
*
q;
Ento, a sentena:
p = &i;
um exemplo de atribuio de um ponteiro, onde o endereo de i copiado em p. Um outro
exemplo de atribuio de ponteiro dado a seguir:
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 gura 9.6.
?
p
q
i
Figura 9.6: Dois ponteiros para um mesmo endereo.
Ambos os ponteiros p e q apontam para i e, assim, podemos modicar o contedo de i
indiretamente atravs da atribuio de valores para
*
p e
*
q.
9.3 Ponteiros em expresses
Ponteiros podem ser usados em expresses aritmticas de mesmo tipo que seus tipos refe-
renciados. Por exemplo, umponteiro para uma varivel do tipo inteiro pode ser usado emuma
FACOM UFMS
9.3 PONTEIROS 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, emuma 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, &i = %p\n\n", i, &i);
printf("j = %d, &j = %p\n\n", j, &j);
printf("&ptr1 = %p, ptr1 = %p,
*
ptr1 = %d\n", &ptr1, ptr1,
*
ptr1);
printf("&ptr2 = %p, ptr2 = %p,
*
ptr2 = %d\n\n", &ptr2, ptr2,
*
ptr2);
return 0;
}
A execuo do programa 9.2 tem a seguinte sada:
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 INTRODUO 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 PONTEIROS EM EXPRESSES 73
Programa 9.4: Programa do exerccio 9.4.
#include <stdio.h>
int main(void)
{
int a, b, c,
*
ptr;
a = 3;
b = 7;
printf("a=%d, b=%d\n", a, b);
ptr = &a;
c =
*
ptr;
ptr = &b;
a =
*
ptr;
ptr = &c;
b =
*
ptr;
printf("a=%d, b=%d\n", a, b);
return 0;
}
Programa 9.5: Programa do exerccio 9.5.
#include <stdio.h>
int main(void)
{
int i, j,
*
p,
*
q;
p = &i;
q = p;
*
p = 1;
printf("i=%d,
*
p=%d,
*
q=%d\n", i,
*
p,
*
q);
q = &j;
i = 6;
*
q =
*
p;
printf("i=%d, j=%d,
*
p=%d,
*
q=%d\n", i, j,
*
p,
*
q);
return 0;
}
FACOM UFMS
74 INTRODUO AOS PONTEIROS
FACOM UFMS
AULA 10
PROGRAMAS EXTENSOS
Nesta aula aprenderemos como dividir e distribuir nossas funes emvrios arquivos. Esta
possibilidade uma caracterstica importante da linguagem C, permitindo que o(a) programa-
dor(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 peque-
nos, 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 arquivo
1
. Nesta aula vere-
mos 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, umprograma pode ser dividido emqualquer nmero de arquivos-
fontes que, por conveno, tm a extenso .c . Cada arquivo-fonte contm uma parte do pro-
grama, em geral denies 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 van-
tagens signicativas:
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 modicado 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 modic-lo a seu gosto.
75
76 PROGRAMAS EXTENSOS
10.2 Arquivos-cabealhos
Quando abordamos, em aulas anteriores, a biblioteca padro e o pr-processador da lin-
guagem C, estudamos com algum detalhe os arquivos-cabealhos. Quando dividimos um
programa em vrios arquivos-fontes, como uma funo denida em um arquivo pode cha-
mar uma outra funo denida em outro arquivo? Como dois arquivos podem compartilhar
a denio de uma mesma macro ou a denio 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 denies de macros e denies de tipos que necessi-
tam ser compartilhadas por vrios arquivos-fontes. Essas denies 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 denies 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 denies deve conter simplesmente a linha a
seguir:
#include "logico.h"
Denies de tipos tambm so comuns em arquivos-cabealhos. Por exemplo, ao invs de
denir a macro LOGIC acima, podemos usar typedef para criar um tipo logic . Assim, o
arquivo logico.h ter as seguintes linhas:
#define VERDADEIRO 1
#define FALSO 0
typedef logic int;
A gura 10.1 mostra um exemplo de dois arquivos-fontes que incluem o arquivo-cabealho
logico.h .
Colocar denies de macros e tipos emumarquivo-cabealho temalgumas vantagens. Pri-
meiro, economizamos tempo por no ter de copiar as denies nos arquivos-fontes onde so
necessrias. Segundo, o programa torna-se muito mais fcil de modicar, j que a modicao
da denio de uma macro ou de um tipo necessita ser feita emumnico arquivo-cabealho. E
terceiro, no temos de nos preocupar com inconsistncias em conseqncia de arquivos-fontes
contendo denies diferentes da mesma macro ou tipo.
FACOM UFMS
10.2 ARQUIVOS-CABEALHOS 77
#define VERDADEIRO 1
#define FALSO 0
#define LOGIC int
logic.h
#include "logic.h" #include "logic.h"
Figura 10.1: Incluso de um arquivo-cabealho em dois arquivos-fontes.
Suponha agora que um arquivo-fonte contm uma chamada a uma funo soma que est
denida emumoutro arquivo-fonte, comnome calculos.c . Chamar a funo soma semsua
declarao pode ocasionar erros de execuo. Quando chamamos uma funo que est denida
em outro arquivo, sempre importante ter certeza que o compilador viu sua declarao, isto ,
seu prottipo, antes dessa chamada.
Nosso primeiro impulso declarar a funo soma no arquivo-fonte onde ela foi chamada.
Isso resolve o problema, mas pode criar imensos problemas de gerenciamento. Suponha que
a funo chamada em cinqenta arquivos-fontes diferentes. Como podemos assegurar que
os prottipos das funes soma so idnticos em todos esses arquivos? Como podemos ga-
rantir que todos esses prottipos correspondem denio de soma em calculos.c ? Se
soma deve ser modicada posteriormente, como podemos encontrar todos os arquivos-fontes
onde ela usada? A soluo evidente: coloque o prottipo da funo soma em um arquivo-
cabealho e inclua ento esse arquivo-cabealho em todos os lugares onde soma chamada.
Como soma denida em calculos.c , um nome natural para esse arquivo-cabealho
calculos.h . Alm de incluir calculos.h nos arquivos-fontes onde soma chamada, pre-
cisamos inclu-lo em calculos.c tambm, permitindo que o compilador verique que o pro-
ttipo de soma em calculos.h corresponde sua denio em calculos.c . regra sem-
pre incluir o arquivo-cabealho que declara uma funo emumarquivo-fonte que contma de-
nio dessa funo. No faz-lo pode ocasionar erros difceis de encontrar. Se calculos.c
contm outras funes, muitas delas devem ser declaradas no mesmo arquivo-cabealho onde
foi declarada a funo soma . Mesmo porque, as outras funes em calculos.c so de al-
guma forma relacionadas com soma . Ou seja, qualquer arquivo que contenha uma chamada
soma provavelmente necessita de alguma das outras funes em calculos.c . Funes cuja
inteno seja us-las apenas como suporte dentro de calculos.c no devem ser declaradas
em um arquivo-cabealho.
FACOM UFMS
78 PROGRAMAS EXTENSOS
Para ilustrar o uso de prottipos de funes em arquivos-cabealhos, vamos supor que
queremos manter diversas denies de funes relacionadas a clculos geomtricos em um
arquivo-fonte geometricas.c . As funes so as seguintes:
double perimetroQuadrado(double lado)
{
return 4
*
lado;
}
double perimetroTriangulo(double lado1, double lado2, double lado3)
{
return lado1 + lado2 + lado3;
}
double perimetroCirculo(double raio)
{
return 2
*
PI
*
raio;
}
double areaQuadrado(double lado)
{
return lado
*
lado;
}
double areaTriangulo(double base, double altura)
{
return base
*
altura / 2;
}
double areaCirculo(double raio)
{
return PI
*
raio
*
raio;
}
double volumeCubo(double lado)
{
return lado
*
lado
*
lado;
}
double volumeTetraedro(double lado, double altura)
{
return (double)1/3
*
areaTriangulo(lado, lado
*
sqrt(altura) / 2)
*
altura;
}
double volumeEsfera(double raio)
{
return (double)4/3
*
PI
*
raio
*
raio;
}
Os prottipos dessas funes, alm de uma denio de uma macro, sero mantidos em
um arquivo-cabealho com nome geometricas.h :
FACOM UFMS
10.2 ARQUIVOS-CABEALHOS 79
double perimetroQuadrado(double lado);
double perimetroCirculo(double raio);
double perimetroTriangulo(double lado1, double lado2, double lado3);
double areaQuadrado(double lado);
double areaCirculo(double raio);
double areaTriangulo(double base, double altura);
double volumeCubo(double lado);
double volumeEsfera(double raio);
double volumeTetraedro(double lado, double altura);
Alm desses prottipos, a macro PI tambm deve ser denida neste arquivo. Ento, um
arquivo-fonte calc.c que calcula medidas de guras geomtricas e que contm a funo
main pode ser construdo. A gura 10.2 ilustra essa diviso.
#define PI 3.141592
double perimetroQuadrado(double lado);
double perimetroCirculo(double raio);
double volumeTetraedro(double lado, double altura);
geometricas.h
#include "geometricas.h" #include "geometricas.h"
int main(void)
{
{
{
}
}
}
x = areaCirculo(r);
double perimetroQuadrado(double lado)
double perimetroCirculo(double raio)
calc.c
geometricas.c
Figura 10.2: Relao entre prottipos, arquivo-fonte e arquivo-cabealho.
FACOM UFMS
80 PROGRAMAS EXTENSOS
10.3 Diviso de programas em arquivos
Como vimos na seo anterior, podemos dividir nossos programas em diversos arquivos
que possuem uma conexo lgica entre si. Usaremos agora o que conhecemos sobre arquivos-
cabealhos e arquivos-fontes para descrever uma tcnica simples de dividir um programa em
arquivos. Consideramos aqui que o programa j foi projetado, isto , j decidimos quais fun-
es o programa necessita e como arranj-las em grupos logicamente relacionados.
Cada conjunto de funes relacionadas sempre colocado em um arquivo-fonte em sepa-
rado. geometricas.c da seo anterior um exemplo de arquivo-fonte desse tipo. Alm
disso, criamos um arquivo-cabealho com o mesmo nome do arquivo-fonte, mas com exten-
so .h . O arquivo-cabealho geometricas.h da seo anterior um outro exemplo. Nesse
arquivo-cabealho, colocamos os prottipos das funes includas no arquivo-fonte, lembrando
que as funes que so projetadas somente para dar suporte s funes do arquivo-fonte no
devem ter seus prottipos descritos no arquivo-cabealho. Ento, devemos incluir o arquivo-
cabealho em cada arquivo-fonte que necessite chamar uma funo denida no arquivo-fonte.
Alm disso, inclumos o arquivo-cabealho no prprio arquivo-fonte para que o compilador
possa vericar que os prottipos das funes no arquivo-cabealho so consistentes com as
denies do arquivo-fonte. Mais uma vez, esse o caso do exemplo da seo anterior, com
arquivo-fonte geometricas.c e arquivo-cabealho geometricas.h . Veja novamente a -
gura 10.2.
A funo principal main deve ser includa em um arquivo-fonte que tem um nome re-
presentando o nome do programa. Por exemplo, se queremos que o programa seja conhecido
como calc ento a funo main deve estar em um arquivo-fonte com nome calc.c .
possvel que existam outras funes no mesmo arquivo-fonte onde main se encontra, funes
essas que no so chamadas em outros arquivos-fontes do programa.
Para gerar um arquivo-executvel de um programa dividido emmltiplos arquivos-fontes,
os mesmos passos bsicos que usamos para um programa em um nico arquivo-fonte so
necessrios:
compilao: cada arquivo-fonte do programa deve ser compilado separadamente; para
cada arquivo-fonte, o compilador gera um arquivo contendo cdigo objeto, que tm ex-
tenso .o ;
ligao: o ligador combina os arquivos-objetos criados na fase compilao, juntamente
com cdigo das funes da biblioteca, para produzir um arquivo-executvel.
Muitos compiladores nos permitem construir um programa em um nico passo. Com o
compilador GCC, usamos o seguinte comando para construir o programa calc da seo ante-
rior:
gcc -o calc calc.c geometricas.c
Os dois arquivos-fontes so primeiro compilados em arquivos-objetos. Esse arquivos-
objetos so automaticamente passados para o ligador que os combina em um nico arquivo. A
opo -o especica que queremos que nosso arquivo-executvel tenha o nome calc .
FACOM UFMS
10.3 DIVISO DE PROGRAMAS EM ARQUIVOS 81
Digitar os nomes de todos os arquivos-fontes na linha de comando de uma janela de um
terminal logo torna-se uma tarefa tediosa. Alm disso, podemos desperdiar uma quantidade
de tempo quando reconstrumos um programa se sempre recompilamos todos os arquivos-
fontes, no apenas aqueles que so afetados pelas nossas modicaes mais recentes.
Para facilitar a construo de grandes programas, o conceito de makeles foi proposto nos
primrdios da criao do sistema operacional UNIX. Um makele um arquivo que contm
informao necessria para construir um programa. Um makele no apenas lista os arquivos
que fazem parte do programa, mas tambm descreve as dependncias entre os arquivos. Por
exemplo, da seo anterior, como calc.c inclui o arquivo geometricas.h , dizemos que
calc.c depende de geometricas.h , j que uma mudana em geometricas.h far com
que seja necessria a recompilao de calc.c .
Abaixo, listamos um makele para o programa calc .
calc: calc.o geometricas.o
gcc -o calc calc.o geometricas.o -lm
calc.o: calc.c geometricas.h
gcc -c calc.c -lm
geometricas.o: geometricas.c geometricas.h
gcc -c geometricas.c -lm
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:
calc: calc.o geometricas.o
gcc -o calc calc.o geometricas.o -lm
A primeira linha dessa regra estabelece que calc depende dos arquivos calc.o e
geometricas.o . Se qualquer um desses dois arquivos foi modicado desde da ltima cons-
truo do programa, ento calc precisa ser reconstrudo. Ocomando 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:
calc.o: calc.c geometricas.h
gcc -c calc.c -lm
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 PROGRAMAS EXTENSOS
Tendo criado um makele para um programa, podemos usar o utilitrio make para cons-
truir ou reconstruir o programa. vericando a data e a hora associada com cada arquivo do
programa, make determina quais arquivos esto desatualizados. Ento, ele invoca os coman-
dos necessrios para reconstruir o programa.
Algumas dicas para criar makeles seguem abaixo:
cada comando emummakele deve ser precedido por umcaractere de tabulao horizon-
tal TAB ;
um makele armazenado em um arquivo com nome Makefile ; quando o utilitrio
make usado, ele automaticamente verica o contedo do diretrio atual buscando por
esse arquivo;
use
make alvo
onde alvo um dos alvos listados no makele; se nenhum alvo especicado, make
construir o alvo da primeira regra.
O utilitrio make complicado o suciente para existiremdezenas de livros e manuais que
nos ensinam a us-lo. Com as informaes desta aula, temos as informaes bsicas necess-
rias 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 (a) Escreva uma funo com a seguinte interface:
void preenche_aleatorio(int n, int v[MAX])
que receba um nmero inteiro n, com 0 < 0 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 umarquivo-fonte comtodas as funes de ordenao que vimos nas aulas 5, 6, 7
e 8. Crie tambm um arquivo-cabealho correspondente.
(c) Escreva umprograma que receba um inteiro n, com 1 n 10000, gere umseqn-
cia de n nmeros aleatrios e execute os mtodos de ordenao que conhecemos so-
bre esse vetor, medindo seu tempo de execuo. Use as funes clock e difftime
da biblioteca time .
(d) Crie um makele para compilar e ligar seu programa.
FACOM UFMS
AULA 11
PONTEIROS E FUNES
Nesta aula revemos ponteiros e funes na linguagem C. At o momento, aprendemos al-
gumas regras de como construir funes que tmparmetros de entrada e sada ou argumen-
tos 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 reexos externos funo, no argumento
correspondente. Esta aula baseada especialmente na referncia [7].
11.1 Parmetros de entrada e sada?
A abstrao que zemos antes para compreendermos o que so parmetros de entrada e
sada na linguagem C ser revista agora e revelar algumas novidades. Veja o programa 11.1.
Programa 11.1: Exemplo de parmetros de entrada e sada e ponteiros.
#include <stdio.h>
/
*
Recebe dois valores e devolve os mesmos valores com contedos trocados
*
/
void troca(int
*
a, int
*
b)
{
int aux;
aux =
*
a;
*
a =
*
b;
*
b = aux;
}
/
*
Recebe dois valores e troca seus contedos
*
/
int main(void)
{
int x, y;
scanf("%d%d", &x, &y);
printf("Antes da troca : x = %d e y = %d\n", x, y);
troca(&x, &y);
printf("Depois da troca: x = %d e y = %d\n", x, y);
return 0;
}
83
84 PONTEIROS 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 gura 11.1.
? ? x y
Figura 11.1: Execuo da linha 11.
Em seguida, suponha que o(a) usurio(a) do programa informe dois valores quaisquer do
tipo inteiro como, por exemplo, 3 e 8. Isso se reete na memria como na gura 11.2.
x y 3 8
Figura 11.2: Execuo da linha 13.
Na linha seguinte da funo main , a execuo da funo printf permite que o(a) usu-
rio(a) verique na sada os contedos das variveis que acabou de informar. Na prxima linha
do programa, a funo troca chamada com os endereos das variveis x e y como argu-
mentos. Isto , esses endereos so copiados nos argumentos correspondentes que compem
a interface da funo. O uxo de execuo do programa ento desviado para o trecho de
cdigo da funo troca . Os parmetros da funo troca so dois ponteiros para valores
do tipo inteiro com identicadores a e b . Esses dois parmetros recebem os endereos das
variveis x e y da funo main , respectivamente, como se pode observar na gura 11.3.
x y a b 3 8
Figura 11.3: Execuo da linha 15.
A seguir, na primeira linha do corpo da funo troca , ocorre a declarao da varivel
aux e um espao identicado por aux reservado na memria principal, como podemos ver
na gura 11.4.
?
x y a b 3 8
aux
Figura 11.4: Execuo da linha 4.
FACOM 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 gura 11.5.
x y a b
3
3 8
aux
Figura 11.5: Execuo da linha 5.
Na execuo da linha seguinte, o contedo da varivel apontada por a recebe o contedo
da varivel apontada por b , como mostra a gura 11.6.
x y a b
3
8 8
aux
Figura 11.6: Execuo da linha 6.
Por m, na execuo da prxima linha, o contedo da varivel apontada por b recebe o
contedo da varivel aux , conforme a gura 11.7.
x y a b 3
3
8
aux
Figura 11.7: Execuo da linha 7.
Aps o trmino da funo troca , seus parmetros e variveis locais so destrudos e o
uxo de execuo volta para a funo main , no ponto logo aps onde foi feita a chamada da
funo troca . A memria neste momento encontra-se no estado ilustrado na gura 11.8.
x y 3 8
Figura 11.8: Estado da memria aps o trmino da funo troca.
FACOM UFMS
86 PONTEIROS E FUNES
Na linha seguinte da funo main a chamada da funo printf , que mostra os con-
tedo das variveis x e y , que foram trocados, conforme j constatado e ilustrado na -
gura 11.8. O programa ento salta para a prxima linha e chega ao m 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 cou
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 passagemde umargumento por referncia usando ponteiros. Assim, passando
(por cpia) o endereo de uma varivel como argumento para uma funo, o parmetro corres-
pondente deve ser um ponteiro e, mais que isso, um ponteiro para a varivel correspondente
cujo endereo foi passado como argumento. Dessa forma, qualquer modicao indireta rea-
lizada 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 passagemde argumentos por cpia na
linguagem C.
11.2 Devoluo de ponteiros
Alm de passar ponteiros como argumentos para funes tambmpodemos faz-las devol-
ver ponteiros. Funes como essas so comuns, por exemplo, quando tratamos de cadeias de
caracteres conforme veremos na aula 14.
A funo abaixo recebe dois ponteiros para nmeros inteiros e devolve um ponteiro para
um maior dos nmeros inteiros, isto , devolve o endereo onde se encontra um maior dos
nmeros.
/
*
Recebe dois valores e devolve o endereo de um maior
*
/
int
*
max(int
*
a, int
*
b)
{
if (
*
a >
*
b)
return a;
else
return b;
}
FACOM UFMS
11.2 DEVOLUO 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);
Na execuo da funo max , temos que
*
a um apelido para i e
*
b um apelido para
j . Se i tem valor maior que j , ento max devolve o endereo de i . Caso contrrio, max
devolve o endereo de j . Depois da chamada, p aponta para i ou para j .
No possvel que uma funo devolva o endereo de uma varivel local sua, j que ao
nal de sua execuo, essa varivel ser destruda.
Exerccios
11.1 (a) Escreva uma funo com a seguinte interface:
void min_max(int n, int v[MAX], int
*
max, int
*
min)
que receba um nmero inteiro n, com 1 n 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 emum 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 (a) Escreva uma funo com a seguinte interface:
void dois_maiores(int n, int v[MAX], int
*
p_maior, int
*
s_maior)
que receba um nmero intero n, com 1 n 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 emum 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 (a) Escreva uma funo com a seguinte interface:
void soma_prod(int a, int b, int
*
soma, int
*
prod)
FACOM UFMS
88 PONTEIROS 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 (a) Escreva uma funo com a seguinte interface:
int
*
maximo(int n, int v[MAX])
que receba um nmero intero n, com 1 n 100, e um vetor v de n nmeros
inteiros e devolva o endereo do elemento de v onde reside um maior elemento de
v.
(b) Escreva um programa que receba n > 0 nmeros inteiros, armazene-os emum vetor
e, usando a funo do item (a), mostre na sada um maior elemento desse conjunto.
Simule no papel a execuo de seu programa antes de implement-lo.
FACOM UFMS
AULA 12
PONTEIROS E VETORES
Nas aulas 9 e 11 aprendemos o que so os ponteiros e tambm como so usados como argu-
mentos/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 subtra-
o 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 lingua-
gem 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 ecincia do programa
executvel resultante. Esta aula baseada nas referncias [2, 7].
12.1 Aritmtica com ponteiros
Nas aulas 9 e 11 vimos que ponteiros podemapontar para elementos de umvetor. Suponha,
por exemplo, que temos declaradas as seguintes variveis:
int v[10],
*
p;
Podemos fazer o ponteiro p apontar para o primeiro elemento do vetor v fazendo a seguinte
atribuio, como mostra a gura 12.1:
p = &v[0];
0 1 2 3 4 5 6 7 8 9
p
v
Figura 12.1: p = &v[0];
89
90 PONTEIROS E VETORES
Podemos acessar o primeiro compartimento de v atravs de p, como ilustrado na gura 12.2.
0 1 2 3 4 5 6 7 8 9
5
p
v
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 arit-
mtica 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;
Adicionar um inteiro j a um ponteiro p fornece um ponteiro para o elemento posicionado
j posies aps p. Mais precisamente, se p aponta para o elemento v[i] , ento p + j aponta
para v[i + j] . A gura 12.3 ilustra essa idia.
Do mesmo modo, se p aponta para o elemento v[i] , ento p j aponta para v[i j] ,
como ilustrado na gura 12.4.
Ainda, quando um ponteiro subtrado de outro, o resultado a distncia, medida em
elementos do vetor, entre os ponteiros. Dessa forma, se p aponta para v[i] e q aponta para
v[j] , ento p q igual a i j. A gura 12.5 ilustra essa situao.
Podemos comparar variveis ponteiros entre si usando os operadores relacionais usuais
( < , <= , > , >= , == e != ). Usar os operadores relacionais para comparar dois ponteiros que
apontam para um mesmo vetor uma tima idia. O resultado da comparao depende das
posies relativas dos dois elementos do vetor. Por exemplo, depois das atribuies dadas a
seguir:
p = &v[5];
q = &v[1];
o resultado da comparao p <= q falso e o resultado de p >= q verdadeiro.
FACOM UFMS
12.1 ARITMTICA COM PONTEIROS 91
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
a
b
c
p
p
p
q
q
v
v
v
Figura 12.3: (a) p = &v[2]; (b) q = p + 3; (c) p = p + 6;
FACOM UFMS
92 PONTEIROS E VETORES
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
a
b
c
p
p
p
q
q
v
v
v
Figura 12.4: (a) p = &v[8]; (b) q = p - 3; (c) p = p - 6;
FACOM UFMS
12.2 USO DE PONTEIROS PARA PROCESSAMENTO DE VETORES 93
0 1 2 3 4 5 6 7 8 9
p q
v
Figura 12.5: p = &v[5]; e q = &v[1]; A expresso p q tem valor 4 e a expresso q p tem
valor 4.
12.2 Uso de ponteiros para processamento de vetores
Usando aritmtica de ponteiros podemos visitar os elementos de um vetor atravs da atri-
buio de um ponteiro para seu incio e do seu incremento em cada passo, como mostrado no
trecho de cdigo abaixo:
.
.
.
#define DIM 100
int main(void)
{
int v[DIM], soma,
*
p;
.
.
.
soma = 0;
for (p = &v[0]; p < &v[DIM]; p++)
soma = soma +
*
p;
.
.
.
A condio p < &v[DIM] na estrutura de repetio for necessita de ateno especial.
Apesar de estranho, possvel aplicar o operador de endereo para v[DIM] , mesmo sabendo
que este elemento no existe no vetor v. Usar v[DIM] dessa maneira perfeitamente seguro,
j que a sentena for no tenta examinar o seu valor. O corpo da estrutura de repetio
for ser executado com p igual a &v[0] , &v[1] , . . ., &v[DIM-1] , mas quando p igual a
&v[DIM] a estrutura de repetio termina.
Como j vimos, podemos tambm combinar o operador de indireo
*
com operadores
de incremento ++ ou decremento -- em sentenas que processam elementos de um vetor.
Considere inicialmente o caso em que queremos armazenar um valor em um vetor e ento
avanar para o prximo elemento. Usando um ndice, podemos fazer diretamente:
v[i++] = j;
FACOM UFMS
94 PONTEIROS E VETORES
Se p est apontando para o (i + 1)-simo elemento de um vetor, a sentena correspondente
usando esse ponteiro :
*
p++ = j;
Devido precedncia do operador ++ sobre o operador
*
, o compilador enxerga essa
sentena como
*
(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 ex-
presso 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
componteiros, 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++;
.
.
.
12.3 Uso do identicador de um vetor como ponteiro
Ponteiros e vetores esto intimamente relacionados. Como vimos nas sees anteriores,
usamos aritmtica de ponteiros para trabalhar com vetores. Mas essa no a nica relao
entre eles. Outra relao importante entre ponteiros e vetores fornecida pela linguagem C
que o identicador de um vetor pode ser usado como um ponteiro para o primeiro elemento
do vetor. Essa relao simplica a aritmtica componteiros e estabelece ganho de versatilidade
em ambos, ponteiros e vetores.
Por exemplo, suponha que temos o vetor v declarado como abaixo:
int v[10];
FACOM UFMS
12.3 USO DO IDENTIFICADOR DE UM VETOR COMO PONTEIRO 95
Usando v como um ponteiro para o primeiro elemento do vetor, podemos modicar o con-
tedo de v[0] da seguinte forma:
*
v = 7;
Podemos tambm modicar o contedo de v[1] atravs do ponteiro v + 1 :
*
(v + 1) = 12;
Em geral, v + i o mesmo que &v[i] , e
*
(v + i) equivalente a v[i] . Em outras palavras,
ndices de vetores podem ser vistos como uma forma de aritmtica de ponteiros.
O fato que o identicador de um vetor pode servir como um ponteiro facilita nossa progra-
mao de estruturas de repetio que percorremvetores. Considere a estrutura de repetio do
exemplo dado na seo anterior:
soma = 0;
for (p = &v[0]; p < &v[DIM]; p++)
soma = soma +
*
p;
Para simplicar 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;
Apesar de podermos usar o identicador de um vetor como um ponteiro, no possvel
atribuir-lhe umnovo valor. A tentativa de faz-lo apontar para qualquer outro lugar um erro,
como mostra o trecho de cdigo abaixo:
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 PONTEIROS E VETORES
Programa 12.1: Imprime os elementos na ordem inversa da de leitura.
#include <stdio.h>
#define N 10
int main(void)
{
int v[N],
*
p;
printf("Informe %d nmeros: ", N);
for (p = v; p < v + N; p++)
scanf("%d", p);
printf("Em ordem inversa: ");
for (p = v + N - 1; p >= v; p--)
printf("%d ",
*
p);
printf("\n");
return 0;
}
Outro uso do identicador de um vetor como um ponteiro quando um vetor um argu-
mento 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;
}
Suponha que chamamos a funo max da seguinte forma:
M = max(N, u);
Essa chamada faz comque 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 modicado, pode-
mos incluir a palavra reservada const precedendo a sua declarao.
FACOM UFMS
12.3 USO DO IDENTIFICADOR DE UM VETOR COMO PONTEIRO 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 argu-
mento no est protegido contra alteraes, j que no ocorre uma cpia do vetor todo. Desse
modo, o tempo necessrio para passar umvetor 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 ve-
tor 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 comque 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 PONTEIROS 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 armao.
.
.
.
#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 componteiros, 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 vari-
veis listadas abaixo:
int v[] = {5, 15, 34, 54, 14, 2, 52, 72};
int
*
p = &v[1],
*
q = &v[5];
(a) Qual o valor de
*
(p + 3) ?
(b) Qual o valor de
*
(q - 3) ?
(c) Qual o valor de q - p ?
(d) A expresso p < q tem valor verdadeiro ou falso?
(e) A expresso
*
p <
*
q tem valor verdadeiro ou falso?
12.2 Qual o contedo do vetor v aps a execuo do seguinte trecho de cdigo?
FACOM UFMS
12.3 USO DO IDENTIFICADOR DE UM VETOR COMO PONTEIRO 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;
}
.
.
.
12.3 Suponha que v um vetor e p um ponteiro. Considere que a atribuio p = v; foi
realizada previamente. Quais das expresses abaixo no so permitidas? Das restantes,
quais tm valor verdadeiro?
(a) p == v[0]
(b) p == &v[0]
(c)
*
p == v[0]
(d) p[0] == v[0]
12.4 Escreva um programa que leia uma mensagem e a imprima em ordem reversa. Use a
funo getchar para ler caractere por caractere, armazenando-os em um vetor. Pare
quando encontrar um caractere de mudana de linha \n . Faa o programa de forma
a usar um ponteiro, ao invs de um ndice como um nmero inteiro, para controlar a
posio corrente no vetor.
FACOM UFMS
100 PONTEIROS E VETORES
FACOM UFMS
AULA 13
PONTEIROS 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].
13.1 Ponteiros para elementos de uma matriz
Sabemos que a linguagemC armazena matrizes, que so objetos representados de forma bi-
dimensional, como uma seqncia contnua de compartimentos de memria, comdemarcaes
de onde comea e onde termina cada uma de suas linhas. Ou seja, uma matriz armazenada
como um vetor na memria, com marcas especiais em determinados pontos regularmente es-
paados: os elementos da linha 0 vm primeiro, seguidos pelos elementos da linha 1, da linha
2, e assim por diante. Uma matriz com n linhas tem a aparncia ilustrada como na gura 13.1.
linha 0 linha 1 linha n 1
Figura 13.1: Representao da alocao de espao na memria para uma matriz.
Essa representao pode nos ajudar a trabalhar com ponteiros e matrizes. Se fazemos um
ponteiro p apontar para a primeira clula de uma matriz, isto , o elemento na posio (0,0), po-
demos ento visitar todos os elementos da matriz incrementando o ponteiro p repetidamente.
Por exemplo, suponha que queremos inicializar todos os elementos de uma matriz de n-
meros inteiros com 0. Suponha que temos a declarao da seguinte matriz:
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 PONTEIROS 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;
A estrutura de repetio acima inicia com p apontando para A[0][0] . Os incrementos
sucessivos de p fazem-no apontar para A[0][1] , A[0][2] , e assim por diante. Quando
p atinge A[0][COLUNAS-1] , o ltimo elemento da linha 0, o prximo incremento faz com
que p aponte para A[1][0] , o primeiro elemento da linha 1. O processo continua at que p
ultrapasse o elemento A[LINHAS-1][COLUNAS-1] , o ltimo elemento da matriz.
13.2 Processamento das linhas de uma matriz
Podemos processar uma linha de uma matriz ou seja, percorr-la, visitar os contedos dos
compartimentos, usar seus valores, modic-los, etc tambmusando ponteiros. Por exemplo,
para visitar os elementos da linha i de uma matriz Apodemos usar umponteiro p apontar para
o elemento da linha i e da coluna 0 da matriz A, como mostrado abaixo:
p = &A[i][0];
ou poderamos fazer simplesmente
p = A[i];
j que a expresso A[i] um ponteiro para o primeiro elemento da linha i.
A justicativa para essa armao vem da aritmtica com ponteiros. Lembre-se que para
um vetor A, a expresso A[i] equivalente a
*
(A + i) . Assim, &A[i][0] o mesmo que
&(
*
(A[i] + 0)) , que equivalente a &
*
A[i] e que, por m, equivalente a A[i] . No
trecho de cdigo a seguir usamos essa simplicao para inicializar com zeros a linha i da
matriz A:
FACOM UFMS
13.3 PROCESSAMENTO DAS COLUNAS DE UMA MATRIZ 103
int A[LINHAS][COLUNAS],
*
p, i;
.
.
.
for (p = A[i]; p < A[i] + COLUNAS; p++)
*
p = 0;
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]);
13.3 Processamento das colunas de uma matriz
O processamento dos elementos de uma coluna de uma matriz no to simples como o
processamento dos elementos de uma linha, j que a matriz armazenada linha por linha na
memria. A seguir, mostramos uma estrutura de repetio que inicializa com zeros a coluna j
da matriz A:
int A[LINHAS][COLUNAS], (
*
p)[COLUNAS], j;
.
.
.
for (p = &A[0]; p < A[LINHAS]; p++)
(
*
p)[j] = 0;
Nesse exemplo, declaramos p como umponteiro para umvetor de dimenso COLUNAS , cu-
jos elementos so nmeros inteiros. Os parnteses envolvendo
*
p so necessrios, j que sem
eles o compilador trataria p como umvetor 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]) .
13.4 Identicadores de matrizes como ponteiros
Assim como o identicador de um vetor pode ser usado como um ponteiro, o identicador
de uma matriz tambm pode e, na verdade, de qualquer o identicador de qualquer varivel
composta homognea pode. No entanto, alguns cuidados so necessrios quando ultrapassa-
mos a barreira de duas ou mais dimenses.
Considere a declarao da matriz a seguir:
FACOM UFMS
104 PONTEIROS E MATRIZES
int A[LINHAS][COLUNAS];
Neste caso, A no um ponteiro para A[0][0] . Ao contrrio, umponteiro 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 simplicar estruturas de repetio que proces-
sam elementos de uma matriz. Por exemplo, ao invs de escrever:
for (p = &A[0]; p < &A[LINHAS]; p++)
(
*
p)[j] = 0;
para inicializar a coluna j da matriz A, podemos escrever
for (p = A; p < A + LINHAS; p++)
(
*
p)[j] = 0;
Com essa idia, podemos fazer o compilador acreditar que uma varivel composta homo-
gnea 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 I
n
. 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 IDENTIFICADORES DE MATRIZES COMO PONTEIROS 105
int soma_matriz(int n, const int A[DIM][DIM])
{
int i, j, soma = 0;
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
soma = soma + A[i][j];
return soma;
}
FACOM UFMS
106 PONTEIROS E MATRIZES
FACOM UFMS
AULA 14
PONTEIROS E CADEIAS
Nas aulas 12 e 13 estudamos formas de trabalhar com variveis compostas homogneas e
ponteiros para seus elementos. No entanto, importante ainda estudar a relao entre pontei-
ros e as cadeias de caracteres que, como j vimos, so vetores especiais que contm caracteres.
Nesta aula, baseada na referncia [7], aprenderemos algumas particularidades de ponteiros
para elementos de cadeias de caracteres na linguagem C, alm de estudar a relao entre pon-
teiros e constantes que so cadeias de caracteres.
14.1 Literais e ponteiros
Devemos relembrar que uma literal uma seqncia de caracteres envolvida por aspas
duplas. Um exemplo de uma literal apresentado a seguir
1
:
"O usurio mdio de computador possui o crebro de um macaco-aranha"
Nosso primeiro contato comliterais foi ainda emAlgoritmos e Programao I. Literais ocor-
rem 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 in-
dica o nal da cadeia. Ocaracter nulo umbyte 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 gura 14.1.
a b c \0
Figura 14.1: Representao de uma literal na memria.
Literais podem ser vazias. A literal "" armazenada como um nico caractere nulo.
Como uma literal armazenada emumvetor, o compilador a enxerga como umponteiro do
tipo char
*
. As funes printf e scanf , por exemplo, esperamum valor do tipo char
*
como primeiro argumento. Se, por exemplo, fazemos a seguinte chamada:
1
Frase de Bill Gates, dono da Micro$oft, em uma entrevista para Computer Magazine.
107
108 PONTEIROS E CADEIAS
printf("abc");
o endereo da literal "abc" passado como argumento para a funo printf , isto , o ende-
reo 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 pon-
teiro 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 gura 14.2.
p
a b c \0
Figura 14.2: Representao da atribuio de uma literal a um ponteiro.
Observe ainda que no permitido alterar uma literal durante a execuo de umprograma.
Isso signica que a tentativa de modicar uma literal pode causar um comportamento inde-
nido do programa.
Relembrando, uma varivel cadeia de caracteres um vetor do tipo char que necessaria-
mente deve reservar espao para o caractere nulo. Quando declaramos um vetor de caracteres
que ser usado para armazenar cadeias de caracteres, devemos sempre declarar esse vetor com
uma posio a mais que a mais longa das cadeias de caracteres possveis, j que por conven-
o da linguagem C, toda cadeia de caracteres nalizada com um caractere nulo. Veja, por
exemplo, a declarao a seguir:
char cadeia[TAM+1];
onde TAM uma macro denida 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 decla-
rao, como mostra o exemplo abaixo:
char data[13] = "7 de outubro";
FACOM UFMS
14.1 LITERAIS E PONTEIROS 109
O compilador ento coloca os caracteres de "7 de outubro" no vetor data e adiciona
o caractere nulo ao nal para que data possa ser usada como uma cadeia de caracteres. A
gura 14.3 ilustra essa situao.
data 7 d e o o u u t b r \0
Figura 14.3: Declarao e inicializao de uma cadeia de caracteres.
Apesar de "7 de outubro" se parecer com uma literal, a linguagem C de fato a v como
uma abreviao para um inicializador de um vetor. Ou seja, a declarao e inicializao acima
enxergada pelo compilador como abaixo:
char data[13] = {7, ,d,e, ,o,u,t,u,b,r,o,\0};
No caso emque o inicializador menor que o tamanho denido para a cadeia de caracteres,
o compilador preencher as posies nais restantes da cadeia com o caractere nulo. Por outro
lado, sempre importante garantir que o inicializador tenha menor comprimento que o tama-
nho 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:
char data[] = "7 de outubro";
que declara um vetor data , que uma cadeia de caracteres, com a declarao a seguir:
char
*
data = "7 de outubro";
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 signicativas
entre as duas, que destacamos abaixo:
na verso em que a varivel declarada como um vetor, os caracteres armazenados em
data podem ser modicados, como fazemos com elementos de qualquer vetor; na ver-
so em que a varivel declarada como um ponteiro, data aponta para uma literal que,
como j vimos, no pode ser modicada;
FACOM UFMS
110 PONTEIROS E CADEIAS
na verso com vetor, data um identicador de um vetor; na verso com ponteiro,
data uma varivel que pode, inclusive, apontar para outras cadeias de caracteres du-
rante a execuo do programa.
Se precisamos que uma cadeia de caracteres seja modicada, nossa responsabilidade de-
clarar um vetor de caracteres no qual ser armazenada essa cadeia. Declarar um ponteiro no
suciente, neste caso. Por exemplo, a declarao abaixo:
char
*
p;
faz com que o compilador reserve espao suciente para uma varivel ponteiro. Infelizmente,
o compilador no reserva espao para uma cadeia de caracteres, mesmo porque, no h indi-
cao 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:
char cadeia[TAM+1],
*
p;
p = cadeia;
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 especicamente com cadeias de caracteres, o que j
zemos nas aulas de Algoritmos e Programao I. Ainda veremos a seguir dois tpicos impor-
tantes sobre cadeias de caracteres: vetores de cadeias de caracteres e argumentos de linha de
comando.
14.2 Vetores de cadeias de caracteres
Uma forma de armazenar em memria um vetor de cadeias de caracteres atravs da cri-
ao de uma matriz de caracteres e ento armazenar as cadeias de caracteres uma a uma. Por
exemplo, podemos fazer como a seguir:
char planetas[][9] = {"Mercurio", "Venus", "Terra",
"Marte", "Jupiter", "Saturno",
"Urano", "Netuno", "Plutao"};
FACOM UFMS
14.2 VETORES DE CADEIAS DE CARACTERES 111
Observe que estamos omitindo o nmero de linhas da matriz, que fornecido pelo inicializa-
dor, mas a linguagem C exige que o nmero de colunas seja especicado, conforme zemos na
declarao.
A gura 14.4 ilustra a declarao e inicializao da varivel planetas . Observe que to-
das 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.
M
M
e
e
e
e
e
e
r
r
r
r
r r
r r c
u
u
u
u
u
u
i
i
o
o
o
o
o
V
n
n
n
n s
T
a
a
a
a
a
t
t
t
t
t
J p
S
U
N
P l \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
0
1
1 2
2
3
3
4
4
5
5
6
6
7
7 8
8
Figura 14.4: Matriz de cadeias de caracteres planetas .
A inecincia de armazenamento aparente nesse exemplo comum quando trabalhamos
com cadeias de caracteres, j que colees de cadeias de caracteres sero, em geral, um misto
entre curtas e longas cadeias. Uma possvel forma de sanar esse problema usar umvetor cujos
elementos so ponteiros para cadeias de caracteres, como podemos ver na declarao abaixo:
char
*
planetas[] = {"Mercurio", "Venus", "Terra",
"Marte", "Jupiter", "Saturno",
"Urano", "Netuno", "Plutao"};
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 identicador da varivel. No entanto, o efeito dessa declarao na
memria muito diferente, como podemos ver na gura 14.5.
Cada elemento do vetor planetas um ponteiro para uma cadeia de caracteres, termi-
nada com um caractere nulo. No h mais desperdcio de compartimentos nas cadeias de ca-
racteres, apesar de termos de alocar espao para os ponteiros no vetor planetas . Para acessar
FACOM UFMS
112 PONTEIROS E CADEIAS
planetas
M
M
e
e
e
e
e
e
r
r
r
r r
r r
r
c
u
u
u
u
u
u
i
i
o
o
o
o
o
V
n
n
n
n s
T
a
a
a
a
a
t
t
t
t
t
J p
S
U
N
P l \0
\0
\0
\0
\0
\0
\0
\0
\0
0
1
2
3
4
5
6
7
8
Figura 14.5: Vetor planetas de ponteiros para cadeias de caracteres.
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:
for (i = 0; i < 9; i++)
if (planetas[i][0] == M)
printf("%s comea com M\n", planetas[i]);
14.3 Argumentos na linha de comandos
Quando executamos um programa, em geral, devemos fornecer a ele alguma informao
como, por exemplo, um nome de um arquivo, uma opo que modica seu comportamento,
etc. Por exemplo, considere o comando UNIX ls . Se executamos esse comando como abaixo:
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 ARGUMENTOS NA LINHA DE COMANDOS 113
prompt$ ls -l
ento o resultado uma listagem detalhada
2
que nos mostra o tamanho de cada arquivo, seu
proprietrio, a data e hora em que houve a ltima modicao no arquivo e assim por diante.
Para modicar ainda mais o comportamento do comando ls podemos especicar que ele
mostre detalhes de apenas um arquivo, como mostrado abaixo:
prompt$ ls -l exerc1.c
Nesse caso, o comando ls mostrar informaes detalhadas sobre o arquivo exerc1.c .
Informaes em linha de comando esto disponveis para todos os programas, no ape-
nas para comandos do sistema operacional. Para ter acesso aos argumentos de linha de co-
mando, chamados de parmetros do programa na linguagem C padro, devemos denir a
funo main como uma funo com dois parmetros que costumeiramente tm identicado-
res argc e argv . Isto , devemos fazer como abaixo:
int main(int argc, char
*
argv[])
{
.
.
.
}
O parmetro argc , abreviao de contador de argumentos, o nmero de argumentos
de linha de comando, incluindo tambm o nome do programa. O parmetro argv , abreviao
de vetor de argumentos, um vetor de ponteiros para os argumentos da linha de comando,
que so armazenados como cadeias de caracteres. Assim, argv[0] aponta para o nome do
programa, enquanto que argv[1] at argv[argc-1] apontam para os argumentos da linha
de comandos restantes. O vetor argv tem um elemento adicional argv[argc] que sempre
umponteiro nulo, umponteiro especial que aponta para nada, representado pela macro NULL .
Se um(a) usurio(a) digita a linha de comando abaixo:
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 gura 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.
2
l do ingls long.
FACOM UFMS
114 PONTEIROS E CADEIAS
PSfrag
argv
0
1
1
2
3
l
e e x r c c . \0
\0
Figura 14.6: Representao de argv .
Como argv um vetor de ponteiros, o acesso aos argumentos da linha de comandos
realizado, em geral, como mostrado na estrutura de repetio a seguir:
int i;
.
.
.
for (i = 1; i < argc; i++)
printf("%s\n", argv[i]);
O programa 14.1 ilustra como acessar os argumentos de uma linha de comandos.
Programa 14.1: Verica nomes de planetas.
#include <stdio.h>
#include <string.h>
#define NUM_PLANETAS 9
int main(int argc, char
*
argv[])
{
char
*
planetas[] = {"Mercurio", "Venus", "Terra", "Marte", "Jupiter",
"Saturno", "Urano", "Netuno", "Plutao"};
int i, j, k;
for (i = 1; i < argc; i++) {
for (j = 0; j < NUM_PLANETAS; j++)
if (strcmp(argv[i], planetas[j]) == 0) {
k = j;
j = NUM_PLANETAS;
}
if (j == NUM_PLANETAS + 1)
printf("%s o planeta %d\n", argv[i], k);
else
printf("%s no um planeta\n", argv[i]);
}
return 0;
}
FACOM UFMS
14.3 ARGUMENTOS 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 caracte-
res, como mostramos no exemplo abaixo:
prompt$ ./planetas Jupiter venus Terra Joaquim
O resultado dessa execuo dado a seguir:
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. Identique quais chamadas no funcio-
nam 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 PONTEIROS E CADEIAS
14.3 Suponha que declaramos as seguintes variveis:
char s[MAX+1];
int i, j;
Suponha tambm que a seguinte chamada foi executada:
scanf("%d%s%d", &i, s, &j);
Se o(a) usurio(a) digita a seguinte entrada:
12abc34 56def78
quais sero os valores de i, j e s depois dessa chamada?
14.4 A funo abaixo supostamente cria uma cpia idntica de uma cadeia de caracteres. O
que h de errado com a funo?
char
*
duplica(const char
*
p)
{
char
*
q;
strcpy(q, p);
return q;
}
14.5 O que imprime na sada o programa abaixo?
#include <stdio.h>
int main(void)
{
char s[] = "Dvmuvsb",
*
p;
for (p = s;
*
p; p++)
--
*
p;
printf("%s\n", s);
return 0;
}
14.6 (a) Escreva uma funo com a seguinte interface:
void maiuscula(char cadeia[])
FACOM UFMS
14.3 ARGUMENTOS 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 uma palavra: laranja
Informe uma palavra: melao
Informe uma palavra: tomate
Informe uma palavra: cereja
Informe uma palavra: uva
Informe uma palavra: banana
Informe uma palavra: maca
Maior palavra: laranja
Menor Palavra: uva
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:
prompt$ ./reverso garfo e faca
deve produzir a seguinte sada:
FACOM UFMS
118 PONTEIROS 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
deve produzir a seguinte sada:
108
FACOM UFMS
AULA 15
PONTEIROS 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 zemos quando usamos
ponteiros para nmeros inteiros, por exemplo. Almdisso, vamos adicionar tambmponteiros
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].
15.1 Ponteiros para registros
Suponha que denimos uma etiqueta de registro data como a seguir:
struct data {
int dia;
int mes;
int ano;
};
A partir dessa denio, podemos declarar variveis do tipo struct data , como abaixo:
struct data hoje;
E ento, assim como zemos com ponteiros para inteiros, caracteres e nmeros de ponto
utuante, podemos declarar um ponteiro para o registro data da seguinte forma:
struct data
*
p;
Podemos, a partir dessa declarao, fazer uma atribuio varivel p como a seguir:
p = &hoje;
119
120 PONTEIROS E REGISTROS
Alm disso, podemos atribuir valores aos campos do registro de forma indireta, como faze-
mos 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 envol-
vendo
*
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 umregistro pode ser substituda, e temo mesmo efeito,
pelo operador -> como mostramos no exemplo abaixo:
p->dia = 11;
O programa 15.1 ilustra o uso de ponteiros para registros.
Programa 15.1: Uso de um ponteiro para um registro.
#include <stdio.h>
struct data {
int dia;
int mes;
int 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;
}
No programa 15.1, h a declarao de duas variveis: umregistro comidenticador hoje e
um ponteiro para registros com identicador p. Na primeira atribuio, p recebe o endereo da
varivel hoje . Observe que a varivel hoje do tipo struct data , isto , a varivel hoje
do mesmo tipo da varivel p e, portanto, essa atribuio vlida. Emseguida, valores do tipo
inteiro so armazenados na varivel hoje , mas de forma indireta, com uso do ponteiro p. Por
m, os valores atribudos so impressos na sada. A gura 15.1 mostra as variveis hoje e p
depois das atribuies realizadas durante a execuo do programa 15.1.
FACOM UFMS
15.2 REGISTROS CONTENDO PONTEIROS 121
p
hoje
dia mes ano
13 10 2010
Figura 15.1: Representao do ponteiro p e do registro hoje .
15.2 Registros contendo ponteiros
Podemos tambm usar ponteiros como campos de registros. Por exemplo, podemos denir
uma etiqueta de registro como abaixo:
struct reg_pts {
int
*
pt1;
int
*
pt2;
};
A partir dessa denio, podemos declarar variveis (registros) do tipo struct reg_pts
como a seguir:
struct reg_pts bloco;
Em seguida, a varivel bloco pode ser usada como sempre zemos. 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 umregis-
tro 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
signica que queremos acessar o contedo do endereo apontado por reg.pt1 . Como o ope-
rador 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 -
gura 15.2 ilustra essa situao.
reg
pt1 pt2
i1 i2 100 2
Figura 15.2: Representao do registro reg contendo dois campos ponteiros.
FACOM UFMS
122 PONTEIROS E REGISTROS
Programa 15.2: Uso de um registro que contm campos que so ponteiros.
#include <stdio.h>
struct pts_int {
int
*
pt1;
int
*
pt2;
};
int main(void)
{
int i1, i2;
struct pts_int reg;
i2 = 100;
reg.pt1 = &i1;
reg.pt2 = &i2;
*
reg.pt1 = -2;
printf("i1 = %d,
*
reg.pt1 = %d\n", i1,
*
reg.pt1);
printf("i2 = %d,
*
reg.pt2 = %d\n", i2,
*
reg.pt2);
return 0;
}
Exerccios
15.1 Qual a sada do programa descrito abaixo?
#include <stdio.h>
struct dois_valores {
int vi;
float vf;
};
int main(void)
{
struct dois_valores reg1 = {53, 7.112}, reg2,
*
p = &reg1;
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;
}
15.2 Simule a execuo do programa descrito abaixo.
FACOM UFMS
15.2 REGISTROS 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 = &real;
scanf("%c%d%f", reg.c, reg.i, reg.f);
printf("%c\n%d\n%f\n", caractere, inteiro, real);
return 0;
}
15.3 Simule a execuo do programa descrito abaixo.
#include <stdio.h>
struct celula {
int valor;
struct celula
*
prox;
};
int main(void)
{
struct celula reg1, reg2,
*
p;
scanf("%d%d", &reg1.valor, &reg2.valor);
reg1.prox = &reg2;
reg2.prox = NULL;
for (p = &reg1; p != NULL; p = p->prox)
printf("%d ", p->valor);
printf("\n");
return 0;
}
FACOM UFMS
124 PONTEIROS E REGISTROS
FACOM UFMS
AULA 16
USO AVANADO DE PONTEIROS
Nas aulas 9 a 15 vimos formas importantes de uso de ponteiros: como parmetros de fun-
es simulando passagempor referncia e como elementos da linguagemC que podemacessar
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 espe-
cca para tanto.
Nesta aula, baseada nas referncias [2, 7], veremos outros usos para ponteiros: como au-
xiliares na alocao dinmica de espaos de memria, como ponteiros para funes e como
ponteiros para outros ponteiros.
16.1 Ponteiros para ponteiros
Como vimos at aqui, um ponteiro uma varivel cujo contedo um endereo. Dessa
forma, podemos acessar o contedo de uma posio de memria atravs de um ponteiro de
forma indireta. Temos visto exemplos de ponteiros e suas aplicaes em algumas das aulas
anteriores e tambm na seo anterior.
Por outro lado, imagine por um momento que uma varivel de um tipo bsico qualquer
contm um endereo de uma posio de memria que, por sua vez, ela prpria tambm con-
tm um endereo de uma outra posio de memria. Ento, essa varivel denida como
um ponteiro para um ponteiro, isto , um ponteiro para um compartimento de memria que
contm um ponteiro. Ponteiros para ponteiros tm diversas aplicaes na linguagem C, espe-
cialmente no uso de matrizes, como veremos adiante na seo 16.2. Ponteiros para ponteiros
so mais complicados de entender e exigem maturidade para sua manipulao.
O conceito de indireo dupla ento introduzido neste caso: uma varivel que contmum
endereo de uma posio de memria, isto , um ponteiro, que, por sua vez, contm um ende-
reo para uma outra posio de memria, ou seja, um outro ponteiro. Certamente, podemos
estender indirees com a multiplicidade que desejarmos como, por exemplo, indireo dupla,
indireo tripla, indireo qudrupla, etc. No entanto, a compreenso de um programa ca
gradualmente mais difcil medida que indirees mltiplas vo sendo utilizadas. Entender
bem um programa componteiros para ponteiros, ou ponteiros para ponteiros para ponteiros, e
assim por diante, bastante complicado e devemos usar esses recursos de forma criteriosa. Al-
gumas estruturas de dados, porm, exigem que indirees mltiplas sejam usadas e, portanto,
necessrio estud-las.
Considere agora o programa 16.1.
125
126 USO AVANADO DE PONTEIROS
Programa 16.1: Um exemplo de indireo dupla.
#include <stdio.h>
int main(void)
{
int x, y,
*
pt1,
*
pt2,
**
ptpt1,
**
ptpt2;
x = 1;
y = 4;
printf("x=%d y=%d\n", x, y);
pt1 = &x;
pt2 = &y;
printf("
*
pt1=%d
*
pt2=%d\n",
*
pt1,
*
pt2);
ptpt1 = &pt1;
ptpt2 = &pt2;
printf("
**
ptpt1=%d
**
ptpt2=%d\n",
**
ptpt1,
**
ptpt2);
return 0;
}
Inicialmente, o programa 16.1 faz a declarao de seis variveis do tipo inteiro: x e y ,
que armazenam valores desse tipo; pt1 e pt2 , que so ponteiros; e ptpt1 e ptpt2 que so
ponteiros para ponteiros. O smbolo
**
antes do identicador das variveis ptpt1 e ptpt2
signica que as variveis so ponteiros para ponteiros, ou seja, podemarmazenar umendereo
onde se encontra um outro endereo onde, por sua vez, encontra-se um valor, um nmero
inteiro nesse caso.
Aps essas declaraes, o programa segue com atribuies de valores s variveis x e y
e com atribuies dos endereos das variveis x e y para os ponteiros pt1 e pt2 , respecti-
vamente, como j vimos em outros exemplos.
Note ento que as duas atribuies abaixo:
ptpt1 = &pt1;
ptpt2 = &pt2;
fazem das variveis ptpt1 e ptpt2 ponteiros para ponteiros. Ou seja, ptpt1 contm o en-
dereo 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 nalmente que para acessar o contedo do endereo apontado pelo endereo apon-
tado por ptpt1 temos de usar o smbolo de indireo dupla
**
, como pode ser vericado na
ltima chamada funo printf do programa.
Veja a gura 16.1 que ilustra indireo dupla no contexto do programa 16.1.
FACOM UFMS
16.2 ALOCAO DINMICA DE MEMRIA 127
4 1 x y
pt1 pt2 ptpt1 ptpt2
Figura 16.1: Exemplo esquemtico de indireo dupla.
16.2 Alocao dinmica de memria
As estruturas de armazenamento de informaes na memria principal da linguagem C
tm, em geral, tamanho xo. Por exemplo, uma vez que um programa foi compilado, a quan-
tidade de elementos de um vetor ou de uma matriz xa. Isso signica que, para alterar a
capacidade de armazenamento de uma estrutura de tamanho xo, necessrio alterar seu ta-
manho no arquivo-fonte e compilar esse programa novamente.
Felizmente, a linguagem C permite alocao dinmica de memria, que a habilidade
de reservar espaos na memria principal durante a execuo de um programa. Usando alo-
cao dinmica, podemos projetar estruturas de armazenamento que crescem ou diminuem
quando necessrio durante a execuo do programa. Apesar de disponvel para qualquer tipo
de dados, a alocao dinmica usada em geral com variveis compostas homogneas e hete-
rogneas.
Aprendemos at aqui a declarar uma varivel composta homognea especicando umiden-
ticador e sua(s) dimenso(es).
Por exemplo, veja as declaraes a seguir:
int vet[100];
float mat[40][60];
Nesse caso, temos a declarao de um vetor com identicador vet e 100 posies de memria
que podem armazenar nmeros inteiros e uma matriz com identicador mat de 40 linhas e
60 colunas que so compartimentos de memria que podem armazenar nmeros de ponto u-
tuante. Todos os compartimentos dessas variveis compostas homogneas cam disponveis
para uso durante a execuo do programa.
Em diversas aplicaes, para que os dados de entrada sejam armazenados em variveis
compostas homogneas comdimenso(es) adequadas, necessrio saber antes essa(s) dimen-
so(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 arma-
zenada no vetor vet , um valor que ser mantido no programa para vericao 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 especicada 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, semultrapassar 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 com-
partimentos 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 USO AVANADO DE PONTEIROS
Essa alocao que acabamos de descrever, e que conhecemos bem de muito tempo, cha-
mada de alocao esttica de memria, o que signica que, no incio da execuo do pro-
grama, quando encontramos uma declarao como essa, ocorre a reserva na memria princi-
pal de um nmero xo de compartimentos correspondentes ao nmero especicado na decla-
rao. Esse espao xo 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 po-
dem no ter impacto signicativo em programas pequenos, que fazem pouco uso da memria
principal, como a grande maioria de nossos programas que desenvolvemos at aqui. No en-
tanto, devemos sempre ter emmente que a memria umrecurso limitado e que emprogramas
maiores e que armazenam muitas informaes na memria principal temos, de alguma forma,
de us-la de maneira eciente, economizando compartimentos sempre que possvel. Essa di-
retriz, 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
signicativo 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
commuitos compartimentos mais. No entanto, emuma outra execuo subseqente do mesmo
programa que declara essas variveis, poderiam ser necessrias capacidades diferentes, bem
maiores, para ambas as variveis. Dessa forma, xar um valor especco 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 signica que possvel
alocar compartimentos de memria durante a execuo do programa, com base nas demandas
do(a) usurio(a).
Alocao dinmica de memria signica que um programa solicita ao sistema computaci-
onal, durante a sua execuo, blocos da memria principal que estejam disponveis para uso
naquele momento. Caso haja espao suciente disponvel na memria principal, a solicitao
atendida e o espao solicitado ca reservado na memria para aquele uso especco. Caso con-
trrio, 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 vericao de situaes
como essa no arquivo-fonte, informando tambm ao() usurio(a) do programa sobre a situ-
ao 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 aloca-
o de um vetor.
FACOM UFMS
16.2 ALOCAO DINMICA DE MEMRIA 129
Programa 16.2: Um exemplo de alocao dinmica de memria.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i, n,
*
vetor,
*
pt;
printf("Informe a dimenso do vetor: ");
scanf("%d", &n);
vetor = (int
*
) malloc(n
*
sizeof(int));
if (vetor != NULL) {
for (i = 0; i < n; i++) {
printf("Informe o elemento %d: ", i);
scanf("%d", (vetor + i));
}
printf("\nVetor : ");
for (pt = vetor; pt < (vetor + n); pt++)
printf("%d ",
*
pt);
printf("\nVetor invertido: ");
for (i = n - 1; i >= 0; i--)
printf("%d ", vetor[i]);
printf("\n");
free(vetor);
}
else
printf("Impossvel alocar o espao requisitado\n");
return 0;
}
Na segunda linha do programa 16.2, inclumos o arquivo-cabealho stdlib.h , que con-
tm a declarao da funo malloc , usada nesse programa. A funo malloc tem sua inter-
face apresentada a seguir:
void
*
malloc(size_t tamanho)
A funo malloc reserva uma certa quantidade especca de memria e devolve um pon-
teiro do tipo void . No programa acima, reservamos n compartimentos contnuos que podem
armazenar nmeros inteiros, o que se reete na expresso n
*
sizeof(int) . Essa expresso,
na verdade, reete o nmero de bytes que sero alocados continuamente na memria, que de-
pende 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 comparti-
mentos que desejamos para armazenar nmeros inteiros. O endereo da primeira posio de
FACOM UFMS
130 USO 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 modicador de tipo (int
*
)
para indicar que o endereo devolvido de fato um ponteiro para um nmero inteiro. Por m,
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 nme-
ros 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 ALOCAO DINMICA DE MEMRIA 131
Observe por m que as linhas em que ocorrem a alocao dinmica de memria no pro-
grama 16.3 podem ser substitudas de forma equivalente pelas linhas a seguir:
matriz = (int
**
) malloc(m
*
sizeof(int
*
));
for (i = 0; i < m; i++)
matriz[i] = (int
*
) malloc(n
*
sizeof(int));
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 ou-
tras duas. Essas funes solicitam blocos de memria de um espao de armazenamento conhe-
cido 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 car
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 pro-
blema de gerao de lixo na memria durante a execuo de programas. Essa funo tem a
seguinte interface na stdlib.h :
void free(void
*
pt)
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 ca
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 USO AVANADO DE PONTEIROS
16.3 Ponteiros para funes
Como vimos at este ponto, ponteiros podem conter endereos de variveis de tipos bsi-
cos, de elementos de vetores e matrizes, de campos de registros ou de registros inteiros. Um
ponteiro pode tambm apontar para outro ponteiro. Um ponteiro pode ainda ser usado para
alocao dinmica de memria. No entanto, a linguagem C no requer que ponteiros conte-
nham apenas endereos de dados. possvel, em um programa, ter ponteiros para funes, j
que as funes ocupam posies de memria e, por isso, possuem um endereo na memria,
assim como todas as variveis.
Podemos usar ponteiros para funes assim como usamos ponteiros para variveis. Em
particular, passar um ponteiro para uma funo como um argumento de outra funo bas-
tante comum em programas da linguagem C.
Suponha que estamos escrevendo a funo integral que integra uma funo matemtica
f entre os pontos a e b . Gostaramos de fazer a funo integral to geral quanto possvel,
passando a funo f como um argumento seu. Isso possvel na linguagem C pela denio
de f como um ponteiro para uma funo. Considerando que queremos integrar funes que
tm um parmetro do tipo double e que devolvem um valor do tipo double , uma possvel
interface da funo integral apresentada a seguir:
double integral(double (
*
f)(double), double a, double b)
Os parnteses em torno de
*
f indicam que f um ponteiro para uma funo, no uma
funo que devolve um ponteiro.
Tambm permitido denir f como se fosse uma funo:
double integral(double f(double), double a, double b)
Do ponto de vista do compilador, no h diferena alguma entre as interfaces apresentadas
acima.
Quando chamamos a funo integral devemos fornecer um nome de uma funo como
primeiro argumento. Por exemplo, a chamada a seguir integra a funo seno de 0 a /2:
result = integral(sin, 0.0, PI / 2);
O argumento sin o nome/identicador da funo seno, que foi includa no programa atra-
vs da biblioteca math.h .
Observe que no h parnteses aps o identicador 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 PONTEIROS 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 identicador 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 identicador 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 se-
guinte forma:
y = (
*
f)(x);
Nessa chamada,
*
f representa a funo apontada por f e x o argumento dessa chamada.
Assim, durante a execuo da chamada integral(sin, 0.0, PI / 2) , cada chamada de
*
f , na verdade, uma chamada de sin .
Tambmpodemos armazenar ponteiros para funes emvariveis ou us-los como elemen-
tos de umvetor, de uma matriz, de umcampo de umregistro. Podemos ainda escrever funes
que devolvem ponteiros para funes. Como um exemplo, declaramos abaixo uma varivel
que pode armazenar um ponteiro para uma funo:
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 se-
guinte forma:
ptf = f;
Observe que no h operador de endereamento antes de f .
Uma vez que ptf aponta para f , podemos chamar f indiretamente atravs de ptf
como a seguir:
(
*
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 USO AVANADO DE PONTEIROS
Programa 16.4: Um exemplo de ponteiro para funo.
#include <stdio.h>
#include <math.h>
/
*
Recebe um ponteiro para uma funo trigonomtrica, um inter-
valo de valores reais e um incremento real e imprime o valor
(real) da integral da funo trigomtrica neste intervalo
*
/
void tabela(double (
*
f)(double), double a, double b, double incr)
{
int i, num_intervalos;
double x;
num_intervalos = ceil((b - a) / incr);
for (i = 0; i <= num_intervalos; i++) {
x = a + i
*
incr;
printf("%11.6f %11.6f\n", x, (
*
f)(x));
}
}
/
*
Imprime a integral de funes trigonomtricas, dados
um intervalo de nmeros reais e um incremento real
*
/
int main(void)
{
double inicio, fim, incremento;
printf("Informe um intervalo [a, b]: ");
scanf("%lf%lf", &inicio, &fim);
printf("Informe o incremento: ");
scanf("%lf", &incremento);
printf("\n x cos(x)"
"\n ------ ------\n");
tabela(cos, inicio, fim, incremento);
printf("\n x sen(x)"
"\n ------ ------\n");
tabela(sin, inicio, fim, incremento);
printf("\n x tan(x)"
"\n ------ ------\n");
tabela(tan, inicio, fim, incremento);
return 0;
}
FACOM UFMS
16.3 PONTEIROS PARA FUNES 135
Exerccios
16.1 Dados dois vetores x e y, ambos com n elementos, 1 n 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 n 100) e uma seqncia de n nmeros inteiros, vericar 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 m, n 100, e um
vetor de nmeros reais v comn elementos, determinar o produto de Apor v. Use alocao
dinmica de memria.
16.5 Dizemos que uma matriz quadrada de nmeros inteiros distintos umquadrado 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 A
nn
, com 1 n 100, vericar se A
um quadrado mgico. Use alocao dinmica de memria.
16.6 Simule a execuo do programa a seguir.
FACOM UFMS
136 USO 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;
}
16.7 Escreva uma funo com a seguinte interface:
int soma(int (
*
f)(int), int inicio, int fim)
Uma chamada soma(g, i, j) deve devolver g(i) + ... + g(j) .
FACOM UFMS
AULA 17
ARQUIVOS
Nos programas que zemos 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 denidas 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 signica que o uxo de dados de entrada e sada dos
programas que zemos sempre passa pela memria principal. Nesta aula, baseada na refe-
rncia [7], aprenderemos funes que realizam tarefas de entrada e sada armazenados em um
dispositivo de memria secundria em arquivos.
17.1 Seqncias de caracteres
Na linguagemC, o termo seqncia de caracteres, do ingls stream, signica qualquer fonte
de entrada ou qualquer destinao para sada de informaes. Os programas que produzimos
at aqui sempre obtiveram toda sua entrada a partir de uma seqncia de caracteres, em geral
associada ao teclado, e escreveram sua sada em outra seqncia de caracteres, associada com
o monitor.
Programas maiores podem necessitar de seqncias de caracteres adicionais, associadas a
arquivos armazenados em uma variedade de meios fsicos tais como discos e memrias ou
ainda portas de rede e impressoras. As funes de entrada e sada mantidas em stdio.h
trabalham do mesmo modo com todas as seqncias de caracteres, mesmo aquelas que no
representam arquivos fsicos.
O acesso a uma seqncia de caracteres na linguagem C se d atravs de um ponteiro de
arquivo, cujo tipo FILE
*
, declarado em stdio.h . Algumas seqncias de caracteres so
representadas por ponteiros de arquivos com nomes padronizados, como veremos a seguir.
Podemos ainda declarar ponteiros de arquivos conforme nossas necessidades, como fazemos
abaixo:
FILE
*
pt1,
*
pt2;
Veremos mais sobre arquivos na linguagem C e as funes de suporte associadas a eles a par-
tir da seo 17.3. Em particular, veremos arquivos do sistema com nomes padronizados na
seo 17.3.5.
137
138 ARQUIVOS
A biblioteca representada pelo arquivo-cabealho stdio.h suporta dois tipos de arquivos:
texto e binrio. Os bytes em um arquivo-texto representam caracteres, fazendo que seja poss-
vel examinar e editar o seu contedo. O arquivo-fonte de um programa na linguagem C, por
exemplo, armazenado emumarquivo-texto. Por outro lado, os bytes emumarquivo-binrio
no representam necessariamente caracteres. Grupos de bytes podem representar outros tipos
de dados tais como inteiros e nmeros com ponto utuante. Um programa executvel, por
exemplo, armazenado em um arquivo-binrio.
Arquivos-texto possuem duas principais caractersticas que os diferem dos arquivos-
binrios: so divididos em linhas e podem conter um marcador especial de m de arquivo.
Cada linha do arquivo-texto normalmente termina com um ou dois caracteres especiais, de-
pendendo do sistema operacional.
17.2 Redirecionamento de entrada e sada
Como j zemos nos trabalhos da disciplina e em algumas aulas, a leitura e escrita em
arquivos podem ser facilmente executadas nos sistemas operacionais em geral. Como perce-
bemos, nenhum comando especial teve de ser adicionado aos nossos programas na linguagem
C para que a leitura e a escrita fossem executadas de/para arquivos. O que zemos at aqui
foi redirecionar a entrada e/ou a sada de dados do programa. Como um exemplo simples,
vejamos o programa 17.1, onde um nmero inteiro na base decimal fornecido como entrada
e na sada apresentado o mesmo nmero na base binria.
Programa 17.1: Converso de um nmero inteiro na base decimal para base binria.
#include <stdio.h>
int main(void)
{
int pot10, numdec, numbin;
scanf("%d", &numdec);
pot10 = 1;
numbin = 0;
while (numdec > 0) {
numbin = numbin + (numdec % 2)
*
pot10;
numdec = numdec / 2;
pot10 = pot10
*
10;
}
printf("%d\n", numbin);
return 0;
}
Supondo que o programa 17.1 tenha sido armazenado no arquivo-fonte decbin.c e o
programa executvel equivalente tenha sido criado aps a compilao com o nome decbin ,
ento, se queremos que a sada do programa executvel decbin seja armazenada no arquivo
resultado , podemos digitar em uma linha de terminal o seguinte:
FACOM UFMS
17.2 REDIRECIONAMENTO DE ENTRADA E SADA 139
prompt$ ./decbin > resultado
Esse comando instrui o sistema operacional a executar o programa decbin redirecio-
nando 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 , con-
forme especicado 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 pro-
grama 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 termi-
nal:
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 solu-
es correspondentes s entradas, podemos comparar esse arquivo de solues com as sadas
de nosso programa, que tambm podem ser armazenadas em um arquivo diferente. Utilit-
rios para comparao de contedos de arquivos disponveis no sistema operacional Linux, tais
como diff e cmp , so usados para atingir esse objetivo. Por exemplo,
prompt$ diff resultado solucao
FACOM UFMS
140 ARQUIVOS
17.3 Funes de entrada e sada da linguagem C
At esta aula, excluindo o redirecionamento de entrada e sada que vimos na seo 17.2,
tnhamos sempre armazenado quaisquer informaes na memria principal do nosso sistema
computacional. Como j mencionado, uma grande quantidade de problemas pode ser resol-
vida com as operaes de entrada e sada que conhecemos e com o redirecionamento de en-
trada e sada que acabamos de aprender na seo 17.2. Entretanto, existem problemas onde h
necessidade de obter, ou armazenar, dados de/para dois ou mais arquivos. Os arquivos so
mantidos em um dispositivo do sistema computacional conhecido como memria secundria,
que pode ser implementado como um disco rgido, um disquete, um disco compacto (CD), um
disco verstil digital (DVD), um carto de memria, um disco removvel (USB ash memory),
entre outros. A linguagemC temum conjunto de funes especcas para tratamento de arqui-
vos que se localiza na biblioteca padro de entrada e sada, cujo arquivo-cabealho stdio.h .
Na verdade, essa biblioteca contmtodas as funes que fazem tratamento de qualquer tipo de
entrada e sada de um programa, tanto da memria principal quanto secundria. Como exem-
plo, as funes printf , scanf , putchar e getchar , que conhecemos bem, encontram-se
nessa biblioteca.
17.3.1 Funes de abertura e fechamento
Para que se possa realizar qualquer operao sobre um arquivo necessrio, antes de tudo,
de abrir esse arquivo. Como um programa pode ter de trabalhar com diversos arquivos, ento
todos eles devero estar abertos durante a execuo desse programa. Dessa forma, h neces-
sidade de identicao de cada arquivo, o que implementado na linguagem C com o uso de
um ponteiro para arquivo.
A funo fopen da biblioteca padro de entrada e sada da linguagemC uma funo que
realiza a abertura de um arquivo no sistema. A funo recebe como parmetros duas cadeias
de caracteres: a primeira o identicador/nome do arquivo a ser aberto e a segunda determina
o modo no qual o arquivo ser aberto. O nome do arquivo deve constar no sistema de arquivos
do sistema operacional. A funo fopen devolve um ponteiro nico para o arquivo que pode
ser usado para identicar esse arquivo a partir desse ponto do programa. Esse ponteiro
posicionado no incio ou no nal do arquivo, dependendo do modo como o arquivo foi aberto.
Se o arquivo no puder ser aberto por algum motivo, a funo devolve o ponteiro com valor
NULL . Um ponteiro para um arquivo deve ser declarado com um tipo pr-denido FILE ,
tambmincluso no arquivo-cabealho stdio.h . Ainterface da funo fopen ento descrita
da seguinte forma:
FILE
*
fopen(char
*
nome, char
*
modo)
As opes para a cadeia de caracteres modo , parmetro da funo fopen , so descritas na
tabela a seguir:
FACOM UFMS
17.3 FUNES DE ENTRADA E SADA DA LINGUAGEM C 141
modo Descrio
r modo de leitura de texto
w modo de escrita de texto

a modo de adicionar texto

r+ modo de leitura e escrita de texto


w+ modo de leitura e escrita de texto

a+ modo de leitura e escrita de texto

rb modo de leitura em binrio


wb modo de escrita em binrio

ab modo de adicionar em binrio

r+b ou rb+ modo de leitura e escrita em binrio


w+b ou wb+ modo de leitura e escrita em binrio

a+b ou ab+ modo de leitura e escrita em binrio

onde:

trunca o arquivo existente com tamanho 0 ou cria novo arquivo;

abre ou cria o arquivo e posiciona o ponteiro no nal do arquivo.


Algumas observaes sobre os modos de abertura de arquivos se fazem necessrias. Pri-
meiro, 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 nal do arquivo, desconsiderando a posio atual do ponteiro
do arquivo. Por m, 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
ptarq = fopen("entrada", "r");
temo efeito de abrir umarquivo comnome entrada no modo de leitura. Achamada funo
fopen devolve um identicador 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 disso-
cia o identicador 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 ARQUIVOS
int fclose(FILE
*
ptarq)
Se a operao de fechamento do arquivo apontado por ptarq obtm sucesso, a funo
fclose devolve o valor 0 (zero). Caso contrrio, o valor EOF devolvido.
17.3.2 Funes de entrada e sada
A funo fgetc da biblioteca padro de entrada e sada da linguagem C permite que um
nico caracter seja lido de um arquivo. A interface dessa funo apresentada a seguir:
int fgetc(FILE
*
ptarq)
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 especicar o valor de devoluo como
sendo do tipo unsigned char , especica-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 , denida 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 m 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:
int fputc(int caracter, FILE
*
ptarq)
Se a funo fputc tem sucesso, o ponteiro ptarq incrementado e o caracter escrito
devolvido. Caso contrrio, isto , se ocorre um erro, o valor EOF devolvido.
Existe outro par de funes de leitura e escrita em arquivos com identicadores fscanf e
fprintf . As interfaces dessas funes so apresentadas a seguir:
int fscanf(FILE
*
ptarq, char
*
formato, ...)
e
int fprintf(FILE
*
ptarq, char
*
formato, ...)
Essas duas funes so semelhantes s respectivas funes scanf e printf que conhe-
cemos bem, a menos de um parmetro a mais que informado, justamente o primeiro, que
FACOM UFMS
17.3 FUNES DE ENTRADA E SADA DA LINGUAGEM C 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
fprintf(ptarq, "O nmero %d primo\n", numero);
realiza a escrita no arquivo apontado por ptarq da mensagem entre aspas duplas, substi-
tuindo o valor numrico correspondente armazenado na varivel numero . O nmero de ca-
racteres escritos no arquivo devolvido pela funo fprintf . Se um erro ocorrer, ento o
valor -1 devolvido.
Do mesmo modo,
fscanf(ptarq, "%d", &numero);
realiza a leitura de um valor que ser armazenado na varivel numero a partir de um arquivo
identicado 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 bibliogrcas do curso.
17.3.3 Funes de controle
Existemdiversas funes de controle que do suporte a operaes sobre os arquivos. Dentre
as mais usadas, listamos uma funo que descarrega o espao de armazenamento temporrio
da memria principal para a memria secundria e as funes que tratam do posicionamento
do ponteiro do arquivo.
A funo fflush faz a descarga de qualquer informao associada ao arquivo que esteja
armazenada na memria principal para o dispositivo de memria secundria associado. A
funo fflush tem a seguinte interface:
int fflush(FILE
*
ptarq)
onde ptarq o ponteiro para um arquivo. Se o ponteiro ptarq contm um valor nulo, en-
to 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 especca da biblioteca padro da linguagemC que realiza umteste de nal de arquivo.
Essa funo tem identicador feof e a seguinte interface:
FACOM UFMS
144 ARQUIVOS
int feof(FILE
*
ptarq)
O argumento da funo feof um ponteiro para um arquivo do tipo FILE . A funo
devolve um valor inteiro diferente de 0 (zero) se o ponteiro ptarq est posicionado no nal
do arquivo. Caso contrrio, a funo devolve o valor 0 (zero).
A funo fgetpos determina a posio atual do ponteiro do arquivo e tem a seguinte
interface:
int fgetpos(FILE
*
ptarq, fpos_t
*
pos)
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, denido no arquivo-cabealho stdio.h , adequado para armazena-
mento 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).
Afuno fsetpos posiciona o ponteiro de umarquivo emalguma posio escolhida e tem
a seguinte interface:
int fsetpos(FILE
*
ptarq, fpos_t
*
pos)
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:
long int ftell(FILE
*
ptarq)
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:
int fseek(FILE
*
ptarq, long int desloca, int a_partir)
FACOM UFMS
17.3 FUNES DE ENTRADA E SADA DA LINGUAGEM C 145
O argumento ptarq o ponteiro para um arquivo. O argumento desloca o nmero de
bytes a serem saltados a partir do contedo do argumento apartir . Esse contedo pode ser
um dos seguintes valores pr-denidos no arquivo-cabealho stdio.h :
SEEK_SET A partir do incio do arquivo
SEEK_CUR A partir da posio atual
SEEK_END A partir do m do arquivo
Em um arquivo de texto, o contudo de apartir dever ser SEEK_SET e o contedo de
desloca deve ser 0 (zero) ou umvalor devolvido pela funo ftell . Se a funo executada
com sucesso, o valor 0 (zero) devolvido. Caso contrrio, um valor diferente de 0 (zero)
devolvido.
A funo rewind faz com que o ponteiro de um arquivo seja posicionado para o incio
desse arquivo. A interface dessa funo a seguinte:
void rewind(FILE
*
ptarq)
17.3.4 Funes sobre arquivos
H funes na linguagem C que permitem que um programador remova um arquivo do
disco ou troque o nome de um arquivo. A funo remove tem a seguinte interface:
int remove(char
*
nome)
A funo remove elimina um arquivo, com nome armazenado na cadeia de caracteres
nome , do sistema de arquivos do sistema computacional. O arquivo no deve estar aberto no
programa. Se a remoo realizada, a funo devolve o valor 0 (zero). Caso contrrio, um
valor diferente de 0 (zero) devolvido.
A funo rename tem a seguinte interface:
int rename(char
*
antigo, char
*
novo)
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 contr-
rio, um valor diferente de 0 (zero) devolvido e o arquivo ainda pode ser identicado por seu
nome antigo.
17.3.5 Arquivos do sistema
Sempre que um programa na linguagem C executado, trs arquivos ou seqncias de
caracteres so automaticamente abertos pelo sistema, identicados pelos ponteiros stdin ,
FACOM UFMS
146 ARQUIVOS
stdout e stderr , denidos no arquivo-cabealho stdio.h da biblioteca padro de entrada
e sada. Em geral, stdin est associado ao teclado e stdout e stderr esto associados ao
monitor. Esses ponteiros so todos do tipo FILE .
O ponteiro stdin identica a entrada padro do programa e normalmente associado
ao teclado. Todas as funes de entrada denidas na linguagem C que executam entrada de
dados e no tm um ponteiro do tipo FILE como um argumento tomam a entrada a partir do
arquivo apontado por stdin . Assim, ambas as chamadas a seguir:
scanf("%d", &numero);
e
fscanf(stdin, "%d", &numero);
so equivalentes e lem um nmero do tipo inteiro da entrada padro, que normalmente o
terminal.
Do mesmo modo, o ponteiro stdout se refere sada padro, que tambm associada ao
terminal. Assim, as chamadas a seguir:
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.
Oponteiro 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 justicativa 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 EXEMPLOS 147
O primeiro exemplo, apresentado no programa 17.2, bem simples e realiza a cpia do
contedo de um arquivo para outro arquivo.
Programa 17.2: Um exemplo de cpia de um arquivo.
#include <stdio.h>
#define MAX 100
int main(void)
{
char nome_base[MAX+1], nome_copia[MAX+1];
int c;
FILE
*
ptbase,
*
ptcopia;
printf("Informe o nome do arquivo a ser copiado: ");
scanf("%s", nome_base);
printf("Informe o nome do arquivo resultante: ");
scanf("%s", nome_copia);
ptbase = fopen(nome_base, "r");
if (ptbase != NULL) {
ptcopia = fopen(nome_copia, "w");
if (ptcopia != NULL) {
c = fgetc(ptbase);
while (c != EOF) {
fputc(c, ptcopia);
c = fgetc(ptbase);
}
fclose(ptbase);
fclose(ptcopia);
printf("Arquivo copiado\n");
}
else {
printf("Impossvel abrir o arquivo %s para escrita\n", nome_copia);
}
}
else {
printf("Impossvel abrir o arquivo %s para leitura\n", nome_base);
}
return 0;
}
O segundo exemplo, apresentado no programa 17.3, mescla o contedo de dois arquivos
texto em um arquivo resultante. Neste exemplo, cada par de palavras do arquivo resultante
composto por uma palavra de um arquivo e uma palavra do outro arquivo. Se um dos
arquivos contiver menos palavras que o outro arquivo, esse outro arquivo descarregado no
arquivo resultante.
FACOM UFMS
148 ARQUIVOS
Programa 17.3: Um segundo exemplo de uso de arquivos.
#include <stdio.h>
#define MAX 100
int main(void)
{
char nome1[MAX+1], nome2[MAX+1], palavra1[MAX+1], palavra2[MAX+1];
int i1, i2;
FILE
*
pt1,
*
pt2,
*
ptr;
printf("Informe o nome do primeiro arquivo: ");
scanf("%s", nome1);
printf("Informe o nome do segundo arquivo: ");
scanf("%s", nome2);
pt1 = fopen(nome1, "r");
pt2 = fopen(nome2, "r");
if (pt1 != NULL && pt2 != NULL) {
ptr = fopen("mesclado", "w");
if (ptr != NULL) {
i1 = fscanf(pt1, "%s", palavra1);
i2 = fscanf(pt2, "%s", palavra2);
while (i1 != EOF && i2 != EOF) {
fprintf(ptr, "%s %s ", palavra1, palavra2);
i1 = fscanf(pt1, "%s", palavra1);
i2 = fscanf(pt2, "%s", palavra2);
}
while (i1 != EOF) {
fprintf(ptr, "%s ", palavra1);
i1 = fscanf(pt1, "%s", palavra1);
}
while (i2 != EOF) {
fprintf(ptr, "%s ", palavra2);
i2 = fscanf(pt2, "%s", palavra2);
}
fprintf(ptr, "\n");
fclose(pt1);
fclose(pt2);
fclose(ptr);
printf("Arquivo mesclado\n");
}
else
printf("No possvel abrir um arquivo para escrita\n");
}
else
printf("No possvel abrir os arquivos para leitura\n");
return 0;
}
FACOM UFMS
17.4 EXEMPLOS 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 lexicogracamente. Por
exemplo, esses arquivos podem conter nomes de pessoas, linha a linha, em ordem al-
fabtica. 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 lexicogracamente. Por exemplo, se os arquivos contm as
linhas
Arquivo 1 Arquivo 2
Antnio Carlos
Berenice Clia
Diana Fbio
Solange Henrique
Snia
Zuleica
o arquivo resultante deve ser
Antnio
Berenice
Carlos
Clia
Diana
Fbio
Henrique
Solange
Snia
Zuleica
FACOM UFMS
150 ARQUIVOS
FACOM UFMS
AULA 18
LISTAS LINEARES
Listas lineares so as prximas estruturas de dados que aprendemos. Essas estruturas so
muito usadas em diversas aplicaes importantes para organizao de informaes na me-
mria tais como representaes alternativas para expresses aritmticas, armazenamento de
argumentos de funes, compartilhamento de espao de memria, entre outras. Nesta aula,
baseada nas referncias [2, 7, 13], aprenderemos a denio dessa estrutura, sua implementa-
o em alocao dinmica e suas operaes bsicas.
18.1 Denio
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 umregistro 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 c
1
, c
2
, . . . , c
n
determi-
nada pela ordem relativa desses elementos:
(i) se n > 0 ento c
1
a primeira clula;
(ii) a clula c
i
precedida pela clula c
i1
, para todo i, 1 < i n.
As operaes bsicas sobre uma lista linear so as seguintes:
busca;
incluso; e
remoo.
151
152 LISTAS 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, orde-
nao 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 dene o tipo de armazenamento a
ser usado, dependendo das operaes sobre a lista, do nmero de listas envolvidas e das carac-
tersticas 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 gura 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 denio de uma clula de uma lista linear encadeada ento descrita como a seguir:
struct cel {
int chave;
struct cel
*
prox;
};
boa prtica de programao denir um novo tipo de dados para as clulas de uma lista
linear em alocao encadeada:
typedef struct cel celula;
Uma clula c e um ponteiro p para uma clula podem ser declarados da seguinte forma:
celula c;
celula
*
p;
FACOM UFMS
18.1 DEFINIO 153
Se c uma clula ento c.chave o contedo da clula e c.prox o endereo da clula
seguinte. Se p o endereo de uma clula ento p->chave o contedo da clula apontada
por p e p->prox o endereo da clula seguinte. Se p o endereo da ltima clula da lista
ento p->prox vale NULL .
Uma ilustrao de uma lista linear em alocao encadeada mostrada na gura 18.2.
p
5 9 2 7 1 3
Figura 18.2: Representao de uma lista linear em alocao encadeada na memria.
Dizemos que o endereo de uma lista encadeada o endereo de sua primeira clula. Se
p o endereo de uma lista, podemos dizer que p uma lista ou ainda considere a lista
p . Por outro lado, quando dizemos p uma lista, queremos dizer que p o endereo
da primeira clula de uma lista.
Uma lista linear pode ser vista de duas maneiras diferentes, dependendo do papel que sua
primeira clula representa. Em uma lista linear com cabea, a primeira clula serve apenas
para marcar o incio da lista e portanto o seu contedo irrelevante. A primeira clula a
cabea da lista. Em uma lista linear sem cabea o contedo da primeira clula to relevante
quanto o das demais.
Uma lista linear est vazia se no temclula alguma. Para criar uma lista vazia lista com
cabea, basta escrever as seguintes sentenas:
celula c,
*
lista;
c.prox = NULL;
lista = &c;
ou ainda
celula
*
lista;
lista = (celula
*
) malloc(sizeof (celula));
lista->prox = NULL;
A gura 18.3 mostra a representao na memria de uma lista linear encadeada comcabea
e vazia.
lista
Figura 18.3: Uma lista linear encadeada com cabea e vazia lista.
FACOM UFMS
154 LISTAS LINEARES
Para criar uma lista vazia lista sem cabea, basta escrever as seguintes sentenas:
celula
*
lista;
lista = NULL;
A gura 18.4 mostra a representao na memria de uma lista linear encadeada semcabea
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 me-
mria.
Para imprimir o contedo de todas as clulas de uma lista linear podemos usar a seguinte
funo:
/
*
Recebe um ponteiro para uma lista linear encadeada
e mostra o contedo de cada uma de suas clulas
*
/
void imprime_lista(celula
*
lst)
{
celula
*
p;
for (p = lst; p != NULL; p = p->prox)
printf("%d\n", p->chave);
}
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);
A seguir vamos discutir e implementar as operaes bsicas de busca, insero e remoo
sobre listas lineares em alocao encadeada. Para isso, vamos dividir o restante do captulo em
listas sem cabea e com cabea.
FACOM UFMS
18.2 LISTAS LINEARES COM CABEA 155
18.2 Listas lineares com cabea
Nesta seo tratamos das operaes bsicas nas listas lineares encadeadas com cabea.
Como mencionamos, as operaes bsicas so facilmente implementadas neste tipo de lista,
apesar de sempre haver necessidade de manter uma clula a mais sem armazenamento de
informaes na lista.
18.2.1 Busca
O processo de busca de um elemento em uma lista linear em alocao encadeada com ca-
bea muito simples. Basta vericar 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 ende-
reo 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 devol-
ve 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 -
gura 18.5.
2 4 1 3 6
1
lst
p x
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 LISTAS LINEARES
/
*
Recebe um nmero inteiro x e uma lista encadeada com cabea lst e devol-
ve 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 fundamen-
tal: 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 comn 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 re-
move 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 remo-
ve 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);
}
A gura 18.6 ilustra a execuo dessa funo.
Observe que no h movimentao de dados na memria, como zemos na aula 4 ao remo-
ver um elemento de um vetor.
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 LISTAS LINEARES COM CABEA 157
2 4 1 3 6
2 4 1 3 6
2 4 1 3 6
2 4 3 6
lixo
lixo
lixo
p
p
p
p
(i)
(ii)
(iii)
(iv)
Figura 18.6: Resultado de uma chamada da funo remove_C para uma clula apontada por
p de uma dada lista linear. As guras de (i) a (iv) representam a execuo linha a linha da
funo.
FACOM UFMS
158 LISTAS LINEARES
/
*
Recebe um nmero inteiro y e o endereo p de uma clula de
uma lista encadeada e insere uma nova clula com contedo
y entre a clula p e a seguinte; supomos que p != NULL
*
/
void insere_C(int y, celula
*
p)
{
celula
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->chave = y;
nova->prox = p->prox;
p->prox = nova;
}
A gura 18.7 ilustra a execuo dessa funo.
1
1
3 6 1
1
1
2 4 3 6
2 4 3 6
2 4
nova
nova
y
y
y
p
p
p
(i)
(ii)
(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.
Observe que tambm no h movimentao de dados na memria na insero realizada
nessa funo.
FACOM UFMS
18.2 LISTAS LINEARES COM CABEA 159
18.2.4 Busca com remoo ou insero
Nesta seo, temos dois problemas a serem resolvidos. No primeiro, dado um nmero
inteiro, nosso problema agora aquele de remover a clula de uma lista linear encadaeda que
contenha tal nmero. Se tal clula no existe, nada fazemos. A funo busca_remove_C
apresentada a seguir implementa esse processo. A gura 18.8 ilustra a execuo dessa funo.
/
*
Recebe um nmero inteiro x e uma lista encadeada com cabea lst e remo-
ve 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 nal da lista. A funo
busca_remove_C apresentada a seguir implementa esse processo. Um exemplo de execuo
dessa funo ilustrado na gura 18.8.
/
*
Recebe dois nmeros inteiros y e x e uma lista encadeada
com cabea lst e insere uma nova clula com chave y nessa
lista antes da primeira que contiver x; se nenhuma clula
contiver x, a nova clula inserida no final da lista
*
/
void busca_insere_C(int y, int x, celula
*
lst)
{
celula
*
p,
*
q,
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->chave = y;
p = lst;
q = lst->prox;
while (q != NULL && q->chave != x) {
p = q;
q = q->prox;
}
nova->prox = q;
p->prox = nova;
}
FACOM UFMS
160 LISTAS LINEARES
2 4 1 3 6
2 4 1 3 6
2 4 1 3 6
2 4 3 6
1
1
1
1
p
p
p
p
q
q
q
q x
x
x
x
lst
lst
lst
lst
(i)
(ii)
(iii)
(iv)
Figura 18.8: Resultado de uma chamada da funo busca_remove_C para a chave 1 e uma
lista lst . As guras de (i) a (iv) representam a execuo linha a linha da funo.
FACOM UFMS
18.2 LISTAS LINEARES COM CABEA 161
1
1
3
3 1
3 6 1
1
1 3
3
1
3 6
2 4 3 6
2 4 3 6
2 4
2 4
p
p
p q
q
q
x
x
x
x
y
y
y
y
lst
lst
lst
lst
nova
nova
nova
(i)
(ii)
(iii)
(iv)
Figura 18.9: Resultado de uma chamada da funo busca_insere_C para as chaves 1 e 3 e
uma lista lst . As guras de (i) a (iv) representam a execuo linha a linha da funo.
FACOM UFMS
162 LISTAS LINEARES
18.3 Listas lineares sem cabea
Nesta seo tratamos das operaes bsicas nas listas lineares encadeadas sem cabea.
Como mencionamos, sempre economizamos uma clula em listas desse tipo, mas a imple-
mentao as operaes bsicas so um pouco mais complicadas.
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 ca-
bea. 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 devol-
ve 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 devol-
ve 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);
}
18.3.2 Busca com remoo ou insero
Nesta seo apresentamos uma funo que realiza uma busca seguida de remoo em uma
lista linear encadeada sem cabea.
H muitas semelhanas entre a funo de busca seguida de remoo em uma lista linear
encadeada com cabea e sem cabea. No entanto, devemos reparar na diferena fundamental
entre as duas implementaes: uma remoo em uma lista linear encadeada sem cabea pode
modicar o ponteiro que identica a lista quando removemos a primeira clula da lista. Dessa
forma, a funo busca_remove_S descrita abaixo tem como parmetro que identica a lista
linear um ponteiro para um ponteiro.
FACOM UFMS
18.3 LISTAS LINEARES SEM CABEA 163
/
*
Recebe um nmero inteiro x e uma lista encadeada com cabea lst e remo-
ve 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);
}
}
Veja a gura 18.10 para um exemplo de busca seguida de remoo.
5
5
5
1 7 6 5 3 4
1 7 6 3 4
1 7 6 5 3 4
*
lst
*
lst
*
lst p
p
p q
q
q
x
x
x
(i)
(ii)
(iii)
Figura 18.10: Remoo de uma chave em uma lista linear encadeada sem cabea. (i) Aps a
busca do valor. (ii) Modicao do ponteiro p->prox . (iii) Liberao da memria.
FACOM UFMS
164 LISTAS LINEARES
Uma chamada funo busca_remove_S ilustrada abaixo, para um nmero inteiro x e
uma lista :
busca_remove_S(x, &lista);
Observe que os ponteiros p e q so variveis locais funo busca_remove_S , que au-
xiliam 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 c-
lula 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 modicar o ponteiro que iden-
tica a lista quando inserimos uma nova clula em uma lista vazia. Dessa forma, a funo
busca_insere_S descrita abaixo tem como parmetro que identica a lista linear um pon-
teiro para um ponteiro.
/
*
Recebe dois nmeros inteiros y e x e uma lista encadeada
com cabea lst e insere uma nova clula com chave y nessa
lista antes da primeira que contiver x; se nenhuma clula
contiver x, a nova clula inserida no final da lista
*
/
void busca_insere_S(int y, int x, celula
**
lst)
{
celula
*
p,
*
q,
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->chave = y;
p = NULL;
q =
*
lst;
while (q != NULL && q->chave != x) {
p = q;
q = q->prox;
}
nova->prox = q;
if (p != NULL)
p->prox = nova;
else
*
lst = nova;
}
Uma chamada funo busca_insere_S pode ser feita como abaixo, para nmeros intei-
ros y e x e uma lista :
busca_insere_S(y, x, &lista);
FACOM UFMS
18.3 LISTAS LINEARES SEM CABEA 165
Exerccios
18.1 Se conhecemos apenas o ponteiro p para uma clula de uma lista linear em alocao
encadeada, como na gura 18.11, e nada mais conhecido, como podemos modicar
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 4 5 19 47
Figura 18.11: Uma lista linear encadeada com um ponteiro p .
18.2 O esquema apresentado na gura 18.12 permite percorrer uma lista linear encadeada
nos dois sentidos, usando apenas o campo prox que contm o endereo do prximo
elemento da lista. Usamos dois ponteiros esq e dir , que apontam para dois elementos
vizinhos da lista. A idia desse esquema que medida que os ponteiros esq e dir
caminham na lista, os campos prox so invertidos de maneira a permitir o trfego nos
dois sentidos.
esq
esq
dir
dir
(i)
(ii)
Figura 18.12: A gura (ii) foi obtida de (i) atravs da execuo da funo dada no exerccio
18.2(a) por trs vezes.
FACOM UFMS
166 LISTAS LINEARES
Escreva funes para:
(a) mover esq e dir para a direita de uma posio.
(b) mover esq e dir para a esquerda de uma posio.
18.3 Que acontece se trocarmos while (p != NULL && p->chave != x) por
while (p->chave != x && p != NULL na funo busca_C ?
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 m da lista e realize a insero desse
valor no nal da lista. Faa uma verses para listas lineares com cabea e sem cabea.
18.6 (a) Escreva duas funes: uma que copie umvetor 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, respectiva-
mente, emuma lista linear comessa 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 cres-
cente. 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 tam-
bm 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 c
1
, c
2
, . . . ,
c
n
, 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 modique e devolva essa lista
de tal forma que a lista resultante contenha as chaves c
2
, c
3
, . . . , c
n
, c
1
, nessa ordem.
(b) Escreva uma funo inverte que receba uma lista e modique e devolva essa lista
de tal forma que a lista resultante contenha as chaves c
n
, c
n1
, . . . , c
2
, c
1
, nessa or-
dem.
(c) Escreva uma funo soma que receba uma lista e modique e devolva essa lista de
tal forma que a lista resultante contenha as chaves c
1
+c
n
, c
2
+c
n1
, . . . , c
n/2
+c
n/2+1
,
nessa ordem. Considere n par.
FACOM UFMS
18.3 LISTAS LINEARES SEM CABEA 167
18.12 SejamS
1
e S
2
dois conjuntos disjuntos de nmeros inteiros. Suponha que S
1
e S
2
esto im-
plementados emduas listas lineares emalocao encadeada. Escreva uma funo uniao
que receba as listas representando os conjuntos S
1
e S
2
e devolva uma lista resultante que
representa a unio dos conjuntos, isto , uma lista linear encadeada que representa o con-
junto S = S
1
S
2
. Considere os casos emque as listas lineares encadeadas so comcabea
e sem cabea.
18.13 Seja um polinmio p(x) = a
0
x
n
+ a
1
x
n1
+ . . . + a
n
, com coecientes de ponto utuante.
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 utu-
ante x0 e calcule e devolva p(x
0
).
(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 LISTAS LINEARES
FACOM UFMS
AULA 19
PILHAS
Quando tratamos de listas lineares, o armazenamento seqencial usado, emgeral, quando
essas estruturas sofrempoucas modicaes ao longo do tempo de execuo emuma aplicao,
com poucas inseres e remoes realizadas durante sua existncia. Isso implica claramente
em poucas movimentaes de clulas. Por outro lado, a alocao encadeada mais usada em
situaes inversas, ou seja, quando modicaes nas listas lineares so mais freqentes.
Caso os elementos a serem inseridos ou removidos de uma lista linear se localizem em
posies especiais nessas estruturas, como por exemplo a primeira ou a ltima posio, ento
temos uma situao favorvel para o uso de listas lineares emalocao seqencial. Este o caso
da estrutura de dados denominada pilha. O que torna essa lista linear especial a adoo de
uma poltica bemdenida de inseres e remoes, sempre emumdos extremos da lista. Alm
da alocao seqencial, a implementao de uma pilha em alocao encadeada tem muitas
aplicaes importantes e tambm ser discutida nesta aula.
Esta aula baseada nas referncias [2, 13].
19.1 Denio
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 nal 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 desempilha-
mento de elementos. Nenhuma outra operao realizada sobre uma pilha, a no ser em casos
especcos. 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 PILHAS
19.2 Operaes bsicas em alocao seqencial
Uma pilha em alocao seqencial descrita atravs de duas variveis: um vetor e um
ndice para o vetor. Supomos ento que a pilha esteja armazenada no vetor P[0..MAX-1] e
que a parte do vetor efetivamente ocupada pela pilha seja P[0..t] , onde t o ndice que
dene o topo da pilha. Os compartimentos desse vetor so convencionalmente do tipo int ,
mas podemos deni-los de qualquer tipo. Uma ilustrao de uma pilha emalocao seqencial
dada na gura 19.1.
P
0
1
1
2
2
3
3
8
t MAX-1
Figura 19.1: Representao de uma pilha P com topo t em alocao seqencial.
Convencionamos que uma pilha est vazia se seu topo t vale 1 e cheia se seu topo t
vale MAX-1 . As representaes de pilhas vazia e cheia so mostradas na gura 19.2.
P P -1
0 0 1
1
1 2
2
2 3
3
3
7 8
t t MAX-1
MAX-1
MAX-1
(a) (b)
Figura 19.2: (a) Pilha vazia. (b) Pilha cheia.
A declarao de uma pilha e sua inicializao so mostradas abaixo:
int t, P[MAX];
t = -1;
Suponha agora que queremos inserir um elemento de valor 7 na pilha P da gura 19.1.
Como resultado desta operao, esperamos que a pilha tenha a congurao mostrada na -
gura 19.3 aps sua execuo.
P
0
1
1 2
3 3
3
8 7
t MAX-1
Figura 19.3: Insero da chave 7 na pilha P da gura 19.1.
A operao de inserir um objeto em uma pilha, ou empilhar, descrita na funo
empilha_seq a seguir.
FACOM UFMS
19.2 OPERAES BSICAS EM ALOCAO SEQENCIAL 171
/
*
Recebe um ponteiro t para o topo de uma pilha P
e um elemento y e insere y no topo da pilha P
*
/
void empilha_seq(int
*
t, int P[MAX], int y)
{
if (
*
t != MAX - 1) {
(
*
t)++;
P[
*
t] = y;
}
else
printf("Pilha cheia!\n");
}
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 congurao ilus-
trada na gura 19.1 tem como resultado a pilha com a congurao mostrada na gura 19.4
aps a sua execuo.
P
0
1
1
2
2
3
3 t MAX-1
Figura 19.4: Remoo de um elemento da pilha P da gura 19.1.
A operao de remover, ou desempilhar, um objeto de uma pilha descrita na funo
desempilha_seq a seguir.
/
*
Recebe um ponteiro t para o topo de uma pilha
P e remove um elemento do topo da pilha P
*
/
int desempilha_seq(int
*
t, int P[MAX])
{
int r;
if (
*
t != -1) {
r = P[
*
t];
(
*
t)--;
} else {
r = INT_MIN;
printf("Pilha vazia!\n");
}
return r;
}
Diversas so as aplicaes que necessitamde uma ou mais pilhas nas suas solues. Dentre
elas, podemos citar a converso de notao de expresses aritmticas (notao prexa, inxa
e posxa), 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 PILHAS
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 verica essa
propriedade para uma seqncia de caracteres usando uma pilha em alocao seqencial.
/
*
Recebe, via entrada padro, uma seqncia de caracteres e ve-
rifica 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;
}
19.3 Operaes bsicas em alocao encadeada
Existem muitas aplicaes prticas onde as pilhas implementadas em alocao encadeada
so importantes e bastante usadas. Consideramos que as clulas da pilha so do tipo abaixo:
typedef struct cel {
int chave;
struct cel
*
prox;
} celula;
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 OPERAES BSICAS EM ALOCAO ENCADEADA 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 gura 19.5.
t 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.
/
*
Recebe um elemento y e uma pilha t e insere y no topo de t
*
/
void empilha_enc_C(int y, celula
*
t)
{
celula
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->conteudo = y;
nova->prox = t->prox;
t->prox = nova;
}
A operao de desempilhar uma clula da pilha descrita na funo desempilha_enc_C .
/
*
Recebe uma pilha t e remove um elemento de seu topo
*
/
int desempilha_enc_C(celula
*
t)
{
int x;
celula
*
p;
if (t->prox != NULL) {
p = t->prox;
x = p->conteudo;
t->prox = p->prox;
free(p);
return x;
}
else {
printf("Pilha vazia!\n");
return INT_MIN;
}
}
FACOM UFMS
174 PILHAS
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 ve-
rifica 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 empi-
lhar 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 eciente para reconhe-
cer se uma dada palavra palndromo.
19.3 Um estacionamento possui um nico corredor que permite dispor 10 carros. Existe so-
mente uma nica entrada/sada do estacionamento em um dos extremos do corredor. Se
umcliente 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 ordemque estavamoriginalmente. Escreva umalgoritmo que processa o uxo
FACOM UFMS
19.3 OPERAES BSICAS EM ALOCAO ENCADEADA 175
de chegada/sada deste estacionamento. Cada entrada para o algoritmo contm uma le-
tra E para entrada ou S para sada, e o nmero da placa do carro. Considere que os
carros chegam e saem pela ordem especicada na entrada. O algoritmo deve imprimir
uma mensagemsempre que umcarro chega ou sai. Quando umcarro chega, a mensagem
deve especicar 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 estaciona-
mento, a mensagem deve incluir o nmero de vezes que o carro foi movimentado para
fora da garagem, para permitir que outros carros pudessemsair.
19.4 Considere o problema de decidir se uma dada seqncia de parnteses e chaves bem-
formada. Por exemplo, a seqncia abaixo:
( ( ) { ( ) } )
bem-formada, enquanto que a seqncia
( { ) }
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
emnotao inxa, isto , na notao emque os operadores binrios aparecementre os ope-
randos. Outra notao freqentemente usada emcompiladores a notao posxa, 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 inxa notao posxa
(A + B
*
C) A B C
*
+
(A
*
(B + C) / D + E) A B C +
*
D / E -
(A + B
*
C / D
*
E - F) A B C
*
D / E
*
+ F -
Escreva uma funo que receba um cadeia de caracteres contendo uma expresso aritm-
tica em notao inxa e devolva uma cadeia de caracteres contendo a mesma expresso
aritmtica em notao posxa. Considere que a cadeia de caracteres da expresso inxa
contm apenas letras, parnteses e os smbolos +, -,
*
e /. Considere tambm que cada
varivel tem apenas uma letra e que a cadeia de caracteres inxa sempre est envolvida
por um par de parnteses.
19.6 Suponha que exista um nico vetor M de cluas de um tipo pilha pr-denido, 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 ecientemente
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 PILHAS
FACOM UFMS
AULA 20
FILAS
Uma la , assim como uma pilha, uma lista linear especial, onde h uma poltica de in-
seres e remoes bem denida. As nicas operaes realizadas sobre as las so exatamente
insero e remoo. H dois extremos em uma la 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 denio formal para essas estruturas de dados e como
implementar as operaes bsicas sobre elas, considerando sua implementao em alocao
seqencial e encadeada.
20.1 Denio
Uma la 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 fun-
cionamento dessa estrutura pode ser comparado a qualquer la que usamos com freqncia
como, por exemplo, uma la de um banco. Em geral, as pessoas entram, ou so inseridas, no
nal da la e as pessoas saem, ou so removidas, do incio da la.
Note que uma la tem sempre um indicador de onde comea e de onde termina. Quando
queremos inserir um elemento na la, esta operao realizada no nal da estrutura. Quando
queremos remover um elemento da la, essa operao realizada no incio da estrutura. Se a
la implementada emalocao seqencial ento esses extremos so ndices de umvetor. Caso
contrrio, se for implementada em alocao encadeada, ento esses extremos so ponteiros
para a primeira e a ltima clulas da la.
Assim como nas pilhas, duas operaes bsicas so realizadas sobre uma la: insero e
remoo. Essas duas operaes so tambm chamadas de enleiramento e desenleiramento
de elementos. Observe que a operao de busca no foi mencionada e no faz parte do conjunto
de operaes bsicas de uma la.
20.2 Operaes bsicas em alocao seqencial
A implementao mais simples de uma la em alocao seqencial armazenada em um
segmento F[i..f-1] de um vetor F[0..MAX-1] , com 0 i f MAX . O primeiro
elemento da la est na posio i e o ltimo na posio f-1 . Uma ilustrao de uma la
mostrada na gura 20.1.
177
178 FILAS
F 0
0
1
1
2
2
3
3
i f
MAX-1
Figura 20.1: Representao de uma la F em alocao seqencial.
Convecionamos que uma la est vazia se seus marcadores de incio e m so tais que
i = f e cheia se seu marcador de m f vale MAX . O problema de controle de espaos
disponveis na la surge naturalmente numa implementao como esta. Representaes de
las vazia e cheia em alocao seqencial mostrada na gura 20.2.
F
F 0
0
0 0
0
1
1
1
2
2
3
3
3
8 7 6 i
i
f
f
MAX-1
MAX-1
MAX
(a)
(b)
Figura 20.2: Representao de uma la em alocao seqencial (a) vazia e (b) cheia.
A declarao de uma la 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 la F da gura 20.1. A insero
de uma nova chave em uma la sempre realizada no m da estrutura. Como resultado desta
operao, esperamos que a la tenha a congurao mostrada na gura 20.3 aps a execuo
dessa operao.
F 0
0
1
1 2
3 3
3
8 i f
MAX-1
Figura 20.3: Insero da chave 8 na la F da gura 20.1.
FACOM UFMS
20.2 OPERAES BSICAS EM ALOCAO SEQENCIAL 179
A operao de inserir, ou enleirar, uma chave em uma la dada pela funo a seguir.
/
*
Recebe o ndice f do fim da fila F e a chave y e insere y no fim de F
*
/
void enfileira_seq(int
*
f, int F[MAX], int y)
{
if (
*
f != MAX) {
F[
*
f] = y;
(
*
f)++;
}
else
printf("Fila cheia!\n");
}
Suponha agora que queremos remover um elemento da la F da gura 20.3. A remoo
de um elemento ou de uma chave sempre realizada no incio da la. Como resultado desta
operao, esperamos que a la tenha a congurao ilustrada na gura 20.3 aps terminada
sua execuo.
F
0
1 1
1 2
3
3
8 i f
MAX-1
Figura 20.4: Remoo de um elemento da la F da gura 20.3.
A operao de remover, ou desenleirar, uma chave emuma la implementada na funo
abaixo.
/
*
Recebe os ndices i e f da fila F e remove a chave do incio i de F
*
/
int desenfileira_seq(int
*
i, int f, int F[MAX])
{
int x;
if (
*
i != f) {
x = F[
*
i];
(
*
i)++;
}
else {
x = INT_MIN;
printf("Fila vazia!\n");
}
return x;
}
De acordo comas implementaes das operaes sobre a la, observe que o ndice i move-
se somente quando da remoo de uma clula da la e o ndice f move-se somente quando
da insero de uma clula na la. Esses incrementos fazem com que a la movimente-se
da esquerda para direita, o que pode ocasionar a falsa impresso de falta de capacidade de
armazenamento na la, como mostramos na gura 20.5.
FACOM UFMS
180 FILAS
F
0
1
1 2
3
3 4 5 6
7
7
8
8 9
10 i f
Figura 20.5: Representao de uma la falsamente cheia.
Consideramos daqui por diante que as clulas so alocadas seqencialmente como se es-
tivessem em um crculo, onde o compartimento F[MAX-1] seguido pelo compartimento
F[0] . Assim, os elementos da la 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 i < MAX e 0 f MAX . Com essas
suposies, dizemos que uma la est vazia se i = INT_MIN e f = INT_MAX e est cheia se
f = i .
As operaes bsicas em uma la circular so descritas a seguir.
/
*
Recebe os ndice i e f da fila F e a chave y e insere y no fim de F
*
/
void enfileira_seq_2(int
*
i, int
*
f, int F[MAX], int y)
{
if (
*
f !=
*
i) {
if (
*
f == INT_MAX) {
*
i = 0;
*
f = 0;
}
F[
*
f] = y;
*
f = (
*
f + 1) % MAX;
}
else
printf("Fila cheia!\n");
}
/
*
Recebe os ndices i e f da fila F e remove a chave do incio de F
*
/
int desenfileira_seq_2(int
*
i, int
*
f, int F[MAX])
{
int r;
r = INT_MIN;
if (
*
i != INT_MIN) {
r = F[
*
i];
*
i = (
*
i + 1) % MAX;
if (
*
i ==
*
f) {
*
i = INT_MIN;
*
f = INT_MAX;
}
}
else
printf("Fila vazia!\n");
return r;
}
FACOM UFMS
20.3 OPERAES BSICAS EM ALOCAO ENCADEADA 181
20.3 Operaes bsicas em alocao encadeada
Uma la tem sempre dois marcadores de onde comea e termina. A poltica de armaze-
namento de elementos nas clulas de uma la estabelece que quando queremos inserir um
elemento, sempre o fazemos no extremo identicado como o nal dessa estrutura. Por outro
lado, quando queremos remover um elemento, essa operao sempre realizada no extremo
identicado como incio da estrutura. Em alocao encadeada, esses extremos so referencia-
dos atravs de ponteiros para clulas que contmo endereo das suas primeira e ltima clulas.
Assimcomo nas pilhas, apenas duas operaes bsicas so realizadas sobre as las: insero
e remoo. Essas duas operaes so tambm chamadas de enleiramento e deseleiramento
de elementos. Observe que a operao bsica de busca no foi mencionada e tambm no faz
parte do conjunto de operaes bsicas de uma la.
Um exemplo de uma la em alocao encadeada dado na gura 20.6.
i f
3 1 7 8
Figura 20.6: Representao de uma la em alocao encadeada com cabea.
Consideramos, como sempre zemos, que as clulas da pilha so do tipo abaixo:
typedef struct cel {
int chave;
struct cel
*
prox;
} celula;
A inicializao de uma la vazia em alocao encadeada com cabea dada a seguir:
celula
*
i,
*
f;
i = (celula
*
) malloc(sizeof (celula));
i->prox = NULL;
f = i;
e sem cabea dada a seguir:
celula
*
i,
*
f;
i = NULL;
f = NULL;
FACOM UFMS
182 FILAS
Exemplos de las vazias com cabea e sem cabea so mostrados na gura 20.7.
i i f f
(a)
(b)
Figura 20.7: Representao de uma la emalocao encadeada (a) comcabea e (b) semcabea.
As operaes bsicas em uma la com cabea so dadas abaixo.
/
*
Recebe uma fila i e f e uma chave y e enfileira y na fila
*
/
void enfileira_enc_C(celula
*
i, celula
**
f, int y)
{
celula
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->chave = y;
nova->prox = NULL;
(
*
f)->prox = nova;
*
f = nova;
}
/
*
Recebe uma fila i e f e desenfileira um elemento da fila
*
/
int desenfileira_enc_C(celula
*
i, celula
**
f)
{
int x;
celula
*
p;
x = INT_MIN;
p = i->prox;
if (p != NULL) {
x = p->chave;
i->prox = p->prox;
free(p);
if (i->prox == NULL)
*
f = i;
}
else
printf("Fila vazia!\n");
return x;
}
Exerccios
20.1 Implemente as funes de enleiramento e desenleiramento em uma la em alocao
encadaeda sem cabea.
FACOM UFMS
20.3 OPERAES BSICAS EM ALOCAO ENCADEADA 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 or-
dem que estavam originalmente. Sempre que um carro sai, todos os carros do sul so
movidos para frente, de modo que as vagas quem disponveis sempre no extremo sul
do estacionamento. Escreva um algoritmo que processa o uxo 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 especicada na entrada. O algoritmo deve imprimir uma mensagem sempre que
um carro chega ou sai. Quando um carro chega, a mensagem deve especicar 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 dis-
ponvel, 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 gara-
gem, 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 la usando duas pilhas.
20.4 Implemente uma pilha usando duas las.
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 denida da
seguinte forma: A[x][y] vale 1 se existe estrada da cidade x para a cidade y e vale 0 emcaso
contrrio. A gura 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
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
0
0
0
0 0
1 1
1 1
1
1
1 1
1
1
1
1
1
2
2
2
2
2
3
3
3
3
3
4
4
4
4
5
5
5
5
6 d
Figura 20.8: A matriz representa cidades 0, . . . , 5 interligadas por estradas de mo nica. O
vetor d d as distncias da cidade 3 a cada uma das demais.
Solucione o problema das distncias em uma rede usando uma la em alocao seqen-
cial. Solucione o mesmo problema usando uma la em alocao encadeada.
20.6 Imagine um tabuleiro quadrado 10por10. As casas livres so marcadas com 0 e as
casas bloqueadas so marcadas com 1. As casas (1, 1) e (10, 10) esto livres. Ajude
FACOM UFMS
184 FILAS
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 m do deque, uma que realiza a remoo de um elemento no incio do
deque e uma que realiza a remoo de um elemento no m do deque.
20.8 Suponha que exista um nico vetor M de clulas de um tipo la pr-denido, com um
total de MAX posies. Este vetor far o papel da memria do computador. Este vetor M
ser compartilhado por duas las em alocao seqencial. Implemente ecientemente as
operaes de enleiramento e desenleiramento para as duas las de modo que nenhuma
delas estoure sua capacidade de armazenamento, a menos que o total de elementos em
ambas as las seja MAX.
FACOM UFMS
AULA 21
LISTAS LINEARES CIRCULARES
Algumas operaes bsicas em listas lineares, em especial aquelas implementadas em alo-
cao encadeada, no so muito ecientes. Um exemplo emblemtico desse tipo de operao
a busca, mais eciente e mais econmica em listas lineares em alocao seqencial como pu-
demos perceber em aulas anteriores. As listas lineares circulares realizam algumas operaes
ainda mais ecientemente, j que possuem uma informao a mais que as listas lineares ordi-
nrias.
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 per-
mite que se identique um m, ou mesmo um comeo, da lista. Caso exista uma clula cabea
da lista, ento possvel identicar facilmente um incio e um m 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.
21.1 Operaes bsicas em alocao encadeada
A adio de uma informao na estrutura de uma lista linear em alocao encadeada per-
mite que operaes sejam realizadas de forma mais eciente. Quando a ltima clula da lista
linear encadeada aponta para a primeira clula, temos uma lista linear circular. Dessa forma,
no podemos identicar um m da lista linear. No entanto, o que aparenta uma diculdade ,
na verdade, uma virtude desse tipo de estrutura, como veremos nas funes que implementam
suas operaes bsicas de busca, insero e remoo. Veja a gura 21.1 para um exemplo de
uma lista linear circular com cabea em alocao encadeada.
lista
3 4 5 6 7
Figura 21.1: Representao de uma lista linear circular com cabea em alocao encadeada.
185
186 LISTAS LINEARES CIRCULARES
A denio de um tipo clula da lista linear circular permanece a mesma:
typedef struct cel {
int chave;
struct cel
*
prox;
} celula;
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 gura 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 comcabea emalocao encadeada. Aimplementao das operaes bsicas
em uma lista linear circular sem cabea em alocao encadeada deixada como exerccio.
FACOM UFMS
21.1 OPERAES BSICAS EM ALOCAO ENCADEADA 187
21.1.1 Busca
Imagine que queremos vericar 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 .
/
*
Recebe uma chave x e uma lista linear circular c e verifica se x consta em
c, devolvendo a clula em que x se encontra ou NULL em caso contrrio
*
/
celula
*
busca_circular(int x, celula
*
c)
{
celula
*
p,
*
q;
p = c;
p->chave = x;
q = c->prox;
while (q->chave != x)
q = q->prox;
if (q != c)
return q;
else
return NULL;
}
Observe que a busca em uma lista linear circular em alocao encadeada muito seme-
lhante 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 emuma lista linear ordinria. Observe, no entanto, que as constantes so
diferentes: enquanto na busca emuma lista linear ordinria a estrutura de repetio sempre re-
aliza 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 emuma 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.
/
*
Recebe uma chave y e uma lista circular c e insere y no incio da lista c
*
/
void insere_circular(int y, celula
*
c)
{
celula
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->chave = y;
nova->prox = c->prox;
c->prox = nova;
}
FACOM UFMS
188 LISTAS LINEARES CIRCULARES
A gura 21.3 mostra a execuo da funo insere_circular para uma chave 2 e uma
lista circular c .
c
c
c
2
2
3
3
3
4
4
4
5
5
5
6
6
6
7
7
7
nova
nova
(a)
(b)
(c)
Figura 21.3: Insero da chave 2 em uma lista circular c .
21.1.3 Remoo
A funo de remoo de um elemento emuma lista linear circular emalocao 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 OPERAES BSICAS EM ALOCAO ENCADEADA 189
/
*
Recebe uma chave x e uma lista linear circular c e
remove a clula com chave x da lista c, caso exista
*
/
void remove_circular(int x, celula
*
c)
{
celula
*
p,
*
q;
p = c;
p->chave = x;
q = p->prox;
while (q->chave != x) {
p = q;
q = q->prox;
}
if (q != c) {
p->prox = q->prox;
free(q);
}
}
A gura 21.4 mostra a execuo de remove_circular para a chave 4 e a lista circular c .
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 aloca-
o 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. Es-
creva as funes para as operaes bsicas de busca, insero e remoo para listas line-
ares 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. Deci-
diram 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 es-
tria como legado. Podemos descrever um problema mais geral como segue. Imagine n
pessoas dispostas emcrculo. Suponha que as pessoas so numeradas de 1 a n no sentido
horrio e que um nmero inteiro m, com 1 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 LISTAS LINEARES CIRCULARES
c
c
c
c
3
3
3
3
4
4
4
5
5
5
5
6
6
6
6
7
7
7
7
p
p
p
q
q
q
(a)
(b)
(c)
(d)
Figura 21.4: Remoo da chave 4 de uma lista circular c .
FACOM UFMS
21.1 OPERAES BSICAS EM ALOCAO ENCADEADA 191
21.5 Um computador permite representar nmeros inteiros at um valor mximo, que de-
pende da sua organizao e arquitetura interna. Suponha que seja necessrio, em uma
dada aplicao, o uso de nmeros inteiros com preciso muito grande, digamos ilimi-
tada. Uma forma de representar nmeros inteiros com preciso ilimitada mostrada na
gura 21.5.
n
89615 79730 19428 82
Figura 21.5: Uma lista linear circular com cabea em alocao encadeada que armazena o n-
mero inteiro 82194287973089615.
O tipo clula, que dene as clulas da lista linear circular, no sofre alteraes na sua
denio, sendo o mesmo o tipo celula previamente denido. 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 LISTAS LINEARES CIRCULARES
FACOM UFMS
AULA 22
LISTAS 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 ope-
raes sobre a lista, evitando percursos adicionais desnecessrios. Esse o caso, por exemplo,
da busca e da insero. No entanto, algumas vezes isso no suciente, j que podemos pre-
cisar percorrer a lista linear nos dois sentidos. Neste caso, para solucionar ecientemente 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 justica pela
economia em no ter de reprocessar a lista linear inteira nas operaes de interesse. Esta aula
baseada nas referncias [7, 13].
22.1 Denio
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, umoutro campo acrescentado
cada clula da lista indicando, alm do endereo da prxima clula, o endereo da clula
anterior da lista. Veja a gura 22.1.
chave
ant prox
Figura 22.1: Representao de uma clula de uma lista linear duplamente encadeada.
A denio de uma clula de uma lista linear duplamente encadeada ento descrita como
um tipo, como mostramos a seguir:
typedef struct cel {
int chave;
struct cel
*
ant;
struct cel
*
prox;
} celula;
193
194 LISTAS LINEARES DUPLAMENTE ENCADEADAS
Uma representao grca de uma lista linear em alocao encadeada mostrada na -
gura 22.2.
3 7
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 tam-
bm apontando para NULL.
Uma lista linear duplamente encadeada com cabea pode ser criada e inicializada da se-
guinte forma:
celula
*
lista;
lista = (celula
*
) malloc(sizeof (celula));
lista->ant = NULL;
lista->prox = NULL;
e sem cabea como abaixo:
celula
*
lista;
lista = NULL;
A gura 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 dupla-
mente encadeadas com cabea, deixando as liestas lineares sem cabea para os exerccios.
FACOM UFMS
22.2 BUSCA 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 uma chave x e um alista linear duplamente encadeada lst
e devolve a clula que contm x em lst ou NULL caso contrrio
*
/
celula
*
busca_dup_C(int x, celula
*
lst)
{
celula
*
p;
p = lst->prox;
while (p != NULL && p->chave != x)
p = p->prox;
return p;
}
Observe que a funo busca_dup_C praticamente uma cpia da funo busca_C .
22.3 Busca seguida de remoo
No primeiro passo da busca seguida de remoo, uma busca de uma chave realizada na
lista. Se a busca tem sucesso, ento o ponteiro para a clula que ser quere remover tambm
d acesso clula posterior e anterior da lista linear duplamente encadeada. Isso tudo o que
precisamos saber para realizar o segundo passo, isto , realizar a remoo satisfatoriamente. A
funo busca_remove_dup_C implementa essa idia.
/
*
Recebe um nmero inteiro x e uma lista duplamente encadeada lst e remo-
ve 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);
}
}
A funo busca_remove_dup_C muito semelhante funo busca_remove_C , mas no
h necessidade do uso de dois ponteiros para duas clulas consectivas da lista. A gura 22.4
FACOM UFMS
196 LISTAS LINEARES DUPLAMENTE ENCADEADAS
ilustra um exemplo de execuo dessa funo.
22.4 Busca seguida de insero
A funo busca_insere_dup_C a seguir realiza a busca de uma chave x e insere uma
chave y antes da primeira ocorrncia de x em uma lista linear duplamente encadeada lst .
Caso x no ocorra na lista, a chave y inserida no nal de lst .
/
*
Recebe dois nmeros inteiros y e x e uma lista duplamente
encadeada lst e insere uma nova clula com chave y nessa
lista antes da primeira que contiver x; se nenhuma clula
contiver x, a nova clula inserida no final da lista
*
/
void busca_insere_dup_C(int y, int x, celula
*
lst)
{
celula
*
p,
*
q,
*
nova;
nova = (celula
*
) malloc(sizeof (celula));
nova->chave = y;
p = lst;
q = lst->prox;
while (q != NULL && q->chave != x) {
p = q;
q = q->prox;
}
nova->ant = p;
nova->prox = q;
if (p != NULL)
p->prox = nova;
if (q != NULL)
q->ant = nova;
}
A funo busca_insere_dup_C tambm muito semelhante funo busca_insere_C ,
mas no h necessidade de manter dois ponteiros como na primeira funo. Veja a gura 22.5.
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 aloca-
o duplamente encadeada, um ponteiro para o m dessa lista e uma chave, e realize a
insero dessa chave no nal 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 BUSCA SEGUIDA DE INSERO 197
7 2 4 9
7 2 8 4 9
7 2 8 4 9
7 2 8 4 9
8
8
8
8
lista
lista
lista
lista x
x
x
x
(a)
(b)
(c)
(d)
p
p
p
Figura 22.4: Remoo em uma lista linear duplamente encadeada com cabea.
FACOM UFMS
198 LISTAS LINEARES DUPLAMENTE ENCADEADAS
7 2 8 9 4
7 2 8 9
4 9 4
7 2 8 9
4 9
7 2 8 9
4 9 4
7 2 8 9
4 9 4
lista
lista
lista
lista
lista
nova
nova
nova
x
x
x
x
y
y
y
y
(a)
(b)
(c)
(d)
(e)
p
p
Figura 22.5: Insero em uma lista linear duplamente encadeada com cabea.
FACOM UFMS
22.4 BUSCA SEGUIDA DE INSERO 199
clula apontar para a clula cabea. Escreva funes para implementar as operaes bsi-
cas de busca, insero e remoo emuma lista linear duplamente encadeada circular com
cabea.
22.4 Considere uma lista linear duplamente encadeada contendo as chaves c
1
, c
2
, . . . , c
n
, como
na gura 22.6.
lista
c1 c2 cn
Figura 22.6: Uma lista linear duplamente encadeada.
Escreva uma funo que altere os ponteiros ant e prox da lista, sem mover suas infor-
maes, tal que a lista que invertida como na gura 22.7.
lista
cn cn1 c1
Figura 22.7: Inverso da lista linear duplamente encadeada da gura 22.6.
22.5 Suponha que voc queira manter uma lista linear duplamente encadeada comcabea com
as chaves em ordemcrescente. Escreva as funes de busca, insero e remoo para essa
lista.
FACOM UFMS
200 LISTAS LINEARES DUPLAMENTE ENCADEADAS
FACOM UFMS
AULA 23
TABELAS 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 espalhamento
1
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 signica corte
e misture ou pique e misture.
Muitos problemas podem ser resolvidos atravs do uso das operaes bsicas de busca, in-
sero 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 correspondems palavras-chaves da linguagem. Outro exemplo menos abstrato
o de armazenamento das correspondncias em papel dos funcionrios de uma empresa. Se a
empresa grande, emgeral no possvel manter umcompartimento para cada funcionrio(a),
sendo mais razovel manter, por exemplo, 26 compartimentos rotulados com as letras do alfa-
beto. 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 eciente 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.
23.1 Tabelas de acesso direto
Suponha que temos uma aplicao emque cada informao seja identicada por uma chave
e que as operaes essenciais realizadas sobre essas informaes sejam a busca, insero e re-
moo. Podemos supor, sem perda de generalidade, que as chaves sejam apenas chaves nu-
mricas, j que sempre fcil associar um nmero inteiro a cada informao. Como as outras
informaes associadas s chaves so irrelevantes para as operaes bsicas necessrias apli-
cao, podemos descart-las e trabalhar apenas com as chaves. Suponha que todas as chaves
possveis na aplicao pertenam ao conjunto C = {0, 1, . . . , m 1}, onde m um nmero
inteiro relativamente pequeno.
1
Do ingls hash ou hashing. Optamos pela traduo espalhamento e tabela de espalhamento usada pelo
professor Tomasz Kowaltowsky, do IC-UNICAMP. O professor Jayme Luiz Szwarcter, da COPPE-UFRJ, usa os
termos disperso e tabela de disperso.
201
202 TABELAS DE ESPALHAMENTO
Ento, usamos uma tabela de acesso direto para representar esse conjunto de informaes,
que nada mais que umvetor T[0..m1] 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
gura 23.1 para uma ilustrao.
3
0
6
8
1
7
4
9
5
2
1
2
3
4
5
6
8
9
7
0
1
7
3
0
6
8
1
7
4
9
5
2
0
1
2
3
4
5
6
7
8
9
3
4
C
C
C
C
F
F
F
F
F
F
V
V
V
V
(a) (b)
Figura 23.1: Dois exemplos de tabela de acesso direto. (a) Cada compartimento da tabela ar-
mazena 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 emum dado
instante da aplicao, a tabela de acesso direto ter, nesse instante, muito espao alocado mas
no usado.
23.2 Introduo s tabelas de espalhamento
Para tentar solucionar ecientemente o problema conseqente do acesso direto, usamos
uma tabela de espalhamento. Em uma tabela de acesso direto, a chave k armazenada no
compartimento de ndice k. Em uma tabela de espalhamento, uma chave x armazenada
no compartimento h(x), onde a funo h: C {0, 1, . . . , m 1} chamada uma funo de
espalhamento. A funo h mapeia as chaves de C nos compartimentos de uma tabela de
espalhamento T[0..m 1]. O principal objetivo de uma funo de espalhamento reduzir o
intervalo de ndices da tabela de espalhamento.
FACOM UFMS
23.2 INTRODUO S TABELAS DE ESPALHAMENTO 203
A gura 23.2 mostra um exemplo de uma tabela de espalhamento associada a uma funo
de espalhamento.
C
C
x
1
x
1
x
2
x
2
x
3
x
3
x
4
x
4
x
5
x
5
x
6
x
6
Figura 23.2: Exemplo de uma tabela de espalhamento T associada a uma funo de espalha-
mento h. Observe que ocorrem as colises h(x
5
) = h(x
1
) = h(x
6
) e h(x
2
) = h(x
4
) 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 x
i
e x
j
, no conjunto das chaves possveis C tais que h(x
i
) = h(x
j
),
como vimos na gura 23.2. Quando duas chaves x
i
e x
j
possuem essa propriedade, dizemos
que h, ou pode haver, uma coliso entre x
i
e x
j
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(x
1
) = h(x
2
) = = h(x
n
) para toda
chave x
i
C, com 1 i n e |C| = n. Isso signica 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 gura 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. Umexemplo de uma
situao como essa ilustrado na gura 23.4.
FACOM UFMS
204 TABELAS DE ESPALHAMENTO
C
C
x
1
x
1
x
2
x
2
x
4
x
4
x
6
x
6
Figura 23.3: Exemplo de uma funo de espalhamento ruim, isto , uma funo que produz
uma tabela de espalhamento degenerada.
b
C
C
x
1
x
1
x
2
x
2
x
3
x
3
x
4
x
4
x
5
x
5
x
6
x
6
x
7
x
7
x
8
x
8
x
9
x
9
x
10
x
10
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 TRATAMENTO DE COLISES COM LISTAS LINEARES ENCADEADAS 205
23.3 Tratamento de colises com listas lineares encadeadas
Em uma tabela de espalhamento, o uso de listas lineares encadeadas uma maneira intui-
tiva e eciente de tratar colises. Assim, as operaes bsicas de busca, remoo e insero
so operaes de busca, remoo e insero sobre listas lineares encadeadas, como vimos na
aula 18. Apenas modicamos levemente a operao de insero, que em uma tabela de espa-
lhamento sempre realizada no incio da lista linear apropriada.
Se temos uma funo de espalhamento, ento a implementao de uma tabela de espalha-
mento com tratamento de colises usando listas lineares encadeadas um espelho da imple-
mentao de uma lista linear encadeada. Para analisar a ecincia dessa implementao, basta
analisar o tempo de execuo de cada uma das operaes. Se considerarmos que todas elas ne-
cessitamde alguma forma da operao de busca, podemos xar e analisar o tempo de execuo
apenas dessa operao.
O tempo de execuo no pior caso da operao de busca proporcional ao tamanho da
lista linear encadeada associada. No pior caso, como j mencionamos, tal lista contm todas
as n chaves do conjunto C e, assim, o tempo de execuo de pior caso da operao de busca
proporcional a n. Dessa forma, primeira vista parece que uma tabela de espalhamento uma
forma muito ruim de armazenar informaes, j que o tempo de pior caso de cada uma das
operaes bsicas ruim.
No entanto, se estudamos o tempo de execuo no caso mdio, ou tempo esperado de exe-
cuo, no o tempo de execuo no pior caso, a situao se reverte em favor dessas estruturas.
O tempo de execuo no caso mdio de uma busca emuma tabela de espalhamento proporci-
onal a 1 +, onde chamado de fator de carga da tabela. Se a tabela de espalhamento T tem
tamanho m e armazena n chaves, ento denido como n/m. Dessa forma, se a quantidade
de chaves n armazenada na tabela T proporcional ao tamanho m de T, ento as operaes
bsicas so realizadas em tempo esperado constante.
23.3.1 Funes de espalhamento
Uma funo de espalhamento deve, a princpio, satisfazer a suposio de que cada chave
igualmente provvel de ser armazenada em qualquer um dos m compartimentos da tabela
de espalhamento T, independentemente de onde qualquer outra chave foi armazenada an-
tes. Uma funo de espalhamento que tem essa propriedade chamada de funo de espa-
lhamento simples e uniforme. Essa suposio implica na necessidade de conhecimento da
distribuio de probabilidades da qual as chaves foram obtidas, o que em geral no sabemos
previamente.
O uso de heursticas para projetar funes de espalhamento bastante freqente. Isso sig-
nica que no h garantia alguma de que uma tal funo seja simples e uniforme, mas muitas
delas so usadas na prtica e funcionam bem na maioria dos casos. O mtodo da diviso e o
mtodo da multiplicao so heursticas que fornecem funes de espalhamento com desem-
penho prtico satisfatrio. Algumas aplicaes, no entanto, exigem que as funes de espalha-
mento tenhamgarantias de que so simples e uniformes. Omtodo universal de espalhamento
gera uma classe de funes de espalhamento que satisfazem essa propriedade.
comum que uma funo de espalhamento tenha como domnio o conjunto dos nme-
ros naturais. Se as chaves de uma aplicao no so nmeros naturais, podemos facilmente
FACOM UFMS
206 TABELAS DE ESPALHAMENTO
transform-las em nmeros naturais. Por exemplo, se as chaves so cadeias de caracteres, po-
demos somar o valor de cada caractere que compe a cadeia, obtendo assimumnmero natural
que representa esta chave.
Nesta aula, estudamos as heursticas mencionadas acima, deixando que os leitores interes-
sados 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 re-
solvidas 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 utuante 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
_
xAxA
__
.
Diferentemente do mtodo da diviso, o valor de m no mtodo da multiplicao no o
mais importante. Em geral, escolhemos m = 2
p
para algum nmero inteiro p, j que dessa
forma podemos usar operaes sobre bits para obter h(x). No entanto, o mtodo da multiplica-
o funciona melhor com alguns valores de A do que com outros. Donald E. Knuth sugere que
A (

5 1)/2 = 0,6180339887 . . . seja um valor que fornea boas funes de espalhamento


pelo mtodo da multiplicao.
Outros mtodos
Algumas outras heursticas para gerao de funes de espalhamento podem ser citadas,
como por exemplo, o mtodo da dobra e o mtodo da anlise dos dgitos. Interessados devem
procurar as referncias desta aula. Alm disso, o mtodo universal escolhe ou gera funes
aleatoriamente, de forma independente das chaves a seremarmazenadas na tabela, garantindo
assim um bom desempenho mdio.
FACOM UFMS
23.4 ENDEREAMENTO ABERTO 207
23.4 Endereamento aberto
Em uma tabela de espalhamento com endereamento aberto, as chaves so todas arma-
zenadas na prpria tabela. Por isso, muitas vezes uma tabela de espalhamento com colises
solucionadas por listas lineares encadeadas chamada de tabela de espalhamento com ende-
reamento exterior.
Cada compartimento de uma tabela de espalhamento com endereamento aberto ou con-
tm uma chave ou um valor que indica que o compartimento est vazio. Na busca por uma
chave, examinamos sistematicamente os compartimentos da tabela at que a chave desejada
seja encontrada ou at que que claro que a chave no consta na tabela. Ao invs de seguir
ponteiros, como em uma tabela de espalhamento com endereamento exterior, devemos com-
putar a seqncia de compartimentos a seremexaminados. Para realizar uma insero emuma
tabela de espalhamento com endereamento aberto, examinamos sucessivamente a tabela at
que um compartimento vazio seja encontrado. A seqncia de compartimentos examinada
depende da chave a ser inserida e no da ordem dos ndices 0, 1, . . . , m1.
Em uma tabela com endereamento aberto, a funo de espalhamento tem domnio e
contra-domnio denidos como:
h: C {0, 1, . . . , m1} {0, 1, . . . , m1} .
Alm disso, necessrio que para toda chave x, a seqncia de tentativas ou seqncia de
exames dada por h(x, 0), h(x, 1), . . . , h(x, m1) seja uma permutao de 0, 1, . . . , m1.
Inicialmente, a tabela de espalhamento com endereamento aberto T inicializada com1
em cada um de seus compartimentos, indicando que todos esto vazios. Veja a gura 23.5.
T
1
1
1
1
0
1
m2
m1
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. Conside-
ramos 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 m1 indicando que a insero obteve sucesso ou o valor m indicando
o contrrio.
FACOM UFMS
208 TABELAS DE ESPALHAMENTO
/
*
Recebe uma tabela de espalhamento T de tamanho m e uma chave x e
insere a chave x na tabela T, devolvendo o ndice da tabela onde a
chave foi inserida, em caso de sucesso, ou m em caso contrrio
*
/
int insere_aberto(int m, int T[MAX], int x)
{
int i, j;
i = 0;
do {
j = h(x, i);
if (T[j] == -1) {
T[j] = x;
return j;
}
else
i++;
} while (i != m);
return m;
}
A busca em uma tabela com endereamento aberto muito semelhante insero que vi-
mos acima. A busca tambm termina se a chave foi encontrada na tabela ou quando encontra
um compartimento vazio.
/
*
Recebe uma tabela de espalhamento T de tamanho m e uma chave
x e busca a chave x na tabela T, devolvendo o ndice da tabe-
la onde a chave foi encontrada ou o valor m em caso contrrio
*
/
int busca_aberto(int m, int T[MAX], int x)
{
int i, j;
i = 0;
do {
j = h(x, i);
if (T[j] == x)
return j;
else
i++;
} while (T[j] != -1 && i != m);
return m;
}
A remoo de uma chave em um compartimento i da tabela de espalhamento com ende-
reamento 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, denido abaixo:
FACOM UFMS
23.4 ENDEREAMENTO ABERTO 209
struct cel {
int chave;
int estado;
};
typedef struct cel celula;
Inicialmente, a tabela de espalhamento T inicializada comseus estados todos preenchidos
com o VAZIA, como mostra a gura 23.6.
T
chave estado
VAZIA
VAZIA
VAZIA
VAZIA 0
1
m2
m1
Figura 23.6: Uma tabela de espalhamento com endereamento aberto vazia, onde cada com-
partimento 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.
/
*
Recebe uma tabela de espalhamento T de tamanho m e uma chave
x e busca a chave x na tabela T, devolvendo o ndice da tabe-
la onde a chave foi encontrada ou o valor m em caso contrrio
*
/
int busca_aberto(int m, int T[MAX], int x)
{
int i, j;
i = 0;
do {
j = h(x, i);
if (T[j].chave == x && T[j].estado == OCUPADA)
return j;
else
i++;
} while (T[j].estado != VAZIA && i != m);
return m;
}
FACOM UFMS
210 TABELAS DE ESPALHAMENTO
/
*
Recebe uma tabela de espalhamento T de tamanho m e uma chave x e
insere a chave x na tabela T, devolvendo o ndice da tabela onde a
chave foi inserida, em caso de sucesso, ou m em caso contrrio
*
/
int insere_aberto(int m, int T[MAX], int x)
{
int i, j;
i = 0;
do {
j = h(x, i);
if (T[j].estado != OCUPADA) {
T[j].chave = x;
T[j].estado = OCUPADA;
return j;
}
else
i++;
} while (i != m);
return m;
}
/
*
Recebe uma tabela de espalhamento T de tamanho m e uma chave x e
remove a chave x na tabela T, devolvendo o ndice da tabela onde a
chave foi inserida, em caso de sucesso, ou m em caso contrrio
*
/
int remove_aberto(int m, int T[MAX], int x)
{
int i, j;
i = 0;
do {
j = h(x, i);
if (T[j].estado != VAZIA) {
if (T[j].chave == x && T[j].estado == OCUPADA) {
T[j].estado = REMOVIDA;
return j;
}
else
i++;
}
else
i = m;
} while (i != m);
return m;
}
A gura 23.7 mostra um exemplo de uma tabela de espalhamento T com endereamento
aberto, alocada com 10 compartimentos e contendo 6 chaves: 41, 22, 104, 16, 37, 16. A funo de
espalhamento usada h dada pelo mtodo da tentativa linear, que veremos na seo 23.4.1 a
seguir, e denida da seguinte forma:
h(x, i) =
_
h

(x) + i
_
mod 10 ,
onde h

uma funo de espalhamento ordinria dada pelo mtodo da diviso apresentado na


seo 23.3.1 e denida como:
h

(x) = x mod 10 .
FACOM UFMS
23.4 ENDEREAMENTO ABERTO 211
0
1
5
2
3
4
6
7
8
9
104
16
37
96
22
41
T
chave estado
VAZIA
VAZIA
VAZIA
VAZIA
OCUPADA
OCUPADA
OCUPADA
OCUPADA
OCUPADA
OCUPADA
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.
Emuma tabela de espalhamento comendereamento aberto temos sempre no mximo uma
chave por compartimento. Isso signica que n m, onde n o nmero de chaves armazenadas
e m o total de compartimentos da tabela. Portanto, o fator de carga da tabela sempre satis-
faz 1. Se consideramos a hiptese que a tabela de espalhamento uniforme, isto , que a
seqncia de tentativas h(x, 0), h(x, 1), . . . , h(x, m1) usada emuma operao bsica igual-
mente provvel a qualquer permutao de 0, 1, . . . , m 1, ento temos duas conseqncias
importantes:
o nmero esperado de tentativas em uma busca sem sucesso no mximo
1
1
;
isso signica, 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

_
ln
_
1
1
_
;
FACOM UFMS
212 TABELAS DE ESPALHAMENTO
isso signica, 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.
23.4.1 Funes de espalhamento
Idealmente, supomos que cada chave em uma tabela de espalhamento com endereamento
aberto tenha qualquer uma das m! permutaes igualmente provveis de 0, 1, . . . , m1 como
sua seqncia de tentativas. Essa suposio sobre a funo de espalhamento chamada de
espalhamento uniforme e generaliza a suposio de espalhamento simples e uniforme que
vimos acima. Funes de espalhamento uniforme so difceis de implementar e na prtica so
usadas algumas boas aproximaes.
As tcnicas mais comuns para computar seqncias de tentativas em tabelas de espalha-
mento com endereamento aberto so as seguintes: tentativa linear, tentativa quadrtica e es-
palhamento duplo. Todas essas tcnicas garantem que a seqncia de tentativas produzida
h(x, 0), h(x, 1), . . . , h(x, m 1) uma permutao da seqncia 0, 1, . . . , m 1 para cada
chave x. Nenhuma delas satisfaz as suposies de espalhamento uniforme, sendo que a tc-
nica do espalhamento duplo a que apresenta melhores resultados.
Tentativa linear
Dada uma funo de espalhamento auxiliar h

: C {0, 1, . . . , m 1}, o mtodo da tenta-


tiva linear dado pela funo
h(x, i) =
_
h

(x) + i
_
mod m ,
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. Emseguida, 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].
fcil de implementar funes de espalhamento usando o mtodo da tentativa linear, mas
tais funes sofrem de um problema conhecido como agrupamento primrio, que provoca o
aumento do tempo mdio das operaes bsicas.
Tentativa quadrtica
Uma funo de espalhamento projetada pelo mtodo da tentativa quadrtica ligeira-
mente diferente de uma funo dada pelo mtodo da busca linear, j que usa uma funo da
forma:
h(x, i) =
_
h

(x) + c
1
i + c
2
i
2
_
mod m ,
onde h

uma funo de espalhamento auxiliar, c


1
e c
2
so constantes auxiliares e i =
0, 1, . . . , m 1. Para uma chave x, o primeiro compartimento examinado T[h

(x)]. Em se-
guida, os compartimentos examinados so obtidos pelo deslocamento quadrtico de valores
que dependem de i. Este mtodo melhor que o anterior, mas a escolha dos valores c
1
, c
2
e
FACOM UFMS
23.4 ENDEREAMENTO ABERTO 213
m restrita. Alm disso, o mtodo sofre do problema de agrupamento secundrio, j que se
h(x
1
, 0) = h(x
2
, 0) ento h(x
1
, i) = h(x
2
, i) para todo i.
Espalhamento duplo
O mtodo do espalhamento duplo usa uma funo de espalhamento da seguinte forma:
h(x, i) =
_
h
1
(x) + ih
2
(x)
_
mod m ,
onde h
1
e h
2
so funes de espalhamento auxiliares. O compartimento inicialmente exami-
nado T[h
1
(x)]. Os compartimentos examinados emseguida so obtidos do deslocamento dos
compartimentos anteriores da quantidade de h
2
(x) mdulo m.
A recomendao que o valor h
2
(x) seja um primo relativo ao tamanho da tabela m. Po-
demos assegurar essa condio fazendo m uma potncia de 2 e projetando h
2
de tal forma que
sempre produza um nmero mpar. Outra forma fazer m primo e projetar h
2
de tal forma
que sempre devolva um nmero inteiro positivo menor que m.
Omtodo do espalhamento duplo umdos melhores mtodos disponveis para o enderea-
mento aberto j que as permutaes produzidas tmmuitas das caractersticas de permutaes
aleatrias. O desempenho do mtodo do espalhamento duplo , assim, prximo ao desempe-
nho do esquema ideal de espalhamento universal.
Exerccios
23.1 Suponha que temos um conjunto de chaves C armazenado em uma tabela de acesso di-
reto 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 emuma 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 = (

51)/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 espalha-
mento 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 ten-


tativa linear, tentativa quadrtica com c
1
= 1 e c
2
= 3 e espalhamento duplo com
h
2
(x) = 1 + (x mod (m1)).
FACOM UFMS
214 TABELAS DE ESPALHAMENTO
23.6 A tabela abaixo composta das palavras-chaves da linguagem C padro.
auto double int long
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Escreva um programa que leia um arquivo contendo um programa na linguagem C e
identique suas palavras-chaves.
23.7 Veja animaes do funcionamento de tabelas de espalhamento nas pginas a seguir:
Hashing Animation Tool, Catalyst Software, 2000
Hash Table Animation, Woi Ang
Hashing, Hang Thi Anh Pham, 2001
FACOM UFMS
AULA 24
OPERAES SOBRE BITS
A linguagem C possui muitas caractersticas de uma linguagem de programao de alto n-
vel e isso signica que podemos escrever programas que so independentes das mquinas que
iro execut-los. A maioria das aplicaes possui naturalmente essa propriedade, isto , pode-
mos propor solues computacionais programas escritos na linguagemC que no tmde se
adaptar s mquinas emque sero executados. Por outro lado, alguns programas mais espec-
cos, como compiladores, sistemas operacionais, cifradores, processadores grcos 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 especcos 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 as-
pectos da linguagem C.
24.1 Operadores bit a bit
AlinguagemCpossui seis operadores bit a bit, que operamsobre dados do tipo inteiro e do
tipo caractere no nvel de seus bits. Atabela abaixo mostra esses operadores e seus signicados.
Operador Signicado
~ complemento
& E
^ OU exclusivo
| OU inclusivo
<< deslocamento esquerda
>> deslocamento direita
O operador de complemento ~ o nico operador unrio dessa tabela, os restantes todos
so binrios. Os operadores ~, &, ^ e | executam operaes booleanas sobre os bits de seus
operandos.
O operador ~ gera o complemento de seu operando, isto , troca os bits 0 por 1 e os bits 1
por 0. O operador & executa um E booleano bit a bit nos seus operandos. Os operadores ^ e |
executam um OU booleano bit a bit nos seus operandos, sendo que o operador ^ produz 0 caso
os dois bits correspondentes dos operandos sejam 1 e, nesse mesmo caso, o operador | produz
215
216 OPERAES 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.
Programa 24.1: Uso de operadores sobre bits.
#include <stdio.h>
#include <stdlib.h>
/
*
Recebe um nmero inteiro n e devolve uma cadeia de carac-
teres representando sua representao na base binria
*
/
char
*
binario(unsigned short int n)
{
char
*
b;
int i;
b = (char
*
) malloc(17
*
sizeof (char));
b[16] = \0;
for (i = 15; i >= 0; i--, n = n / 2)
b[i] = 0 + n % 2;
return b;
}
int main(void)
{
unsigned short int i, j, k;
i = 51;
printf(" i = %5d (%s)\n", i, binario(i));
j = 15;
printf(" j = %5d (%s)\n\n", j, binario(j));
k = ~i;
printf(" ~i = %5d (%s)\n", k, binario(k));
k = i & j;
printf(" i & j = %5d (%s)\n", k, binario(k));
k = i ^ j;
printf(" i ^ j = %5d (%s)\n", k, binario(k));
k = i | j;
printf(" i | j = %5d (%s)\n", k, binario(k));
k = i << 4;
printf("i << 4 = %5d (%s)\n", k, binario(k));
k = j >> 2;
printf("j >> 2 = %5d (%s)\n", k, binario(k));
return 0;
}
A execuo do programa 24.1 provoca a seguinte sada:
FACOM UFMS
24.1 OPERADORES BIT A BIT 217
i = 51 (0000000000110011)
j = 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 signica que um sentena como a seguir
if (status & 0x4000 != 0)
tem o signicado a seguir
if (status & (0x4000 != 0))
o que muito provavelmente no a inteno do(a) programador(a) neste caso. Dessa forma,
para que a inteno se concretize, devemos escrever:
if ((status & 0x4000) != 0)
Muitas vezes em programao de baixo nvel, queremos acessar bits especcos de uma
seqncia de bits. Por exemplo, quando trabalhamos com computao grca, queremos co-
locar dois ou mais pixeis em um nico byte. Podemos extrair e/ou modicar 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, umcompartimento
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 signi-
cativo, 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 OPERAES 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:
i = 0x00ff; i = i & ~0x0010;
Usando a mesma idia, podemos escrever uma sentena que desliga um bit cuja posio
est armazenada em uma varivel j :
i = i & ~(1 << j);
Testando um bit: o trecho de cdigo a seguir verica se o bit 4 da varivel i est ligado:
if (i & 0x0010)
Para testar se o bit j est ligado, temos de usar a seguinte sentena:
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 denir macros que representamessas 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 OPERADORES BIT A BIT 219
i = i | AZUL;
i = i & ~AZUL;
if (i & AZUL) ...
Podemos ainda ligar, desligar e testar vrios bits ao mesmo tempo:
i = i | AZUL | VERDE;
i = i & ~(AZUL | VERMELHO);
if (i & (VERDE | VERMELHO)) ...
Trabalhar com um grupo de vrios bits consecutivos, chamados de trecho de bits
1
um
pouco mais complicado que trabalhar com um nico bit. As duas operaes mais comuns
sobre trechos de bits so as seguintes:
Modicar um trecho de bits: para alterar um trecho de bits necessrio inicialmente
limp-lo usando a operao E bit a bit e posteriormente armazenar os novos bits no trecho
de bits com a operao de OU inclusivo. A operao a seguir mostra como podemos
armazenar o valor binrio 101 nos bits 46 da varivel i :
i = i & ~0x0070 | 0x0050;
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 umtrecho de bits: quando o trecho de bits atinge o bit 0 da varivel, a recupe-
rao 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:
j = (i >> 4) & 0x0007;
1
Traduo livre de bit-eld.
FACOM UFMS
220 OPERAES SOBRE BITS
24.2 Trechos de bits em registros
Alm das tcnicas que vimos na seo anterior para trabalhar com trechos de bits, a lin-
guagem C fornece como alternativa a declarao de registros cujos campos so trechos de bits.
Essa caracterstica pode ser bem til especialmente em casos em que a complexidade de pro-
gramao envolvendo trechos de bits gera arquivos-fontes complicados e confusos.
Suponha que queremos armazenar uma data na memria. Como os nmeros envolvidos
so relativamente pequenos, podemos pensar emarmazen-la em16 bits, com5 bits para o dia,
4 bits para o ms e 7 bits para o ano, como mostra a gura 24.1.
15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
ano ms dia
Figura 24.1: A forma de armazenamento de uma data.
Usando trechos de bits, podemos denir um registro com o mesmo formato:
struct data {
unisgned int dia: 5;
unisgned int mes: 4;
unisgned 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 me-
lhor 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:
struct data data_arquivo;
data_arquivo.dia = 28;
data_arquivo.mes = 12;
data_arquivo.ano = 8;
Podemos considerar que um valor armazenado no campo ano seja relativo ao ano de
1980
2
. Ou seja a atribuio do valor 8 ao campo ano , como feita acima, tem signicado 1988.
Depois dessas atribuies, a varivel data_arquivo tem a aparncia mostrada na gura 24.2.
Poderamos usar operadores sobre bits para obter o mesmo resultado, talvez at mais rapi-
damente. No entanto, escrever umprograma 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 modicado.
FACOM UFMS
24.2 TRECHOS DE BITS EM REGISTROS 221
15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1
Figura 24.2: Varivel data_arquivo com valores armazenados.
Trechos de bits tm uma restrio que no se aplica a outros campos de um registro. Como
os trechos de bits no tmumendereo no sentido usual, a linguagemCno nos permite aplicar
o operador de endereo & para um trecho de bits. Por conta disso, funes como scanf no
podem armazenar valores diretamente em um campo de um registro que um trecho de bits.
claro que podemos usar scanf para ler uma entrada em uma varivel e ento atribui seu
valor a um campo de um registro que um trecho de bits.
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 zermos um OU exclusivo do
caractere z com a chave, obtemos o caractere \:
00100110 cdigo binrio ASCII para &
XOR 01111010 cdigo binrio ASCII para z
01011100 cdigo binrio ASCII para \
Para decifrar uma mensagem, aplicamos a mesma idia. Cifrando uma mensagem pre-
viamente cifrada, obtemos a mensagem original. Se executamos um OU exclusivo entre o
caractere & e o caractere \, por exemplo, obtemos o caractere original z:
00100110 cdigo binrio ASCII para &
XOR 01011100 cdigo binrio ASCII para \
01111010 cdigo binrio ASCII para z
Escreva umprograma que receba uma cadeia de caracteres e cifre essa mensagemusando
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 OPERAES SOBRE BITS
FACOM UFMS
AULA 25
UNIES E ENUMERAES
Complementando o contedo apresentado at aqui sobre variveis compostas, a linguagem
C ainda oferece dois tipos delas: as unies e as enumeraes. Uma unio um registro que
armazena apenas um dos valores de seus campos e, portanto, apresenta economia de espao.
Uma enumerao ajuda o(a) programador(a) a declarar e usar uma varivel que representa
intuitivamente uma seqncia de nmeros naturais. Nesta aula, baseada especialmente na
referncia [7], veremos uma breve introduo sobre unies e enumeraes.
25.1 Unies
Como os registros, as unies tambm so compostas por um ou mais campos, denidos
possivelmente por tipos diferentes de dados. No entanto, o compilador aloca espao suciente
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
umvalor a umdos campos da unio tambmaltera os valores dos outros campos nessa mesma
estrutura.
Vamos declarar a unio u com dois campos, um do tipo caractere comsinal e outro do tipo
inteiro com sinal, como abaixo:
union {
char c;
int i;
} u;
Observe tambm que a declarao de uma unio basicamente idntica declarao de
um registro, a menos da palavra reservada union que substitui a palavra reservada struct .
Por exemplo, podemos declarar o registro r com os mesmos dois campos da unio u como a
seguir:
struct {
char c;
int i;
} r;
223
224 UNIES E ENUMERAES
A diferena entre a unio u e o registro r concentra-se apenas no fato que os campos
de u esto localizados no mesmo endereo de memria enquanto que os campos de r es-
to localizados em endereos diferentes. Considerando que um caractere ocupa um byte na
memria e um nmero inteiro ocupa quatro bytes em algumas implementaes vigentes de
computadores, a gura 25.1 apresenta uma ilustrao do que ocorre na memria na declarao
das variveis u e r .
c
c
i
i
u
r
Figura 25.1: Unio e registro representados na memria.
No registro r , os campos c e i ocupam diferentes posies da memria. Assim, o total
de memria necessrio para armazenamento do registro r na memria de cinco bytes. Na
unio u , os campos c e i compartilham a mesma posio da memria isto , os campos
c e i de u tm o mesmo endereo de memria e, portanto, o total de memria necessrio
para armazenamento de u de quatro bytes.
Campos de uma unio so acessados da mesma forma que os campos de um registro. Por
exemplo, podemos fazer a atribuio a seguir:
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 , qual-
quer 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 UNIES 225
Como o que ocorre com registros, unies podem ser copiadas diretamente usando o co-
mando de atribuio = , sem a necessidade de faz-las campo a campo. Declaraes e ini-
cializaes 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, po-
demos fazer:
union {
char c;
int i;
} u = \0;
Usamos unies freqentemente como uma forma de economizar espao alocado desne-
cessariamente 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 utuante 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 umvalor 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
atribuemumnmero inteiro e umnmero de ponto utuante 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, com1 n 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 UNIES E ENUMERAES
Programa 25.1: Um exemplo de uso de registros e unies.
#include <stdio.h>
#define TIPO_INT 0
#define TIPO_DOUBLE 1
#define MAX 100
typedef struct {
int tipo;
union {
int i;
double d;
} u;
} mix_num;
/
*
Recebe uma seqncia de n numeros, inteiros e reais, e mos-
tra a soma dos nmeros inteiros e a soma dos nmeros reais
*
/
int main(void)
{
int i, n, soma, inteiro, real;
double produto;
mix_num numero[MAX];
printf("Informe n: ");
scanf("%d", &n);
for (i = 0; i < n; i++) {
printf("Informe o tipo do nmero (0: inteiro, 1: real): ");
scanf("%d", &numero[i].tipo);
printf("Informe o nmero: ");
if (numero[i].tipo == TIPO_INT)
scanf("%d", &numero[i].u.i);
else
scanf("%lf", &numero[i].u.d);
}
soma = 0;
produto = 1.0;
inteiro = 0;
real = 0;
for (i = 0; i < n; i++)
if (numero[i].tipo == TIPO_INT) {
soma = soma + numero[i].u.i;
inteiro = 1;
}
else {
produto = produto
*
numero[i].u.d;
real = 1;
}
if (inteiro)
printf("Soma dos nmeros inteiros: %d\n", soma);
else
printf("No foram informados nmeros inteiros na entrada\n");
if (real)
printf("Produto dos nmeros reais: %g\n", produto);
else
printf("No h nmeros reais na entrada\n");
return 0;
}
FACOM UFMS
25.2 ENUMERAES 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 deve-
ro 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 de-
clarao 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. As-
sim, 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 con-
tm 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 signicado do valor 0 no
imediatamente aparente.
Uma estratgia melhor que a anterior o uso de macros para denir um tipo naipe e
tambm para denir nomes para os diversos naipes existentes. Ento, podemos fazer:
#define NAIPE int
#define PAUS 0
#define COPAS 1
#define ESPADAS 2
#define OUROS 3
O exemplo anterior ca ento bem mais fcil de ler:
NAIPE n;
n = PAUS;
Apesar de melhor, essa estratgia ainda possui restries. Por exemplo, algum que neces-
sita 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 denio de
uma macro para cada valor pode se tornar uma tarefa tediosa. Por m, os identicadores das
macros sero removidos pelo pr-processador e substitudos pelos seus valores respectivos e
isso signica que no estaro disponveis para a fase de depurao do programa. Depurao
uma atividade muito til para programao a medida que nossos programas cam maiores e
mais complexos.
FACOM UFMS
228 UNIES E ENUMERAES
A linguagem C possui um tipo especial especco para variveis que armazenam um con-
junto 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 identicador, 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 :
enum PAUS, COPAS, ESPADAS, OUROS n1, n2;
n1 = PAUS;
n2 = n1;
Apesar de terem pouco em comum com registros e unies, as enumeraes so declaradas
de modo semelhante. Entretanto, diferentemente dos campos dos registros e unies, os nomes
das constantes da enumerao devem ser diferentes de outros nomes de outras enumeraes
declaradas.
A linguagem C trata variveis que so enumeraes e constantes da enumerao como
nmeros inteiros. Por padro, o compilador atribui os inteiros 0, 1, 2, . . . para as constantes da
enumerao na declarao de uma varivel qualquer do tipo enumerao. No exemplo acima,
PAUS , COPAS , ESPADAS e OUROS representam os valores 0, 1, 2 e 3, respectivamente.
Podemos escolher valores diferentes dos acima para as constantes da enumerao. Por
exemplo, podemos fazer a seguinte declarao:
enum PAUS = 1, COPAS = 2, ESPADAS = 3, OUROS = 4 n;
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;
Se nenhum valor especicado para uma constante da enumerao, o valor atribudo
constante um valor maior que o valor da constante imediatamente anterior. A primeira cons-
tante da enumerao tem o valor padro 0 (zero). Na enumerao a seguir, a constante PRETO
tem o valor 0, CINZA_CLARO tem o valor 7, CINZA_ESCURO tem o valor 8 e BRANCO tem o
valor 15:
enum PRETO, CINZA_CLARO = 7, CINZA_ESCURO, BRANCO = 15 coresEGA;
Como as constantes de uma enumerao so, na verdade, nmeros inteiros, a linguagem C
permite que um(a) programador(a) use-as em expresses com inteiros. Por exemplo, o trecho
de cdigo a seguir vlido em um programa:
FACOM UFMS
25.2 ENUMERAES 229
int i;
enum PAUS, COPAS, ESPADAS, OUROS n;
i = COPAS;
n = 0;
n++;
i = n + 2;
Apesar da convenincia de podermos usar uma constante de uma enumerao como um
nmero inteiro, perigoso usar um nmero inteiro como um valor de uma enumerao. Por
exemplo, podemos acidentalmente armazenar o nmero 4, que no corresponde a qualquer
naipe, na varivel n .
Vejamos o programa 25.2 com um exemplo do uso de enumeraes, onde contamos o n-
mero de cartas de cada naipe de um conjunto de cartas fornecido como entrada. Uma entrada
uma cadeia de caracteres, dada como 4pAp2c1o7e, onde p signica o naipe de paus, c
copas, e espadas e o ouros.
Exerccios
25.1 Escreva um programa que receba uma coleo de n nmeros, com 1 n 100, que
podem ser inteiros pequenos de 1 (um) byte, inteiros maiores de 4 (quatro) bytes ou n-
meros reais, e receba ainda um nmero x e verique 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 m, n
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 enume-
rao.
FACOM UFMS
230 UNIES E ENUMERAES
Programa 25.2: Uso de enumeraes.
#include <stdio.h>
typedef struct {
char valor[2];
enum PAUS, COPAS, ESPADAS, OUROS naipe;
} carta;
/
*
Recebe uma cadeia de caracteres representando uma mo de
cartas e conta o nmero de cartas de cada naipe nessa mo
*
/
int main(void)
{
char entrada[109];
int i, j, conta[4] = 0;
carta mao[52];
printf("Informe uma mo: ");
scanf("%s", entrada);
for (i = 0, j = 0; entrada[i]; j++) {
mao[j].valor[0] = entrada[i];
i++;
if (entrada[i] == 0) {
mao[j].valor[1] = entrada[i];
i++;
}
switch (entrada[i]) {
case p:
mao[j].naipe = PAUS;
break;
case c:
mao[j].naipe = COPAS;
break;
case e:
mao[j].naipe = ESPADAS;
break;
case o:
mao[j].naipe = OUROS;
break;
default:
break;
}
i++;
}
for (j--; j >= 0; j--)
conta[mao[j].naipe]++;
printf("Cartas:\n");
printf(" %d de paus\n", conta[PAUS]);
printf(" %d de copas\n", conta[COPAS]);
printf(" %d de espadas\n", conta[ESPADAS]);
printf(" %d de ouros\n", conta[OUROS]);
return 0;
}
FACOM UFMS
REFERNCIAS BIBLIOGRFICAS
[1] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to Algorithms. The
MIT Press, 3rd edition, 2009. 2, 2.4, 3, 6, 7, 8, 8.1.2, 8.3, 23
[2] P. Feoloff. Algoritmos em linguagem C. Editora Campus/Elsevier, 2009. 1, 1.1, 1.2, 2, 3,
3.2.2, 4, 4.1, 4.2, 5, 6, 7, 7.1, 8, 9, 12, 15, 16, 18, 19, 20
[3] Debugging with GDB: the GNU source-level debugger for GDB.
http://sourceware.org/gdb/current/onlinedocs/gdb/.
[4] E. Huss. The C Library Reference Guide.
http://www.acm.uiuc.edu/webmonkeys/book/c_guide/, 1997.
ltimo acesso em agosto de 2010.
[5] B. W. Kernighan and R. Pike. The Practice of Programming. Addison-Wesley Professional,
1999.
[6] B. W. Kernighan and D. M. Ritchie. C Programming Language. Prentice Hall, 2nd edition,
1988.
[7] K. N. King. C Programming A Modern Approach. W. W. Norton & Company, Inc., 2nd
edition, 2008. 1, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 24, 25
[8] J. Kleinberg and va Tardos. Algorithm Design. Pearson Education, Inc., 2006. 2
[9] S. G. Kochan. Unix Shell Programming. Sams Publishing, 3rd edition, 2003.
[10] Departamento de Cincia da Computao IME/USP, listas de exerccios Introduo
Computao. http://www.ime.usp.br/~macmulti/.
ltimo acesso em agosto de 2010.
[11] M. F. Siqueira. Algoritmos e Estruturas de Dados I. Notas de aula, 1998. (ex-professor do
Departamento de Computao e Estatstica da Universidade Federal de Mato Grosso do
Sul (DCT/UFMS), atualmente professor do Departamento de Informtica e Matemtica
Aplicada da Universidade Federal do Rio Grande do Norte (DIMAp/UFRN)).
[12] S. S. Skiena and M. Revilla. Programming Challenges. Springer, 2003.
[13] J. L. Szwarcter and L. Markenzon. Estruturas de Dados e seus Algoritmos. Livros Tcni-
cos e Cientcos LTC, 3 edition, 2010. 8, 18, 19, 20, 21, 22, 23
[14] Wikipedia the Free Encyclopedia.
http://en.wikipedia.org/wiki/Main_Page.
ltimo acesso em agosto de 2010.
231