Buscar

58404724 TI listas em C

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 8 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 8 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

MC202 – Estruturas de Dados
Aulas 4–7: Listas Ligadas
Felipe P.G. Bergo
1 Listas Generalizadas
E´ muitas vezes deseja´vel armazenar uma sequ¨eˆncia de elementos em alguma ordem espec´ıfica, em
algum tipo de lista. Operac¸o˜es naturais sobre listas sa˜o:
• Inserir um novo elemento (no fim, no in´ıcio, em algum ponto espec´ıfico).
• Buscar por um determinado valor.
• Obter o n-e´simo elemento da lista.
• Remover um elemento da lista.
• A partir de um elemento, obter o pro´ximo elemento, ou o elemento anterior.
• Apagar toda a lista.
• Obter o primeiro ou o u´ltimo elemento da lista.
• Obter o nu´mero de elementos da lista.
• Percorrer todos os elementos da lista, do primeiro para o u´ltimo ou vice-versa.
Embora seja poss´ıvel realizar todas estas operac¸o˜es sobre um vetor, algumas delas tornam-se ine-
ficientes devido a` necessidade de manter os elementos em um espac¸o cont´ıguo de memo´ria. Listas
ligadas sa˜o uma soluc¸a˜o em geral mais eficiente para a implementac¸a˜o de listas.
2 Listas Ligadas
Uma lista ligada e´ uma colec¸a˜o de no´s que podem ser alocados em regio˜es na˜o cont´ıguas de memo´ria.
Cada no´ mantem uma ou mais refereˆncias (ponteiros) que indicam onde esta˜o localizados os no´s
relacionados (pro´ximo no´, no´ anterior).
Podemos nos referenciar a uma lista ligada atrave´s de seu primeiro no´. Entretanto, toda vez que
realizarmos uma inserc¸a˜o no in´ıcio da lista teremos que garantir que todas as partes do programa
que referenciam a lista tenha suas refereˆncias atualizadas. Para evitar este problema, e´ comum
usar um no´ especial, chamado no´-cabec¸a, e´ um ponteiro para o primeiro elemento da lista.
1
2.1 Lista Simplesmente Ligada
Nesta sec¸a˜o comentamos a implementac¸a˜o em C de uma lista simplesmente ligada com no´ cabec¸a.
Precisamos de dois tipos de dados: um tipo No´, que representa cada elemento da lista ligada, e um
tipo Lista que contem um ponteiro para o no´ cabec¸a:
typedef struct Noh {
int dado; // o dado poderia ser de qualquer tipo
struct Noh *prox; // ponteiro para proximo elemento
} Noh;
typedef struct Lista {
Noh *cabeca;
} Lista;
As operac¸o˜es ba´sicas sobre a estrutura sa˜o a criac¸a˜o de listas e de no´s. Ambas as operac¸o˜es levam
tempo constante, O(1):
Lista * lista_cria() {
Lista *L;
L = (Lista *) malloc(sizeof(Lista));
L->cabeca = NULL;
return L;
}
Noh * noh_cria(int dado) {
Noh *N;
N = (Noh *) malloc(sizeof(Noh));
N->dado = dado;
N->prox = NULL;
return N;
}
Ao contra´rio dos vetores, onde acessamos qualquer elemento usando um ı´ndice nume´rico, com uma
lista temos apenas acesso sequ¨eˆncial: para chegar no de´cimo elemento temos que passar por todos
os nove elementos anteriores. Para percorrer uma lista, precisamos obter seu primeiro elemento e
enta˜o obtemos os elementos subsequ¨entes seguindo os ponteiros de pro´ximo elemento em cada no´.
Estas duas operac¸o˜es tambe´m levam tempo constante.
Noh *lista_primeiro(Lista *L) {
return(L->cabeca);
}
Noh *lista_proximo(Noh *N) {
return(N->prox);
}
2
Andar para tra´s em uma lista simplesmente ligada na˜o e´ ta˜o simples, requer que a lista seja
percorrida desde o in´ıcio verificando se, para cada elemento x, proximo(x) e´ o elemento do qual
procuramos o elemento anterior. A operac¸a˜o leva tempo O(n) em uma lista com n elementos.
Noh *lista_anterior(Lista *L,Noh *N) {
Noh *p;
if (N == L->cabeca) return NULL;
p = lista_primeiro(L);
while(lista_proximo(p)!=NULL) {
if (lista_proximo(p) == N)
return p;
p = lista_proximo(p);
}
return NULL;
}
Obter o u´ltimo elemento tambe´m requer que toda a lista seja percorrida:
Noh *lista_ultimo(Lista *L) {
Noh *p;
p = lista_primeiro(L);
if (p==NULL) return NULL;
while(lista_proximo(p)!=NULL)
p = lista_proximo(p);
return p;
}
Podemos querer 3 tipos de inserc¸o˜es em uma lista: no in´ıcio, no fim ou em uma posic¸a˜o arbitra´ria
no meio. Na lista simplesmente ligada a inserc¸a˜o no in´ıcio e´ trivial: Fazemos o pro´ximo do novo
elemento apontar para o primeiro elemento da lista e fazemos com que o novo elemento seja o
primeiro elemento (cabec¸a) da lista. A inserc¸a˜o em posic¸a˜o arbitra´ria pode ser feita em tempo
constante se tivermos um ponteiro para o elemento que sera´ o elemento anterior ao novo elemento:
void lista_insere_apos(Noh *novo, Noh *anterior) {
novo->prox = anterior->prox;
anterior->prox = novo;
}
A inserc¸a˜o no final da lista exige que encontremos seu u´ltimo elemento, o que leva tempo O(n). A
remoc¸a˜o de um elemento arbitra´rio requer que encontremos o elemento anterior ao removido, o que
leva tempo O(n). A operac¸a˜o de remoc¸a˜o fica assim:
void lista_remove(Lista *L, Noh *N) {
3
Noh *p;
if (N == L->cabeca) {
L->cabeca = N->prox;
} else {
p = lista_anterior(L,N);
if (p!=NULL)
q->prox = p->prox;
}
noh_destroi(N); // desaloca o no’ removido
}
Para desalocar no´s e listas temos as 3 operac¸o˜es abaixo.
void noh_destroi(Noh *N) {
free(N);
}
void noh_destroi_recursivo(Noh *N) {
if (N->prox != NULL) noh_destroi_recursivo(N->prox);
noh_destroi(N);
}
void lista_destroi(Lista *L) {
if (L->cabeca != NULL) noh_destroi_recursivo(L->cabeca);
free(L);
}
A busca de um dado dentro de uma lista ligada leva tempo O(n) (pode ser necessa´rio percorrer
todos os elementos), e a obtenc¸a˜o do x-e´simo elemento leva tempo Θ(x) (e´ necessa´rio percorrer a
lista desde o in´ıcio ate´ o elemento desejado).
2.2 Listas Circulares Duplamente Ligadas
A lista simplesmente ligada e´ ineficiente quando precisamos acessar o final da lista ou obter o
elemento anterior. Estes dois problemas podem ser resolvidos pela lista duplamente ligada circular.
Nesta lista, cada no´ tem um apontador para o pro´ximo elemento e para o elemento anterior. Ale´m
disso, o primeiro elemento aponta para o u´ltimo como seu “anterior”, e o u´ltimo elemento aponta
para o primeiro como seu “pro´ximo”. Sabemos onde a lista comec¸a atrave´s do apontador cabec¸a,
que indica o primeiro elemento. As definic¸o˜es de tipos para a Lista Duplamente Ligada Circular
(LDLC) ficam assim:
typedef struct LdlNoh {
int dado;
struct LdlNoh *ant, *prox;
4
} LdlNoh;
typedef struct {
LdlNoh *cabeca;
} LDLC;
As operac¸o˜es para obter o elemento anterior e o u´ltimo elemento ficam triviais, O(1), bastando
seguir o ponteiro ant e pegar o elemento anterior ao primeiro, respectivamente. A lista duplamente
ligada circular exige, entretanto, alguns cuidados especiais: como o u´ltimo elemento tem o ponteiro
de pro´ximo elemento apontando para o primeiro, e´ preciso tomar cuidado para na˜o percorrer a lista
em c´ırculos. Abaixo mostramos um exemplo de como percorrer uma LDLC, implementando uma
operac¸a˜o de busca:
LdlNoh * ldlc_busca(LDLC *L, int dado) {
LdlNoh *p;
p = ldlc_primeiro(L); // p = L->cabeca;
if (p==NULL) return NULL;
do {
if (p->dado == dado)
return p;
p = ldlc_proximo(p);
} while(p!=ldlc_primeiro(L));
return NULL;
}
Ao realizar inserc¸o˜es e remoc¸o˜es tambe´m e´ preciso tomar cuidado para “costurar” os ponteiros
afetados de forma adequada. A func¸a˜o C abaixo implementa a inserc¸a˜o apo´s um no´ arbitra´rio:
void ldlc_insere_apos(LDLC *L, LdlNoh *novo, LdlNoh *onde) {
novo->ant = onde;
novo->prox = onde->prox;
novo->ant->prox = novo;
novo->prox->ant = novo;
}
Com a LDLC conseguimos realizar todas as operac¸o˜es, exceto busca e acesso aleato´rio, em tempo
constante. O prec¸o pago por esta performance e´ o gasto de memo´ria com o ponteiro adicional para
o elemento anterior. Em um PC comum, se utilizarmos uma LDLC para armazenar apenas um
inteiro em cada no´ de lista, estamos usando 12 bytes por no´, 4 para o dado e 8 para os ponteiros.
Isto significa que 66% da memo´ria sera´ usada apenas para manter a estrutura, e na˜o para os dados
em si.
5
3 Performance
Na tabela abaixo comparamos a performanceassinto´tica de tempo para operac¸o˜es sobre listas em
treˆs estruturas de dados: vetores, lista simplesmente ligada (LSL) e LDLC.
Operac¸a˜o Vetor LSL LDLC
Criac¸a˜o O(1) O(1) O(1)
Destruic¸a˜o O(1) O(N) O(N)
Obter x-e´simo Elemento O(1) O(N) O(N)
Obter Primeiro Elemento O(1) O(1) O(1)
Obter U´ltimo Elemento O(1) O(N) O(1)
Obter Pro´ximo Elemento O(1) O(1) O(1)
Obter Elemento Anterior O(1) O(N) O(1)
Busca O(N) O(N) O(N)
Inserc¸a˜o (In´ıcio) O(N) O(1) O(1)
Inserc¸a˜o (Meio) O(N) O(N) O(1)
Inserc¸a˜o (Fim) O(1) O(N) O(1)
Remoc¸a˜o O(N) O(N) O(1)
Listas ligadas gastam um pouco mais de memo´ria que vetores, mas permitem inserc¸o˜es e remoc¸o˜es
mais eficientes. Em conjuntos dinaˆmicos de dados, em que sa˜o realizadas muitas inserc¸o˜es e
remoc¸o˜es, listas ligadas sa˜o mais vantajosas que vetores. Em algumas arquiteturas de computadores
a fragmentac¸a˜o de uma grande lista como va´rios no´s na˜o cont´ıguos na memo´ria principal pode ser
uma vantagem. A grande vantagem de vetores sobre listas ligadas e´ o acesso aleato´rio (obter o
x-e´simo elemento) em tempo constante.
Para melhorar a performance da busca e´ necessa´rio manter os dados ordenados dentro da estrutura.
Em um vetor ordenado podemos realizar buscas em tempo O(logN). A busca bina´ria exige acesso
aleato´rio, e na˜o e´ diretamente aplica´vel a listas ligadas. Para realizar buscas ra´pidas em estruturas
ligada utilizaremos a´rvores de busca, que sera˜o apresentadas em aulas futuras.
4 Exemplo: Representac¸a˜o de Polinoˆmios
Listas ligadas sa˜o uma forma comum para a representac¸a˜o de polinoˆmios. Podemos usar uma LDLC
para representar um polinoˆmio mantendo cada termo em um no´ da lista. Neste caso, cada no´ tera´
duas informac¸o˜es: o coeficiente e e o expoente. Uma opc¸a˜o que melhora a performance na soma
de termos e facilita a impressa˜o do polinoˆmio e´ manter os no´s em ordem crescente de expoente.
Temos, enta˜o, as estruturas da lista definidas da seguinte forma:
typedef struct Termo {
float coef, expo;
struct Termo *prox, *ant;
} Termo;
6
typedef struct {
Termo *cabeca;
} Poly;
A operac¸a˜o essencial e´ a adic¸a˜o de um termo a um polinoˆmio existente: procuramos um termo
com o mesmo expoente. Se houver, apenas somamos os coeficientes. Sena˜o, adicionamos um novo
termo na posic¸a˜o adequada da lista.
void poly_soma_termo(Poly *P, float coef, float expo) {
Termo *t,*u,*s;
t = poly_primeiro(P);
if (t==NULL || t->expo > expo) {
u = termo_cria(coef,expo);
poly_insere_inicio(P,u);
} else {
while(t->expo < expo) {
t = poly_proximo(t);
if (t == poly_primeiro(P)) break;
}
if (t->expo == expo) {
t->coef = t->coef + coef;
} else {
u = termo_cria(coef,expo);
s = poly_ultimo(P);
if (expo > s->expo) {
poly_insere_fim(P,u);
} else {
t = poly_anterior(t);
poly_insere_apos(P,u,t); // insere u apos t
}
}
}
}
E podemos usar esta operac¸a˜o para realizar a soma de polinoˆmios. Para realizar P1 = P1 + P2
basta percorrer P2 e somar cada um de seus termos a P1:
void poly_soma(Poly *P1, Poly *P2) {
Termo *t;
t = poly_primeiro(P2);
if (t==NULL) return;
do {
poly_soma_termo(P1,t->coef,t->expo);
t = poly_proximo(t);
} while(t!=poly_primeiro(P2));
}
7
A multiplicac¸a˜o de dois polinoˆmios pode ser realizada com a seguinte func¸a˜o:
Poly *poly_mult(Poly *P1, Poly *P2) {
Termo *t1, *t2;
Poly *P3;
P3 = poly_cria();
if (poly_primeiro(P1)==NULL || poly_primeiro(P2)==NULL) return P3;
t1 = poly_primeiro(P1);
do {
t2 = poly_primeiro(P2);
do {
poly_soma_termo(P3,(t1->coef)*(t2->coef),(t1->expo)+(t2->expo));
t2 = poly_proximo(t2);
} while(t2 != poly_primeiro(P2));
t1 = poly_proximo(t1);
} while(t1 != poly_primeiro(P1));
return P3;
}
8

Continue navegando