Você está na página 1de 19

10.

Listas Encadeadas
W. Celes e J. L. Rangel
Para representarmos um grupo de dados, j vimos que podemos usar um vetor em C. O
vetor a forma mais primitiva de representar diversos elementos agrupados. Para
simplificar a discusso dos conceitos que sero apresentados agora, vamos supor que
temos que desenvolver uma aplicao que deve representar um grupo de valores
inteiros. Para tanto, podemos declarar um vetor escolhendo um nmero mximo de
elementos.
#define MAX 1000
int vet[MAX];

Ao declararmos um vetor, reservamos um espao contguo de memria para armazenar


seus elementos, conforme ilustra a figura abaixo.

vet
Figura 9.1: Um vetor ocupa um espao contguo de memria, permitindo que qualquer elemento seja
acessado indexando-se o ponteiro para o primeiro elemento.

O fato de o vetor ocupar um espao contguo na memria nos permite acessar qualquer
um de seus elementos a partir do ponteiro para o primeiro elemento. De fato, o smbolo
vet, aps a declarao acima, como j vimos, representa um ponteiro para o primeiro
elemento do vetor, isto , o valor de vet o endereo da memria onde o primeiro
elemento do vetor est armazenado. De posse do ponteiro para o primeiro elemento,
podemos acessar qualquer elemento do vetor atravs do operador de indexao vet[i].
Dizemos que o vetor uma estrutura que possibilita acesso randmico aos elementos,
pois podemos acessar qualquer elemento aleatoriamente.
No entanto, o vetor no uma estrutura de dados muito flexvel, pois precisamos
dimension-lo com um nmero mximo de elementos. Se o nmero de elementos que
precisarmos armazenar exceder a dimenso do vetor, teremos um problema, pois no
existe uma maneira simples e barata (computacionalmente) para alterarmos a dimenso
do vetor em tempo de execuo. Por outro lado, se o nmero de elementos que
precisarmos armazenar no vetor for muito inferior sua dimenso, estaremos subutilizando o espao de memria reservado.
A soluo para esses problemas utilizar estruturas de dados que cresam medida que
precisarmos armazenar novos elementos (e diminuam medida que precisarmos retirar
elementos armazenados anteriormente). Tais estruturas so chamadas dinmicas e
armazenam cada um dos seus elementos usando alocao dinmica.
Nas sees a seguir, discutiremos a estrutura de dados conhecida como lista encadeada.
As listas encadeadas so amplamente usadas para implementar diversas outras
estruturas de dados com semnticas prprias, que sero tratadas nos captulos seguintes.
Estruturas de Dados PUC-Rio

10-1

10.1. Lista encadeada


Numa lista encadeada, para cada novo elemento inserido na estrutura, alocamos um
espao de memria para armazen-lo. Desta forma, o espao total de memria gasto
pela estrutura proporcional ao nmero de elementos nela armazenado. No entanto, no
podemos garantir que os elementos armazenados na lista ocuparo um espao de
memria contguo, portanto no temos acesso direto aos elementos da lista. Para que
seja possvel percorrer todos os elementos da lista, devemos explicitamente guardar o
encadeamento dos elementos, o que feito armazenando-se, junto com a informao de
cada elemento, um ponteiro para o prximo elemento da lista. A Figura 9.2 ilustra o
arranjo da memria de uma lista encadeada.
prim
Info1

Info2

Info3

10.1.
ULL

Figura 9.2: Arranjo da memria de uma lista encadeada.

A estrutura consiste numa seqncia encadeada de elementos, em geral chamados de


ns da lista. A lista representada por um ponteiro para o primeiro elemento (ou n).
Do primeiro elemento, podemos alcanar o segundo seguindo o encadeamento, e assim
por diante. O ltimo elemento da lista aponta para NULL, sinalizando que no existe um
prximo elemento.
Para exemplificar a implementao de listas encadeadas em C, vamos considerar um
exemplo simples em que queremos armazenar valores inteiros numa lista encadeada. O
n da lista pode ser representado pela estrutura abaixo:
struct lista {
int info;
struct lista* prox;
};
typedef struct lista Lista;

Devemos notar que trata-se de uma estrutura auto-referenciada, pois, alm do campo
que armazena a informao (no caso, um nmero inteiro), h um campo que um
ponteiro para uma prxima estrutura do mesmo tipo. Embora no seja essencial, uma
boa estratgia definirmos o tipo Lista como sinnimo de struct lista, conforme
ilustrado acima. O tipo Lista representa um n da lista e a estrutura de lista encadeada
representada pelo ponteiro para seu primeiro elemento (tipo Lista*).
Considerando a definio de Lista, podemos definir as principais funes necessrias
para implementarmos uma lista encadeada.
Funo de inicializao
A funo que inicializa uma lista deve criar uma lista vazia, sem nenhum elemento.
Como a lista representada pelo ponteiro para o primeiro elemento, uma lista vazia
Estruturas de Dados PUC-Rio

