Estruturas de Dados Listas Dinâmicas Simplesmente Encadeadas Prof. Ricardo J. G. B. Campello Créditos Parte dos slides a seguir são adaptações, extensões e recodificações em C dos originais: disponíveis em http://ww3.datastructures.net/ cedidos pela Profa. Maria Cristina F. de Oliveira 2 1 Listas Dinâmicas Utiliza alocação dinâmica de memória ao invés de arranjos (vetores) pré-alocados. Lista dispõe de toda memória disponível para o programa durante a execução (heap) Alocação/liberação desses endereços é gerenciada pelo S.O., por meio de comandos da linguagem de programação Linguagem C: malloc e free 3 Listas Simplesmente Encadeadas Uma lista encadeada é uma estrutura de dados linear que consiste de uma seqüência interligada de nós (ou nodos) dinamicamente alocados. próximo No caso de encadeamento simples, cada nodo armazena: elemento (registro, objeto, ...) ponteiro para o próximo nodo. elemento nodo ∅ A B C D 4 2 Listas Simplesmente Encadeadas nodo Nodo: lig elem struct list_node { tipo_elem struct list_node }; typedef struct list_node elem; *lig; nodo; 5 Listas Simplesmente Encadeadas nulo (NULL) Lista: typedef struct { int nelem; nodo *head, *tail; } Lista; Lista L; /* Exemplo de Declaração */ 6 3 Listas Simplesmente Encadeadas Inicialização: nulo (NULL) Lista *Definir(void){ Lista *L; L = malloc(sizeof(Lista)); L->nelem = 0; L->head = NULL; L->tail = NULL; return L; } 7 Inserindo no Início 1. Aloque o novo nodo. 2. Insira o novo elemento. 3. Aponte o novo nodo para head. 4. Aponte head para o novo nodo. PS. símbolo ∅ representa NULL. 8 4 Inserindo no Início nodo *Inserir_frente(tipo_elem x, Lista *L){ nodo *Pa; Pa = malloc(sizeof(nodo)); Pa->elem = x; Pa->lig = L->head; L->head = Pa; if (L->tail == NULL) /* L antes vazia */ L->tail = L->head; L->nelem++; return Pa; /* O(1) */ } 9 Removendo do Início 1. Aponte um ponteiro auxiliar Pa para head 2. Aponte head para o próximo nodo. 3. Desaloque o primeiro nodo usando Pa 10 5 Removendo do Início tipo_elem Remover_frente(Lista *L){ tipo_elem x; nodo *Pa; Pa = L->head; L->head = Pa->lig; if (L->head == NULL) /* L antes com elemento único */ L->tail = NULL; x = Pa->elem; free(Pa); L->nelem--; return x; /* O(1) */ } 11 Inserindo no Final 1. Aloque o novo nodo. 2. Insira o novo elemento. 3. Faça o novo nodo apontar para NULL. 4. Faça o antigo último nodo apontar para o novo. 5. Atualize tail para o novo nodo. Podemos inverter a ordem dos passos 4 e 5 ? 12 6 Inserindo no Final nodo *Inserir_final(tipo_elem x, Lista *L){ nodo *Pa; Pa = malloc(sizeof(nodo)); Pa->elem = x; Pa->lig = NULL; if (L->head == NULL) L->head = Pa; /* L antes vazia */ else (L->tail)->lig = Pa; L->tail = Pa; L->nelem++; return Pa; /* O(1) */ } 13 Removendo do Final Remover no final de uma lista simplesmente encadeada não é eficiente. Não existe algoritmo de complexidade (tempo) constante para acessar o nodo anterior a tail. A obtenção a partir de head é O(n), onde n é o número de elementos na lista (nelem na nossa implementação). 14 7 Removendo do Final tipo_elem Remover_final(Lista *L){ tipo_elem x; nodo *Pa; Pa = L->head; if ((L->head)->lig == NULL) { /* L com 1 elemento */ L->tail = NULL; L->head = NULL; } else { while (Pa->lig != L->tail) Pa = Pa->lig; L->tail = Pa; Pa = Pa->lig; (L->tail)->lig = NULL; } x = Pa->elem; free(Pa); L->nelem--; return x; } /* O(L.nelem) */ Exercícios 1. Implemente uma função Lista_vazia(Lista *L) que retorna True ou False se a lista está vazia ou não, respectivamente 2. Seja o tipo dos elementos da lista, tipo_elem, definido como um registro (struct) com dois campos: chave e info 1.a. Implemente a seguinte função: retorno Localizar(tipo_elem x, Lista *L); que retorna um registro do tipo retorno, também com dois campos: Campo 1 (int): Rank do elemento com chave x.chave na lista L Campo 2 (nodo*): Ponteiro para o nodo de x em L 1.b. Qual o tempo de execução assintótico (BIG-O) de pior caso? 3. Faz sentido aqui Localizar via Busca Binária? Justifique 8 Exercícios 4. Implemente funções para remover elementos quaisquer da lista: tipo_elem Remover_elem(tipo_elem x, Lista *L); tipo_elem Remover_rank(int p, Lista *L); A 1a deve remover da lista o nodo que armazena o elemento com chave x.chave (retornando ao sistema a respectiva memória) e retornar esse elemento A 2a deve fazer o mesmo, mas para o p-ésimo elemento da lista 5. Repita o exercício anterior para as funções Buscar_elem e Buscar_rank, que apenas retornam o elemento, sem remover o respectivo nodo da lista 6. Implemente uma função Modificar que substitua o elemento em uma dada colocação (rank) da lista por outro, retornando-o 17 Exercícios 7. Implemente uma função Modify que substitua um dado elemento x com chave x.chave por outro y também dado. Use Localizar do exercício 1 e Modificar do exercício 5 8. Suponha que se disponha de um ponteiro para um dado nodo da lista (por exemplo obtido via Localizar do exercício 1): 9. Explique porque, mesmo com esse ponteiro em mãos, não é possível remover o respectivo nodo em tempo constante O(1) (sem ter que percorrer a lista a partir de head) É possível modificar a ED dos nodos para tornar isso possível? Justifique Faça um procedimento Esvaziar(Lista *L) que devolva a memória de todos os nodos de uma lista L ao sistema e reinicialize a lista 18 9 Bibliografia A. M. Tenembaum et al., Data Structures Using C, Prentice-Hall, 1990 M. T. Goodrich & R. Tamassia, Data Structures and Algorithms in C++/Java, John Wiley & Sons, 2002/2005 N. Ziviani, Projeto de Algoritmos, Thomson, 2a. Edição, 2004 J. L. Szwarcfiter & L. Markenzon, Estruturas de Dados e seus Algoritmos, LTC, 1994 Schildt, H. "C Completo e Total", 3a. Edição, Pearson, 1997 19 10