You are on page 1of 10

Listas encadeadas

Uma lista encadeada uma representao de uma sequncia de objetos na memria do computador. Cada elemento da sequncia armazenado em uma clula da lista: o primeiro elemento na primeira clula, o segundo na segunda e assim por diante. Veja o verbete Linked list na Wikipedia.

Estrutura de uma lista encadeada


Uma lista encadeada (= linked list = lista ligada) uma sequncia de clulas; cada clula contm um objeto de algum tipo e o endereo da clula seguinte. Suporemos nesta pgina que os objetos armazenados nas clulas so do tipo int. A estrutura de cada clula de uma tal lista pode ser definida assim:
struct cel { int conteudo; conteudo prox

struct cel *prox; };

conveniente tratar as clulas como um novo tipo-de-dados e atribuir um nome a esse novo tipo:
typedef struct cel celula;

Uma clula

e um ponteiro
c;

para uma clula podem ser declarados assim:

celula

celula *p;

Se c uma clula ento c.conteudo o contedo da clula e c.prox o endereo da prxima clula. Se endereo de uma clula, ento p->conteudo o contedo da clula e p->prox o endereo da prxima clula. Se p o endereo da ltima clula da lista ento
p->prox

vale

NULL.

Endereo de uma lista encadeada

O endereo de uma lista encadeada o endereo de sua primeira clula. Se p o endereo de uma lista, convm, s vezes, dizer simplesmente "p uma lista". Listas so animais eminentemente recursivos. Para tornar isso evidente, basta fazer a seguinte observao: se p uma lista ento vale uma das seguintes alternativas:

p == NULL p->prox

ou uma lista.

Listas com cabea e sem cabea


Uma lista encadeada pode ser organizada de duas maneiras diferentes, um bvia e outra menos bvia.

Lista com cabea. O contedo da primeira clula irrelevante: ela serve apenas para marcar o incio da lista. A primeira clula a cabea (= head cell = dummy cell) da lista. A primeira clula est sempre no mesmo lugar na memria, mesmo que a lista fique vazia. Digamos que ini o endereo da primeira clula. Ento ini->prox == NULL se e somente se a lista est vazia. Para criar uma lista vazia, basta dizer
celula c, *ini; c.prox = NULL; ini = &c;

ou

celula *ini; ini = malloc (sizeof (celula)); ini->prox = NULL;

Lista sem cabea. O contedo da primeira clula to relevante quanto o das demais. Nesse caso, a lista est vazia se o endereo de sua primeira clula NULL. Para criar uma lista vazia basta fazer
celula *ini; ini = NULL;

[Veja observao sobre alocao de pequenos blocos de bytes na pgina "Alocao dinmica de memria".] Suporemos no que segue que nossas listas tm cabea. O caso de listas sem cabea ser tratado nos exerccios. Eu prefiro listas sem cabea (porque so mais "puras"), mas a vida do programador fica mais fcil quando a lista tem cabea.

Exemplos
Eis como se imprime o contedo de uma lista encadeada com cabea:
// Imprime o contedo de uma lista encadeada // com cabea. O endereo da primeira clula ini.

void imprima (celula *ini) { celula *p; for (p = ini->prox; p != NULL; p = p->prox)

printf ("%d\n", p->conteudo); }

Eis a correspondente funo para lista sem cabea:


// Imprime o contedo de uma lista encadeada ini. // A lista no tem cabea.

void imprima (celula *ini) { celula *p; for (p = ini; p != NULL; p = p->prox) printf ("%d\n", p->conteudo); }

Busca em uma lista encadeada


Veja como fcil verificar se um inteiro x pertence a uma lista encadeada, ou seja, se igual ao contedo de alguma clula da lista:
// Esta funo recebe um inteiro x e uma lista // encadeada de inteiros. O endereo da lista // ini e ela tem uma celula-cabea. A funo // devolve o endereo de uma celula que contm x. // Se tal celula no existe, a funo devolve NULL.