10-2

representada pelo ponteiro NULL, pois no existem elementos na lista. A funo tem
como valor de retorno a lista vazia inicializada, isto , o valor de retorno NULL. Uma
possvel implementao da funo de inicializao mostrada a seguir:
/* funo de inicializao: retorna uma lista vazia */
Lista* inicializa (void)
{
return NULL;
}

Funo de insero
Uma vez criada a lista vazia, podemos inserir novos elementos nela. Para cada elemento
inserido na lista, devemos alocar dinamicamente a memria necessria para armazenar
o elemento e encade-lo na lista existente. A funo de insero mais simples insere o
novo elemento no incio da lista.
Uma possvel implementao dessa funo mostrada a seguir. Devemos notar que o
ponteiro que representa a lista deve ter seu valor atualizado, pois a lista deve passar a
ser representada pelo ponteiro para o novo primeiro elemento. Por esta razo, a funo
de insero recebe como parmetros de entrada a lista onde ser inserido o novo
elemento e a informao do novo elemento, e tem como valor de retorno a nova lista,
representada pelo ponteiro para o novo elemento.
/* insero no incio: retorna a lista atualizada */
Lista* insere (Lista* l, int i)
{
Lista* novo = (Lista*) malloc(sizeof(Lista));
novo->info = i;
novo->prox = l;
return novo;
}

Esta funo aloca dinamicamente o espao para armazenar o novo n da lista, guarda a
informao no novo n e faz este n apontar para (isto , ter como prximo elemento) o
elemento que era o primeiro da lista. A funo ento retorna o novo valor que
representa a lista, que o ponteiro para o novo primeiro elemento. A Figura 9.3 ilustra a
operao de insero de um novo elemento no incio da lista.
prim

Novo

Info1

10.2.
ULL

Info2

Info3

Figura 9. 3: Insero de um novo elemento no incio da lista.

A seguir, ilustramos um trecho de cdigo que cria uma lista inicialmente vazia e insere
nela novos elementos.

Estruturas de Dados PUC-Rio

10-3

int main (void)


{
Lista* l;
l = inicializa();
l = insere(l, 23);
l = insere(l, 45);
...
return 0;
}

/*
/*
/*
/*

declara uma lista no inicializada */


inicializa lista como vazia */
insere na lista o elemento 23 */
insere na lista o elemento 45 */

Observe que no podemos deixar de atualizar a varivel que representa a lista a cada
insero de um novo elemento.
Funo que percorre os elementos da lista
Para ilustrar a implementao de uma funo que percorre todos os elementos da lista,
vamos considerar a criao de uma funo que imprima os valores dos elementos
armazenados numa lista. Uma possvel implementao dessa funo mostrada a
seguir.
/* funo imprime: imprime valores dos elementos */
void imprime (Lista* l)
{
Lista* p;
/* varivel auxiliar para percorrer a lista */
for (p = l; p != NULL; p = p->prox)
printf(info = %d\n, p->info);
}

Funo que verifica se lista est vazia


Pode ser til implementarmos uma funo que verifique se uma lista est vazia ou no.
A funo recebe a lista e retorna 1 se estiver vazia ou 0 se no estiver vazia. Como
sabemos, uma lista est vazia se seu valor NULL. Uma implementao dessa funo
mostrada a seguir:
/* funo vazia: retorna 1 se vazia ou 0 se no vazia */
int vazia (Lista* l)
{
if (l == NULL)
return 1;
else
return 0;
}

Essa funo pode ser re-escrita de forma mais compacta, conforme mostrado abaixo:
/* funo vazia: retorna 1 se vazia ou 0 se no vazia */
int vazia (Lista* l)
{
return (l == NULL);
}

Funo de busca
Outra funo til consiste em verificar se um determinado elemento est presente na
lista. A funo recebe a informao referente ao elemento que queremos buscar e
fornece como valor de retorno o ponteiro do n da lista que representa o elemento. Caso
o elemento no seja encontrado na lista, o valor retornado NULL.
Estruturas de Dados PUC-Rio

10-4

/* funo busca: busca um elemento na lista */


Lista* busca (Lista* l, int v)
{
Lista* p;
for (p=l; p!=NULL; p=p->prox)
if (p->info == v)
return p;
return NULL;
/* no achou o elemento */
}

Funo que retira um elemento da lista


