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.
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;
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.
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.
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
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)
void imprima (celula *ini) { celula *p; for (p = ini; p != NULL; p = p->prox) printf ("%d\n", p->conteudo); }
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; }
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) {
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.
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.
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.
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,
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) {
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.