celula *busca (int x, celula *ini) { celula *p; p = ini->prox; while (p != NULL && p->conteudo != x) p = p->prox; return p;

Que beleza! Nada de variveis booleanas! A funo se comporta bem at mesmo quando a lista est vazia. Eis uma verso recursiva da mesma funo:
celula *busca2 (int x, celula *ini) { if (ini->prox == NULL) return NULL; if (ini->prox->conteudo == x) return ini->prox; return busca2 (x, ini->prox); }

Exerccios
1. Critique a funo abaixo. Ao receber uma lista encadeada com cabea e um inteiro x, ela promete devolver o endereo de uma clula com contedo x. Se tal clula no existe, promete devolver NULL.
2. celula *busca (int x, celula *ini) { 3. int achou; 4. celula *p; 5. achou = 0; 6. p = ini->prox; 7. while (p != NULL && !achou) { 8. if (p->conteudo == x) achou = 1; 9. p = p->prox; } 10. if (achou) return p; 11. else return NULL; }

12. Escreva uma verso da funo busca para listas sem cabea. 13. [MNIMO] Escreva uma funo que encontre uma clula de contedo mnimo. Faa duas verses: uma iterativa e uma recursiva. 14. Escreva uma funo que faa um busca em uma lista crescente. Faa verses para listas com e sem cabea. Faa verses recursiva e iterativa. 15. [PONTO MDIO DE UMA LISTA] Escreva uma funo que receba uma lista encadeada e devolva o endereo de um n que esteja o mais prximo possvel do meio da lista. Faa isso sem contar explicitamente o nmero de ns da lista. 16. VERIFICAO DO TAMANHO. Compile e execute o seguinte programa:
17. 18. 19. 20. 21. 22. 23. typedef struc cel celula; struct cel { int conteudo; celula *prox; }; int main (void) { printf ("sizeof (celula) = %d\n", sizeof (celula));

24. 25.

return 0; }

Insero em uma lista


Quero inserir (= insert) uma nova clula com contedo x entre a posio apontada por p e a posio seguinte [por que seguinte e no anterior?] em uma lista encadeada. claro que isso s faz sentido se p diferente de NULL.
// Esta funo insere uma nova celula em uma // lista encadeada. A nova celula tem conteudo // x e inserida entre a celula apontada por // p e a seguinte. Supe-se que p != NULL.

void insere (int x, celula *p) { celula *nova; nova = mallocX (sizeof (celula)); nova->conteudo = x; nova->prox = p->prox; p->prox = nova; }

Veja que maravilha! No preciso movimentar clulas para "criar espao" para um nova clula, como fizemos para inserir um elemento de um vetor. Basta mudar os valores de alguns ponteiros. Observe tambm que a funo se comporta corretamente mesmo quando quero inserir no fim da lista, isto , quando p->prox == NULL. Se a lista tem cabea, a funo pode ser usada para inserir no incio da lista: basta que p aponte para a clula-cabea. Infelizmente, a funo no capaz de inserir antes da primeira clula de uma lista sem cabea. O tempo que a funo consome no depende do ponto da lista onde quero fazer a insero: tanto faz inserir uma nova clula na parte inicial da lista quanto na parte final. Isso bem diferente do que ocorre com a insero em um vetor.

Exerccios
7. Por que a seguinte verso de insere no funciona?
8. void insere (int x, celula *p) {

9. 10. 11. 12.

celula nova; nova.conteudo = x; nova.prox = p->prox; p->prox = &nova; }

13. Escreva uma funo que insira um novo elemento em uma lista encadeada sem cabea. Ser preciso tomar algumas decises de projeto antes de comear a programar.

Remoo em uma lista


Suponha que quero remover (= to remove = to delete) uma certa clula da lista. Como posso especificar a clula em questo? A ideia mais bvia apontar para a clula que quero remover. Mas fcil perceber que essa ideia no boa. melhor apontar para a clula anterior que quero remover. Infelizmente, isso traz uma nova dificuldade: no h como pedir a remoo da primeira clula. Portanto, vamos nos limitar s listas com cabea. Vamos supor que p o endereo de uma clula de uma lista com cabea e que desejo remover a clula apontada por p->prox. (Note que a funo de remoo no precisa saber onde a lista comea.)
// Esta funo recebe o endereo p de uma celula // de uma lista encadeada. A funo remove da // lista a celula p->prox. A funo supe que // p != NULL e p->prox != NULL.

void remove (celula *p) { celula *morta; morta = p->prox; p->prox = morta->prox; free (morta); }

Veja que maravilha! No preciso copiar informaes de um lugar para outro, como fizemos para remover um elemento de um vetor: basta mudar o valor de um ponteiro. A funo consome sempre o mesmo tempo, quer a clula a ser removida esteja perto do incio da lista, quer esteja perto do fim.

Exerccios
9. Critique a seguinte verso da funo remove:
10. void remove (celula *p, celula *ini) { 11. celula *morta; 12. morta = p->prox; 13. if (morta->prox == NULL) p->prox = NULL;

14. 15.

else p->prox = morta->prox; free (morta); }

16. Invente um jeito de remover uma clula de uma lista encadeada sem cabea. (Ser preciso tomar algumas decises de projeto antes de comear a programar.)

Mais exerccios
11. Escreva uma funo que copie um vetor para uma lista encadeada. Faa duas verses: uma iterativa e uma recursiva. 12. Escreva uma funo que copie uma lista encadeada para um vetor. Faa duas verses: uma iterativa e uma recursiva. 13. Escreva uma funo que faa uma cpia de uma lista dada. 14. Escreva uma funo que concatena duas listas encadeadas (isto , "amarra" a segunda no fim da primeira). 15. Escreva uma funo que conta o nmero de clulas de uma lista encadeada. 16. Escreva uma funo que remove a k-sima clula de uma lista encadeada sem cabea. Escreva uma funo que insere na lista uma nova clula com contedo x entre a k-sima e a k+1-sima clulas. 17. Escreva uma funo que verifica se duas listas dadas so iguais, ou melhor, se tm o mesmo contedo. Faa duas verses: uma iterativa e uma recursiva. 18. Escreva uma funo que desaloca (funo free) todos os ns de uma lista encadeada. Estamos supondo, claro, que cada n da lista foi originalmente alocado por malloc. 19. Escreva uma funo que inverte a ordem das clulas de uma lista encadeada (a primeira passa a ser a ltima, a segunda passa a ser a penltima etc.). Faa isso sem usar espao auxiliar; apenas altere os ponteiros. D duas solues: uma iterativa e uma recursiva. 20. PROJETO DE PROGRAMAO. Digamos que um texto um vetor de caracteres contendo apenas letras, espaos e sinais de pontuao. Digamos que uma palavra um segmento maximal de texto que consiste apenas de letras. Escreva uma funo que recebe um texto e imprime uma relao de todas as palavras que ocorrem no texto juntamente com o nmero de ocorrncias de cada palavra.

Outros tipos de listas


A partir de agora, tudo festa: voc pode inventar uma grande variedade de tipos de listas encadeadas. Por exemplo, voc pode fazer uma lista encadeada circular: a ltima clula aponta para a primeira. A lista pode ou no ter uma clula-cabea (voc decide). Para especificar uma lista circular, basta fornecer um endereo (por exemplo, o endereo da ltima clula). Outro tipo til a lista duplamente encadeada: cada clula contm o endereo da clula anterior e o da clula seguinte. A lista pode ou no ter uma clula-cabea (voc decide). A lista pode at ter uma clula-rabo se voc achar isso til! Pense nas seguintes questes, apropriadas para qualquer tipo de lista encadeada. Em que condies a lista est vazia? Como remover a clula apontada por p? Idem para a clula seguinte apontada por p? Idem para a clula anterior apontada por p? Como inserir uma nova clula entre o elemento apontado por p e o seu antecessor? Idem entre p e seu sucessor?

Exerccios
21. Descreva, em linguagem C, a estrutura de uma das clula de uma lista duplamente encadeada. 22. Escreva uma funo que remove de uma lista duplamente encadeada a clula apontada por p. (Que dados sua funo recebe? Que coisa devolve?) 23. Escreva uma funo que insira em uma lista duplamente encadeada, logo aps a clula apontada por p, uma nova clula com contedo y. (Que dados sua funo recebe? Que coisa devolve?) 24. PROBLEMA DE JOSEPHUS. Imagine que temos n pessoas dispostas em crculo. Suponha que as pessoas esto numeradas 1 a n no sentido horrio. 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. Qual o nmero do sobrevivente?

Busca-e-remoo
Suponha que ini o endereo de uma lista encadeada com cabea. Nosso problema: Dado um inteiro y, remover da lista a primeira clula que contm y (se tal clula no existe, no preciso fazer nada).
// Esta funo recebe uma lista encadeada ini, // com cabea, e remove da lista a primeira // celula que contiver y, se tal celula existir.

void buscaEremove (int y, celula *ini) { celula *p, *q; p = ini; q = ini->prox; while (q != NULL && q->conteudo != y) { p = q; q = q->prox; } if (q != NULL) { p->prox = q->prox; free (q);

} }

Invariante: no incio de cada iterao (imediatamente antes da comparao de q com NULL), temos
q == p->prox

ou seja,

est sempre um passo frente de p.

Exerccios
25. Escreva uma funo busca-e-remove para listas encadeadas sem cabea (s pra ver que dor de cabea isso d).

Busca-e-insero
Mais uma vez, suponha que tenho uma lista encadeada ini, com cabea. ( bvio que ini diferente de NULL.) Nosso problema: Inserir na lista uma nova clula com contedo x imediatamente antes da primeira clula que tiver contedo y ; se tal clula no existe, inserir x no fim da lista.
// Esta funo recebe uma lista encadeada ini, // com cabea, e insere na lista uma nova celula // imediatamente antes da primeira que contiver y. // Se nenhuma celula contm y, insere a nova // celula no fim da lista. O conteudo da nova // celula x.

void buscaEinsere (int x, int y, celula *ini) { celula *p, *q, *nova; nova = mallocX (sizeof (celula)); nova->conteudo = x; p = ini; q = ini->prox; while (q != NULL && q->conteudo != y) {

p = q; q = q->prox; } nova->prox = q; p->prox = nova; }

Exerccios
26. Escreva uma funo busca-e-insere para listas encadeadas sem cabea (s pra ver que dor de cabea isso d). 27. Escreva uma funo para remover de uma lista encadeada todos os elementos que contm y.

You might also like