Para completar o conjunto de funes que manipulam uma lista, devemos implementar
uma funo que nos permita retirar um elemento. A funo tem como parmetros de
entrada a lista e o valor do elemento que desejamos retirar, e deve retornar o valor
atualizado da lista, pois, se o elemento removido for o primeiro da lista, o valor da lista
deve ser atualizado.
A funo para retirar um elemento da lista mais complexa. Se descobrirmos que o
elemento a ser retirado o primeiro da lista, devemos fazer com que o novo valor da
lista passe a ser o ponteiro para o segundo elemento, e ento podemos liberar o espao
alocado para o elemento que queremos retirar. Se o elemento a ser removido estiver no
meio da lista, devemos fazer com que o elemento anterior a ele passe a apontar para o
elemento seguinte, e ento podemos liberar o elemento que queremos retirar. Devemos
notar que, no segundo caso, precisamos do ponteiro para o elemento anterior para
podermos acertar o encadeamento da lista. As Figuras 9.4 e 9.5 ilustram as operaes de
remoo.
prim

10.3.
ULL
Info1

Info2

Info3

Figura 9.4: Remoo do primeiro elemento da lista.

prim

Info1

Info2

Info3

Figura 9.5: Remoo de um elemento no meio da lista.

10.4.
ULL a
Uma possvel implementao da funo para retirar um elemento da lista mostrada
seguir. Inicialmente, busca-se o elemento que se deseja retirar, guardando uma
referncia para o elemento anterior.

Estruturas de Dados PUC-Rio

10-5

/* funo retira: retira


Lista* retira (Lista* l,
Lista* ant = NULL; /*
Lista* p = l;
/*

elemento da lista */
int v) {
ponteiro para elemento anterior */
ponteiro para percorrer a lista*/

/* procura elemento na lista, guardando anterior */


while (p != NULL && p->info != v) {
ant = p;
p = p->prox;
}
/* verifica se achou elemento */
if (p == NULL)
return l;
/* no achou: retorna lista original */
/* retira elemento */
if (ant == NULL) {
/* retira elemento do inicio */
l = p->prox;
}
else {
/* retira elemento do meio da lista */
ant->prox = p->prox;
}
free(p);
return l;
}

O caso de retirar o ltimo elemento da lista recai no caso de retirar um elemento no


meio da lista, conforme pode ser observado na implementao acima. Mais adiante,
estudaremos a implementao de filas com listas encadeadas. Numa fila, devemos
armazenar, alm do ponteiro para o primeiro elemento, um ponteiro para o ltimo
elemento. Nesse caso, se for removido o ltimo elemento, veremos que ser necessrio
atualizar a fila.
Funo para liberar a lista
Uma outra funo til que devemos considerar destri a lista, liberando todos os
elementos alocados. Uma implementao dessa funo mostrada abaixo. A funo
percorre elemento a elemento, liberando-os. importante observar que devemos
guardar a referncia para o prximo elemento antes de liberar o elemento corrente (se
liberssemos o elemento e depois tentssemos acessar o encadeamento, estaramos
acessando um espao de memria que no estaria mais reservado para nosso uso).
void libera (Lista* l)
{
Lista* p = l;
while (p != NULL) {
Lista* t = p->prox; /* guarda referncia para o prximo elemento
*/
free(p);
/* libera a memria apontada por p */
p = t;
/* faz p apontar para o prximo */
}
}

Estruturas de Dados PUC-Rio

10-6

Um programa que ilustra a utilizao dessas funes mostrado a seguir.


#include <stdio.h>
int main (void) {
Lista* l;
l = inicializa();
l = insere(l, 23);
l = insere(l, 45);
l = insere(l, 56);
l = insere(l, 78);
imprime(l);
l = retira(l, 78);
imprime(l);
l = retira(l, 45);
imprime(l);
libera(l);
return 0;
}

/*
/*
/*
/*
/*
/*
/*

declara uma lista no iniciada */


inicia lista vazia */
insere na lista o elemento 23 */
insere na lista o elemento 45 */
insere na lista o elemento 56 */
insere na lista o elemento 78 */
imprimir: 78 56 45 23 */

/* imprimir: 56 45 23 */
/* imprimir: 56 23 */

