Lista duplamente vinculada - Doubly linked list

Na ciência da computação , uma lista duplamente vinculada é uma estrutura de dados vinculada que consiste em um conjunto de registros vinculados sequencialmente chamados de nós . Cada nó contém três campos : dois campos de link ( referências ao nó anterior e ao próximo na sequência de nós) e um campo de dados. Os links anterior e seguinte dos nós inicial e final , respectivamente, apontam para algum tipo de terminador, normalmente um nó sentinela ou nulo , para facilitar o percurso da lista. Se houver apenas um nó sentinela, a lista será ligada circularmente por meio do nó sentinela. Pode ser conceituado como duas listas unidas formadas a partir dos mesmos itens de dados, mas em ordens sequenciais opostas.

Uma lista duplamente vinculada cujos nós contêm três campos: um valor inteiro, o link para o próximo nó e o link para o nó anterior.
Uma lista duplamente vinculada cujos nós contêm três campos: o link para o nó anterior, um valor inteiro e o link para o próximo nó.

Os dois links de nó permitem a travessia da lista em qualquer direção. Embora adicionar ou remover um nó em uma lista duplamente ligada exija a alteração de mais links do que as mesmas operações em uma lista ligada individualmente, as operações são mais simples e potencialmente mais eficientes (para nós que não sejam os primeiros nós) porque não há necessidade de monitorar o nó anterior durante a travessia ou não há necessidade de percorrer a lista para encontrar o nó anterior, para que seu link possa ser modificado.

Nomenclatura e implementação

O primeiro e o último nós de uma lista duplamente vinculada são imediatamente acessíveis (ou seja, acessíveis sem travessia e geralmente chamados de cabeça e cauda ) e, portanto, permitem percorrer a lista desde o início ou fim da lista, respectivamente: por exemplo, percorrer a lista do início ao fim, ou do fim ao início, em uma busca na lista de um nó com valor de dados específico. Qualquer nó de uma lista duplamente ligada, uma vez obtido, pode ser usado para iniciar um novo percurso da lista, em qualquer direção (para o início ou fim), a partir de um determinado nó.

Os campos de link de um nó de lista duplamente vinculado são freqüentemente chamados de próximo e anterior ou para frente e para trás . As referências armazenadas nos campos de link são geralmente implementadas como ponteiros , mas (como em qualquer estrutura de dados vinculada) também podem ser deslocamentos de endereço ou índices em uma matriz onde os nós residem.

Algoritmos básicos

Considere os seguintes algoritmos básicos escritos em Ada:

Abrir listas duplamente vinculadas

record DoublyLinkedNode {
    next // A reference to the next node
    prev // A reference to the previous node
    data // Data or a reference to data
}
record DoublyLinkedList {
     DoublyLinkedNode firstNode   // points to first node of list
     DoublyLinkedNode lastNode    // points to last node of list
}

Percorrendo a lista

A passagem de uma lista duplamente vinculada pode ser em qualquer direção. Na verdade, a direção do percurso pode mudar muitas vezes, se desejado. Traversal é freqüentemente chamado de iteração , mas essa escolha de terminologia é lamentável, pois iteração tem uma semântica bem definida (por exemplo, em matemática) que não é análoga a traversal .

Para a frente

node  := list.firstNode
while node ≠ null
    <do something with node.data>
    node  := node.next

Para trás

node  := list.lastNode
while node ≠ null
    <do something with node.data>
    node  := node.prev

Inserindo um nó

Essas funções simétricas inserem um nó depois ou antes de um determinado nó:

function insertAfter(List list, Node node, Node newNode)
    newNode.prev  := node      
    if node.next == null
        newNode.next  := null -- (not always necessary)
        list.lastNode  := newNode
    else
        newNode.next  := node.next
        node.next.prev  := newNode
    node.next  := newNode
function insertBefore(List list, Node node, Node newNode)
    newNode.next  := node
    if node.prev == null
        newNode.prev  := null -- (not always necessary)
        list.firstNode  := newNode
    else
        newNode.prev  := node.prev
        node.prev.next  := newNode
    node.prev  := newNode

Também precisamos de uma função para inserir um nó no início de uma lista possivelmente vazia:

function insertBeginning(List list, Node newNode)
    if list.firstNode == null
        list.firstNode  := newNode
        list.lastNode   := newNode
        newNode.prev  := null
        newNode.next  := null
    else
        insertBefore(list, list.firstNode, newNode)

Uma função simétrica é inserida no final:

function insertEnd(List list, Node newNode)
     if list.lastNode == null
         insertBeginning(list, newNode)
     else
         insertAfter(list, list.lastNode, newNode)

Removendo um nó

A remoção de um nó é mais fácil do que a inserção, mas requer tratamento especial se o nó a ser removido for o firstNode ou o lastNode :

function remove(List list, Node node)
    if node.prev == null
        list.firstNode  := node.next
    else
        node.prev.next  := node.next
    if node.next == null
        list.lastNode  := node.prev
    else
        node.next.prev  := node.prev

Uma consequência sutil do procedimento acima é que a exclusão do último nó de uma lista define firstNode e lastNode como nulo e, portanto, trata a remoção do último nó de uma lista de um elemento corretamente. Observe que também não precisamos dos métodos "removeBefore" ou "removeAfter" separados, porque em uma lista duplamente vinculada podemos apenas usar "remove (node.prev)" ou "remove (node.next)" onde eles são válidos. Isso também pressupõe que o nó que está sendo removido tem a garantia de existência. Se o nó não existir nesta lista, será necessário algum tratamento de erros.

Listas circulares duplamente vinculadas

Percorrendo a lista

Supondo que someNode seja algum nó em uma lista não vazia, este código percorre essa lista começando com someNode (qualquer nó servirá):

Para a frente

node  := someNode
do
    do something with node.value
    node  := node.next
while node ≠ someNode

Para trás

node  := someNode
do
    do something with node.value
    node  := node.prev
while node ≠ someNode

Observe o adiamento do teste para o final do loop. Isso é importante para o caso em que a lista contém apenas o nó someNode .

Inserindo um nó

Esta função simples insere um nó em uma lista duplamente ligada circularmente após um determinado elemento:

function insertAfter(Node node, Node newNode)
    newNode.next  := node.next
    newNode.prev  := node
    node.next.prev  := newNode
    node.next       := newNode

Para fazer um "insertBefore", podemos simplesmente "insertAfter (node.prev, newNode)".

Inserir um elemento em uma lista possivelmente vazia requer uma função especial:

function insertEnd(List list, Node node)
    if list.lastNode == null
        node.prev := node
        node.next := node
    else
        insertAfter(list.lastNode, node)
    list.lastNode := node

Para inserir no início, simplesmente "insertAfter (list.lastNode, node)".

Finalmente, a remoção de um nó deve lidar com o caso em que a lista se esvazia:

function remove(List list, Node node);
    if node.next == node
        list.lastNode := null
    else
        node.next.prev := node.prev
        node.prev.next := node.next
        if node == list.lastNode
            list.lastNode := node.prev;
    destroy node

Excluindo um nó

Como em listas duplamente vinculadas, "removeAfter" e "removeBefore" podem ser implementados com "remove (list, node.prev)" e "remove (list, node.next)".

Conceitos avançados

Lista duplamente ligada assimétrica

Uma lista duplamente vinculada assimétrica está em algum lugar entre a lista vinculada simples e a lista duplamente vinculada regular. Ele compartilha alguns recursos com a lista unicamente vinculada (passagem em uma única direção) e outros da lista duplamente vinculada (facilidade de modificação)

É uma lista em que o link anterior de cada nó aponta não para o nó anterior, mas para o link para si mesmo. Embora isso faça pouca diferença entre os nós (apenas aponta para um deslocamento dentro do nó anterior), ele muda o cabeçalho da lista: permite que o primeiro nó modifique o link firstNode facilmente.

Enquanto um nó estiver em uma lista, seu link anterior nunca será nulo.

Inserindo um nó

Para inserir um nó antes de outro, alteramos o link que apontava para o nó antigo, usando o link anterior ; em seguida, defina o próximo link do novo nó para apontar para o nó antigo e altere o link anterior desse nó de acordo.

function insertBefore(Node node, Node newNode)
    if node.prev == null
        error "The node is not in a list"
    newNode.prev  := node.prev
    atAddress(newNode.prev)  := newNode
    newNode.next  := node
    node.prev = addressOf(newNode.next)
function insertAfter(Node node, Node newNode)
    newNode.next  := node.next
    if newNode.next != null
        newNode.next.prev = addressOf(newNode.next)
    node.next  := newNode
    newNode.prev  := addressOf(node.next)

Excluindo um nó

Para remover um nó, simplesmente modificamos o link apontado por prev , independentemente de o nó ser o primeiro da lista.

function remove(Node node)
    atAddress(node.prev)  := node.next
    if node.next != null
        node.next.prev = node.prev
    destroy node

Veja também

Referências