5. Implementação de Listas Lineares usando Alocação Estática e Acesso Encadeado Uma lista estática encadeada pode ser implementada através de um vetor com dois campos, um que contém a informação e outro que encadeia os elementos da lista. Como exemplo, considere a lista abaixo: inicio da lista 0 1 a1 3 2 3 a2 4 4 a3 -1 5 No caso da lista encadeada é necessário um ponteiro que explicitamente indique o inicio da lista. Após sucessivas inserções e remoções como saber quais nós da lista estão disponíveis? Uma solução é agrupá-los em uma lista de nós livres inicio da lista 0 2 1 a1 3 2 5 3 a2 4 4 a3 -1 5 -1 lista de livres Vantagens: não requer mais a movimentação de elementos na inserção e eliminação (como na lista sequencial); apenas os ponteiros são alterados (lembre que cada campo de informação do nó pode conter elementos muito complexos); Desvantagens: necessário prever espaço máximo da lista (tamanho máximo pré-definido); necessário gerenciar a lista de nós livres; o acesso é não indexado, para acessar a(i) temos que percorrer a(1) ... a(i-1) pois o endereço de a(i) está disponível apenas em a(i-1); gasto maior de espaço de memória; Quando usar: quando for possível fazer uma boa previsão do espaço e quando o ganho dos movimentos sobre a perda do acesso direto a cada elemento for compensador. 5.1 Implementação do TDA lista_não_ordenada Nesta seção implementaremos o TDA lista_não _ordenada, definida na seção 3.1, usando alocação estática e acesso encadeado. a) estrutura da lista: é necessário definir explicitamente um ponteiro para o inicio da lista, um ponteiro para a lista de nós livres além de um vetor com dois campos, um para conter a informação e outro para conter os ponteiros. inicio da lista 0 2 1 a1 3 2 5 3 a2 4 4 a3 -1 5 -1 lista de livres define estrutura NO_ENC como um inteiro que representa o elemento da lista; um inteiro que representa um ponteiro; fim; define estrutura LISTA_ENC como vetor de elementos itens do tipo NO_ENC com MAX posições; um inteiro que indica inicio-da-lista um inteiro que indica lista-de-livres fim; b) Inicializar_Lista - esta função deve garantir que a lista esteja vazia. Neste caso inicioda-lista deve apontar para um endereço inválido e todos os nós da lista estão livres. inicio-da-lista = -1 0 1 lista-de-livres 1 2 2 3 3 4 5 4 5 -1 Inicializar_Lista (endereço da lista) inicio atribua -1 à variável inicio-da-lista; atribua 0 à variável lista-de-livres; para i variando de 0 até MAX-2 atribua ao campo de ponteiro de itens[i] o valo de i+1; atribua ao campo de ponteiro de itens[MAX-1] o valor -1 fim; c) Lista_É_Vazia Uma lista está vazia se inicio-da-lista aponta para uma posição inválida. tipo Lista_É_Vazia(endereço da lista) inicio se inicio-da-lista é inválido então a função retorna True senão a função retorna False; fim; d) Lista_É_Cheia Uma lista está cheia se não há nós livres, isto é, se lista-de-livres aponta para uma posição inválida. tipo Lista_É_Cheia (endereço da lista) inicio se lista-de-livres é inválido então a função retorna True senão a função retorna False; fim; e) Insere_Elem A inserção de um elemento em uma lista estática/encadeada é um pouco mais elaborada que em uma lista estática/sequencial. Neste caso é necessário saber se há nó livre e qual é a posição deste nó. O elemento será inserido na lista nesta posição. Tanto os ponteiro da lista de elementos quanto da lista de livres precisam ser atualizados para refletir as modificações. Considere a seguinte lista: inicio-da-lista = 2 lista-de-livres =0 0 1 1 3 2 23 4 3 5 4 10 -1 5 -1 Suponha que queiramos inserir o elemento com valor 8. Para executar esta inserção precisamos encontrar um nó livre. O nó livre mais fácil de ser obtido é justamente o apontado por lista-de-livres. Sendo assim, o elemento será inserido na posição 0 do vetor. inicio-da-lista = 2 lista-de-livres =0 0 1 1 3 2 23 4 3 5 4 10 -1 5 -1 Agora é necessário inserir o elemento no nó escolhido e atualizar as listas de livre e de dados. Note que a inserção ocorreu no inicio da lista (cabeça da lista), diferente da alocação estática/sequencial em que a inserção ocorria no final da lista. inicio-da-lista = 0 lista-de-livres = 1 0 8 2 1 3 2 23 4 3 5 4 10 -1 5 -1 Algoritmo inteiro Insere_Elem (endereço da lista, elemento a ser inserido) inicio se lista está cheia então a função retorna o valor -1 senão inicio atribi a uma variável pos o valor de lista-de-livres; insere o elemento na posição apontada por lista-de-livres; atualiza a variável lista-de-livres com o conteúdo do campo de ponteiros do nó apontado por lista-de-livres; atribui ao campo de ponteiros do nó pos o valor de inicio-da-lista; atribui à variável inicio-da-lista o endereço do nó pos; fim; fim; f) Remove_Elem Para remover um elemento, a sua posição deve ser localizada na lista. Mas não basta localizar o nó onde o elemento se encontra, é preciso saber também quem o antecede na lista para que a remoção possa ser realizada com sucesso. Podemos destacar três casos: caso 1: lista é vazia Neste caso não é possível remover o elemento desejado; caso 2: o elemento a ser removido está no inicio da lista. Neste caso a remoção é simples. Como exemplo considere a seguinte lista: inicio-da-lista = 0 lista-de-livres = 1 0 8 2 1 3 2 23 4 3 -1 4 10 5 5 3 -1 Suponha que queiramos remover o elemento 8, que está no primeiro nó da lista. Neste caso, basta atualizar as variáveis inicio-da-lista e lista-de-livres. inicio-da-lista = 2 lista-de-livres = 0 0 1 1 3 2 23 4 3 -1 4 10 5 5 3 -1 caso 3: Neste caso queremos remover um elemento que não é o primeiro da lista. Suponha que queiramos remover o elemento 10 da lista dada acima. A lista deve ser percorrida a partir de inicio-da-lista até encontrar o elemento a ser removido ou até chegar ao final da lista. Como o elemento 10 pertence a lista a processo pára indicando que o elemento que se deseja remover está na posição 4 do vetor. Mas para realizar a remoção é preciso saber qual é o nó cujo elemento antecede o elemento 10. Neste caso, é o elemento 23 que está no nó 2. Agora é possível realiza a remoção. inicio-da-lista = 2 lista-de-livres = 4 0 1 1 3 2 23 5 3 4 -1 0 5 3 -1 Algoritmo inteiro Remove_Elem ( endereço da lista, elemento a ser removido) inicio se lista está vazia então a função retorna o valor -1; se elemento a ser removido está no inicio da lista então inicio atribui à variável auxiliar pos o valor de inicio-da-lista; atribui à variável inicio-da-lista o conteúdo do campo de ponteiros do nó pos; atribui ao o conteúdo do campo de ponteiros do nó pos o valor de lista-de-livres; atribui à variável lista-de-livres o endereço de pos; fim; senão inicio atribui à variável anterior o conteúdo de inicio-da-lista; enquanto não chegou ao fim a lista E elemento não está no nó que sucede anterior faça atualiza anterior com o próximo elemento da lista; se chegou ao final da lista então a função retorna o valor -1 senão inicio // anterior aponta para o nó que antecede o nó cujo elemento // queremos remover atribui à pos a posição do nó que sucede o nó anterior; atribui ao conteúdo do campo de ponteiros do nó anterior o conteúdo do campo de ponteiros de pos; atribui ao conteúdo do campo de ponteiros de pos o conteúdo de lista-de-livres; atribui à lista-de-livres o endereço de pos; fim; fim; fim; 5.2 Implementação do TDA lista_ordenada O estudo e os algoritmos para implementar o TDA para listas ordenadas com alocação de memória e acesso encadeado forma deixados com exercícios para o leitor.