Mais uma vez, observe que no podemos deixar de atualizar a varivel que representa a
lista a cada insero e a cada remoo de um elemento. Esquecer de atribuir o valor de
retorno varivel que representa a lista pode gerar erros graves. Se, por exemplo, a
funo retirar o primeiro elemento da lista, a varivel que representa a lista, se no fosse
atualizada, estaria apontando para um n j liberado. Como alternativa, poderamos
fazer com que as funes insere e retira recebessem o endereo da varivel que
representa a lista. Nesse caso, os parmetros das funes seriam do tipo ponteiro para
lista (Lista** l) e seu contedo poderia ser acessado/atualizado de dentro da funo
usando o operador contedo (*l).
Manuteno da lista ordenada
A funo de insero vista acima armazena os elementos na lista na ordem inversa
ordem de insero, pois um novo elemento sempre inserido no incio da lista. Se
quisermos manter os elementos na lista numa determinada ordem, temos que encontrar
a posio correta para inserir o novo elemento. Essa funo no eficiente, pois temos
que percorrer a lista, elemento por elemento, para acharmos a posio de insero. Se a
ordem de armazenamento dos elementos dentro da lista no for relevante, optamos por
fazer inseres no incio, pois o custo computacional disso independe do nmero de
elementos na lista.
No entanto, se desejarmos manter os elementos em ordem, cada novo elemento deve ser
inserido na ordem correta. Para exemplificar, vamos considerar que queremos manter
nossa lista de nmeros inteiros em ordem crescente. A funo de insero, neste caso,
tem a mesma assinatura da funo de insero mostrada, mas percorre os elementos da
lista a fim de encontrar a posio correta para a insero do novo. Com isto, temos que
saber inserir um elemento no meio da lista. A Figura 9.6 ilustra a insero de um
elemento no meio da lista.

Estruturas de Dados PUC-Rio

10-7

prim
Info1

Info2

Info3

10.5.
ULL
Novo

Figura 9.6: Insero de um elemento no meio da lista.

Conforme ilustrado na figura, devemos localizar o elemento da lista que ir preceder o


elemento novo a ser inserido. De posse do ponteiro para esse elemento, podemos
encadear o novo elemento na lista. O novo apontar para o prximo elemento na lista e
o elemento precedente apontar para o novo. O cdigo abaixo ilustra a implementao
dessa funo. Neste caso, utilizamos uma funo auxiliar responsvel por alocar
memria para o novo n e atribuir o campo da informao.
/* funo auxiliar: cria e inicializa um n */
Lista* cria (int v)
{
Lista* p = (Lista*) malloc(sizeof(Lista));
p->info = v;
return p;
}
/* funo insere_ordenado: insere elemento em ordem */
Lista* insere_ordenado (Lista* l, int v)
{
Lista* novo = cria(v); /* cria novo n */
Lista* ant = NULL;
/* ponteiro para elemento anterior */
Lista* p = l;
/* ponteiro para percorrer a lista*/
/* procura posio de insero */
while (p != NULL && p->info < v) {
ant = p;
p = p->prox;
}
/* insere elemento */
if (ant == NULL) {
/* insere elemento no incio */
novo->prox = l;
l = novo;
}
else {
/* insere elemento no meio da lista */
novo->prox = ant->prox;
ant->prox = novo;
}
return l;
}

Devemos notar que essa funo, analogamente ao observado para a funo de remoo,
tambm funciona se o elemento tiver que ser inserido no final da lista.

Estruturas de Dados PUC-Rio

10-8

10.2. Implementaes recursivas


Uma lista pode ser definida de maneira recursiva. Podemos dizer que uma lista
encadeada representada por:
uma lista vazia; ou
um elemento seguido de uma (sub-)lista.
Neste caso, o segundo elemento da lista representa o primeiro elemento da sub-lista.
Com base na definio recursiva, podemos implementar as funes de lista
recursivamente. Por exemplo, a funo para imprimir os elementos da lista pode ser reescrita da forma ilustrada abaixo:
/* Funo imprime recursiva */
void imprime_rec (Lista* l)
{
if (vazia(l))
return;
/* imprime primeiro elemento */
printf(info: %d\n,l->info);
/* imprime sub-lista */
imprime_rec(l->prox);
}

fcil alterarmos o cdigo acima para obtermos a impresso dos elementos da lista em
ordem inversa: basta invertermos a ordem das chamadas s funes printf e
imprime_rec.
A funo para retirar um elemento da lista tambm pode ser escrita de forma recursiva.
Neste caso, s retiramos um elemento se ele for o primeiro da lista (ou da sub-lista). Se
o elemento que queremos retirar no for o primeiro, chamamos a funo recursivamente
para retirar o elemento da sub-lista.
/* Funo retira recursiva */
Lista* retira_rec (Lista* l, int v)
{
if (vazia(l))
return l;
/* lista vazia: retorna valor original */
/* verifica se elemento a ser retirado o primeiro */
if (l->info == v) {
Lista* t = l;
/* temporrio para poder liberar */
l = l->prox;
free(t);
}
else {
/* retira de sub-lista */
l->prox = retira_rec(l->prox,v);
}
return l;
}

A funo para liberar uma lista tambm pode ser escrita recursivamente, de forma
bastante simples. Nessa funo, se a lista no for vazia, liberamos primeiro a sub-lista e
depois liberamos a lista.

Estruturas de Dados PUC-Rio

10-9

void libera_rec (Lista* l)


{
if (!vazia(l))
{
libera_rec(l->prox);
free(l);
}
}

Exerccio: Implemente uma funo que verifique se duas listas encadeadas so iguais.
Duas listas so consideradas iguais se tm a mesma seqncia de elementos. O
prottipo da funo deve ser dado por:
int igual (Lista* l1, Lista* l2);

Exerccio: Implemente uma funo que crie uma cpia de uma lista encadeada. O
prottipo da funo deve ser dado por:
Lista* copia (Lista* l);

10.3. Listas genricas


Um n de uma lista encadeada contm basicamente duas informaes: o encadeamento
e a informao armazenada. Assim, a estrutura de um n para representar uma lista de
nmeros inteiros dada por:
struct lista {
int info;
struct lista *prox;
};
typedef struct lista Lista;

Analogamente, se quisermos representar uma lista de nmeros reais, podemos definir a


estrutura do n como sendo:
struct lista {
float info;
struct lista *prox;
};
typedef struct lista Lista;

A informao armazenada na lista no precisa ser necessariamente um dado simples.


Podemos, por exemplo, considerar a construo de uma lista para armazenar um
conjunto de retngulos. Cada retngulo definido pela base b e pela altura h. Assim, a
estrutura do n pode ser dada por:
struct lista {
float b;
float h;
struct lista *prox;
};
typedef struct lista Lista;

Esta mesma composio pode ser escrita de forma mais clara se definirmos um tipo
adicional que represente a informao. Podemos definir um tipo Retangulo e us-lo
para representar a informao armazenada na lista.
struct retangulo {
float b;
float h;
};
typedef struct retangulo Retangulo;

Estruturas de Dados PUC-Rio

10-10

struct lista {
Retangulo info;
struct lista *prox;
};
typedef struct lista Lista;

Aqui, a informao volta a ser representada por um nico campo (info), que uma
estrutura. Se p fosse um ponteiro para um n da lista, o valor da base do retngulo
armazenado nesse n seria acessado por: p->info.b.
Ainda mais interessante termos o campo da informao representado por um ponteiro
para a estrutura, em vez da estrutura em si.
struct retangulo {
float b;
float h;
};
typedef struct retangulo Retangulo;
struct lista {
Retangulo *info;
struct lista *prox;
};
typedef struct lista Lista;

Neste caso, para criarmos um n, temos que fazer duas alocaes dinmicas: uma para
criar a estrutura do retngulo e outra para criar a estrutura do n. O cdigo abaixo ilustra
uma funo para a criao de um n.
Lista* cria (void)
{
Retangulo* r = (Retangulo*) malloc(sizeof(Retangulo));
Lista* p = (Lista*) malloc(sizeof(Lista));
p->info = r;
p->prox = NULL;
return p;
}

Naturalmente, o valor da base associado a um n p seria agora acessado por: p->info>b . A vantagem dessa representao (utilizando ponteiros) que, independente da
informao armazenada na lista, a estrutura do n sempre composta por um ponteiro
para a informao e um ponteiro para o prximo n da lista.
A representao da informao por um ponteiro nos permite construir listas
heterogneas, isto , listas em que as informaes armazenadas diferem de n para n.
Diversas aplicaes precisam construir listas heterogneas, pois necessitam agrupar
elementos afins mas no necessariamente iguais. Como exemplo, vamos considerar uma
aplicao que necessite manipular listas de objetos geomtricos planos para clculos de
reas. Para simplificar, vamos considerar que os objetos podem ser apenas retngulos,
tringulos ou crculos. Sabemos que as reas desses objetos so dadas por:
r = b*h

Estruturas de Dados PUC-Rio

t=

b*h
2

c = p r2

10-11

Devemos definir um tipo para cada objeto a ser representado:


struct retangulo {
float b;
float h;
};
typedef struct retangulo Retangulo;
struct triangulo {
float b;
float h;
};
typedef struct triangulo Triangulo;
struct circulo {
float r;
};
typedef struct circulo Circulo;

O n da lista deve ser composto por trs campos:


um identificador de qual objeto est armazenado no n
um ponteiro para a estrutura que contm a informao
um ponteiro para o prximo n da lista
importante salientar que, a rigor, a lista homognea, no sentido de que todos os ns
contm as mesmas informaes. O ponteiro para a informao deve ser do tipo
genrico, pois no sabemos a princpio para que estrutura ele ir apontar: pode apontar
para um retngulo, um tringulo ou um crculo. Um ponteiro genrico em C
representado pelo tipo void*. A funo do tipo ponteiro genrico pode representar
qualquer endereo de memria, independente da informao de fato armazenada nesse
espao. No entanto, de posse de um ponteiro genrico, no podemos acessar a memria
por ele apontada, j que no sabemos a informao armazenada. Por esta razo, o n de
uma lista genrica deve guardar explicitamente um identificador do tipo de objeto de
fato armazenado. Consultando esse identificador, podemos converter o ponteiro
genrico no ponteiro especfico para o objeto em questo e, ento, acessarmos os
campos do objeto.
Como identificador de tipo, podemos usar valores inteiros definidos como constantes
simblicas:
#define RET 0
#define TRI 1
#define CIR 2

Assim, na criao do n, armazenamos o identificador de tipo correspondente ao objeto


sendo representado. A estrutura que representa o n pode ser dada por:
/* Define o n da estrutura */
struct listagen {
int
tipo;
void *info;
struct listagen *prox;
};
typedef struct listagen ListaGen;

A funo para a criao de um n da lista pode ser definida por trs variaes, uma para
cada tipo de objeto que pode ser armazenado.

Estruturas de Dados PUC-Rio

10-12

/* Cria um n com um retngulo, inicializando os campos base e altura


*/
ListaGen* cria_ret (float b, float h)
{
Retangulo* r;
ListaGen* p;
/* aloca retngulo */
r = (Retangulo*) malloc(sizeof(Retangulo));
r->b = b;
r->h = h;
/* aloca n */
p = (ListaGen*) malloc(sizeof(ListaGen));
p->tipo = RET;
p->info = r;
p->prox = NULL;
return p;
}
/* Cria um n com um tringulo, inicializando os campos base e altura
*/
ListaGen* cria_tri (float b, float h)
{
Triangulo* t;
ListaGen* p;
/* aloca tringulo */
t = (Triangulo*) malloc(sizeof(Triangulo));
t->b = b;
t->h = h;
/* aloca n */
p = (ListaGen*) malloc(sizeof(ListaGen));
p->tipo = TRI;
p->info = t;
p->prox = NULL;
return p;
}
/* Cria um n com um crculo, inicializando o campo raio */
ListaGen* cria_cir (float r)
{
Circulo* c;
ListaGen* p;
/* aloca crculo */
c = (Circulo*) malloc(sizeof(Circulo));
c->r = r;
/* aloca n */
p = (ListaGen*) malloc(sizeof(ListaGen));
p->tipo = CIR;
p->info = c;
p->prox = NULL;
return p;
}

Uma vez criado o n, podemos inseri-lo na lista como j vnhamos fazendo com ns de
listas homogneas. As constantes simblicas que representam os tipos dos objetos
podem ser agrupadas numa enumerao (ver seo 7.5):
Estruturas de Dados PUC-Rio

10-13

enum {
RET,
TRI,
CIR
};

Manipulao de listas heterogneas


Para exemplificar a manipulao de listas heterogneas, considerando a existncia de
uma lista com os objetos geomtricos apresentados acima, vamos implementar uma
funo que fornea como valor de retorno a maior rea entre os elementos da lista. Uma
implementao dessa funo mostrada abaixo, onde criamos uma funo auxiliar que
calcula a rea do objeto armazenado num determinado n da lista:
#define PI 3.14159
/* funo auxiliar: calcula rea correspondente ao n */
float area (ListaGen *p)
{
float a;
/* rea do elemento */
switch (p->tipo) {
case RET:
{
/* converte para retngulo e calcula rea */
Retangulo *r = (Retangulo*) p->info;
a = r->b * r->h;
}
break;
case TRI:
{
/* converte para tringulo e calcula rea */
Triangulo *t = (Triangulo*) p->info;
a = (t->b * t->h) / 2;
}
break;
case CIR:
{
/* converte para crculo e calcula rea */
Circulo *c = (Circulo)p->info;
a = PI * c->r * c->r;
}
break;
}
return a;
}
/* Funo para clculo da maior rea */
float max_area (ListaGen* l)
{
float amax = 0.0;
/* maior rea */
ListaGen* p;
for (p=l; p!=NULL; p=p->prox) {
float a = area(p);
/* rea do n */
if (a > amax)
amax = a;
}
return amax;
}

Estruturas de Dados PUC-Rio

10-14

A funo para o clculo da rea mostrada acima pode ser subdivida em funes
especficas para o clculo das reas de cada objeto geomtrico, resultando em um
cdigo mais estruturado.
/* funo para clculo da rea de um retngulo */
float ret_area (Retangulo* r)
{
return r->b * r->h;
}
/* funo para clculo da rea de um tringulo */
float tri_area (Triangulo* t)
{
return (t->b * t->h) / 2;
}
/* funo para clculo da rea de um crculo */
float cir_area (Circulo* c)
{
return PI * c->r * c->r;
}
/* funo para clculo da rea do n (verso 2) */
float area (ListaGen* p)
{
float a;
switch (p->tipo) {
case RET:
a = ret_area(p->info);
break;
case TRI:
a = tri_area(p->info);
break;
case CIR:
a = cir_area(p->info);
break;
}
return a;
}

Neste caso, a converso de ponteiro genrico para ponteiro especfico feita quando
chamamos uma das funes de clculo da rea: passa-se um ponteiro genrico que
atribudo, atravs da converso implcita de tipo, para um ponteiro especfico1.
Devemos salientar que, quando trabalhamos com converso de ponteiros genricos,
temos que garantir que o ponteiro armazene o endereo onde de fato existe o tipo
especfico correspondente. O compilador no tem como checar se a converso vlida;
a verificao do tipo passa a ser responsabilidade do programador.

10.4. Listas circulares


Algumas aplicaes necessitam representar conjuntos cclicos. Por exemplo, as arestas
que delimitam uma face podem ser agrupadas por uma estrutura circular. Para esses
casos, podemos usar listas circulares.
Numa lista circular, o ltimo elemento tem como prximo o primeiro elemento da lista,
formando um ciclo. A rigor, neste caso, no faz sentido falarmos em primeiro ou ltimo
1

Este cdigo no vlido em C++. A linguagem C++ no tem converso implcita de um ponteiro
genrico para um ponteiro especfico. Para compilar em C++, devemos fazer a converso explicitamente.
Por exemplo:
a = ret_area((Retangulo*)p->info);
Estruturas de Dados PUC-Rio

10-15

elemento. A lista pode ser representada por um ponteiro para um elemento inicial
qualquer da lista. A Figura 9.7 ilustra o arranjo da memria para a representao de uma
lista circular.
ini
Info1

Info2

Info3

Figura 9.7: Arranjo da memria de uma lista circular.

Para percorrer os elementos de uma lista circular, visitamos todos os elementos a partir
do ponteiro do elemento inicial at alcanarmos novamente esse mesmo elemento. O
cdigo abaixo exemplifica essa forma de percorrer os elementos. Neste caso, para
simplificar, consideramos uma lista que armazena valores inteiros. Devemos salientar
que o caso em que a lista vazia ainda deve ser tratado (se a lista vazia, o ponteiro
para um elemento inicial vale NULL).
void imprime_circular (Lista* l)
{
Lista* p = l;
/* faz p apontar para o n inicial */
/* testa se lista no vazia */
if (p) {
{
/* percorre os elementos at alcanar novamente o incio */
do {
printf("%d\n", p->info);
/* imprime informao do n */
p = p->prox;
/* avana para o prximo n */
} while (p != l);
}

Exerccio: Escreva as funes para inserir e retirar um elemento de uma lista circular.

10.5. Listas duplamente encadeadas**


A estrutura de lista encadeada vista nas sees anteriores caracteriza-se por formar um
encadeamento simples entre os elementos: cada elemento armazena um ponteiro para o
prximo elemento da lista. Desta forma, no temos como percorrer eficientemente os
elementos em ordem inversa, isto , do final para o incio da lista. O encadeamento
simples tambm dificulta a retirada de um elemento da lista. Mesmo se tivermos o
ponteiro do elemento que desejamos retirar, temos que percorrer a lista, elemento por
elemento, para encontrarmos o elemento anterior, pois, dado um determinado elemento,
no temos como acessar diretamente seu elemento anterior.
Para solucionar esses problemas, podemos formar o que chamamos de listas
duplamente encadeadas. Nelas, cada elemento tem um ponteiro para o prximo
elemento e um ponteiro para o elemento anterior. Desta forma, dado um elemento,
podemos acessar ambos os elementos adjacentes: o prximo e o anterior. Se tivermos
um ponteiro para o ltimo elemento da lista, podemos percorrer a lista em ordem
inversa, bastando acessar continuamente o elemento anterior, at alcanar o primeiro
elemento da lista, que no tem elemento anterior (o ponteiro do elemento anterior vale
NULL).

Estruturas de Dados PUC-Rio

10-16

A Figura 9.8 esquematiza a estruturao de uma lista duplamente encadeada.

prim
Info1

Info2

Info3

Figura 9.8: Arranjo da memria de uma lista duplamente encadeada.

Para exemplificar a implementao de listas duplamente encadeadas, vamos novamente


considerar o exemplo simples no qual queremos armazenar valores inteiros na lista. O
n da lista pode ser representado pela estrutura abaixo e a lista pode ser representada
atravs do ponteiro para o primeiro n.
struct lista2 {
int info;
struct lista2* ant;
struct lista2* prox;
};
typedef struct Lista2 Lista2;

Com base nas definies acima, exemplificamos a seguir a implementao de algumas


funes que manipulam listas duplamente encadeadas.
Funo de insero
O cdigo a seguir mostra uma possvel implementao da funo que insere novos
elementos no incio da lista. Aps a alocao do novo elemento, a funo acertar o
duplo encadeamento.
/* insero no incio */
Lista2* insere (Lista2* l, int v)
{
Lista2* novo = (Lista2*) malloc(sizeof(Lista2));
novo->info = v;
novo->prox = l;
novo->ant = NULL;
/* verifica se lista no est vazia */
if (l != NULL)
l->ant = novo;
return novo;
}

Nessa funo, o novo elemento encadeado no incio da lista. Assim, ele tem como
prximo elemento o antigo primeiro elemento da lista e como anterior o valor NULL. A
seguir, a funo testa se a lista no era vazia, pois, neste caso, o elemento anterior do
ento primeiro elemento passa a ser o novo elemento. De qualquer forma, o novo
elemento passa a ser o primeiro da lista, e deve ser retornado como valor da lista
atualizada. A Figura 9.9 ilustra a operao de insero de um novo elemento no incio
da lista.

Estruturas de Dados PUC-Rio

10-17

prim

Novo

Info1

Info2

Info3

Figura 9.9: Insero de um novo elemento no incio da lista.

Funo de busca
A funo de busca recebe a informao referente ao elemento que queremos buscar e
tem como valor de retorno o ponteiro do n da lista que representa o elemento. Caso o
elemento no seja encontrado na lista, o valor retornado NULL.
/* funo busca: busca um elemento na lista */
Lista2* busca (Lista2* l, int v)
{
Lista2* p;
for (p=l; p!=NULL; p=p->prox)
if (p->info == v)
return p;
return NULL;
/* no achou o elemento */
}

Funo que retira um elemento da lista


A funo de remoo fica mais complicada, pois temos que acertar o encadeamento
duplo. Em contrapartida, podemos retirar um elemento da lista conhecendo apenas o
ponteiro para esse elemento. Desta forma, podemos usar a funo de busca acima para
localizar o elemento e em seguida acertar o encadeamento, liberando o elemento ao
final.
Se p representa o ponteiro do elemento que desejamos retirar, para acertar o
encadeamento devemos conceitualmente fazer:
p->ant->prox = p->prox;
p->prox->ant = p->ant;

isto , o anterior passa a apontar para o prximo e o prximo passa a apontar para o
anterior. Quando p apontar para um elemento no meio da lista, as duas atribuies
acima so suficientes para efetivamente acertar o encadeamento da lista. No entanto, se
p for um elemento no extremo da lista, devemos considerar as condies de contorno.
Se p for o primeiro, no podemos escrever p->ant->prox, pois p->ant NULL; alm
disso, temos que atualizar o valor da lista, pois o primeiro elemento ser removido.

Estruturas de Dados PUC-Rio

10-18

Uma implementao da funo para retirar um elemento mostrada a seguir:


/* funo retira: retira elemento da lista */
Lista2* retira (Lista2* l, int v) {
Lista2* p = busca(l,v);
if (p == NULL)
return l;
/* no achou o elemento: retorna lista inalterada */
/* retira elemento do encadeamento */
if (l == p)
l = p->prox;
else
p->ant->prox = p->prox;
if (p->prox != NULL)
p->prox->ant = p->ant;
free(p);
return l;
}

Lista circular duplamente encadeada


Uma lista circular tambm pode ser construda com encadeamento duplo. Neste caso, o
que seria o ltimo elemento da lista passa ter como prximo o primeiro elemento, que,
por sua vez, passa a ter o ltimo como anterior. Com essa construo podemos percorrer
a lista nos dois sentidos, a partir de um ponteiro para um elemento qualquer. Abaixo,
ilustramos o cdigo para imprimir a lista no sentido reverso, isto , percorrendo o
encadeamento dos elementos anteriores.
void imprime_circular_rev (Lista2* l)
{
Lista2* p = l;
/* faz p apontar para o n inicial */
/* testa se lista no vazia */
if (p) {
{
/* percorre os elementos at alcanar novamente o incio */
do {
printf("%d\n", p->info);
/* imprime informao do n */
p = p->ant;
/* "avana" para o n anterior */
} while (p != l);
}

Exerccio: Escreva as funes para inserir e retirar um elemento de uma lista circular
duplamente encadeada.

Estruturas de Dados PUC-Rio

10-19

Você também pode gostar