Doubly Linked List

vapordev

Wagner Abrantes

Posted on October 15, 2020

Doubly Linked List

Em uma Doubly Linked List, (que a partir de agora vou chamar de DLL) todos os nodes têm um ponteiro para o node ao qual estão conectados.
Isso significa que cada node está conectado a dois nodes, e podemos avançar para o próximo node ou retroceder até o node anterior.

DLL permitem operações de insert, deleting e, obviamente, de traversing.

E Para fins de manter o exemplo de linhas temos agora o que seria uma representação de estações de trem.
Enquanto que a single linked list representa um monotrilho aqui temos duas ligações.

Quem nunca voltou da paulista mais loco e que o coringa chegou na consolação e se perguntou "o meu é sentido vila madalena ou é sentido vila prudente? E o sentido da vida?"

Listas duplamente ligadas são exatamente iguais a estações de metrô pois através de um Node você pode seguir para o próximo ou para o anterior pois temos ponteiros nas duas direções.

// Estacao struct = Node
type Estacao struct {
    // property
    nome string
    // nextNode
    estacaoSeguinte *Estacao
    // previousNode
    estacaoAnterior *Estacao
}
Enter fullscreen mode Exit fullscreen mode

Assim como na single LinkedList temos o mesmo conceito de head e tail então a struct é exatamente igual.

// Metro = Doubly linked list
type Metro struct {
    // head
    inicioDaLinha *Estacao
    // tail
    finalDaLinha *Estacao
}
Enter fullscreen mode Exit fullscreen mode

Node Between Values

O método EntreDuasEstacoes do Metro retorna a Estacao que tem um nome situado entre os valores anterior e proxima. O método percorre a lista para descobrir se as strings anterior e proxima correspondem em Nodes consecutivos.

func (linhaVerde *Metro) EntreDuasEstacoes(anterior string, proxima string) *Estacao {

    var estacao *Estacao
    var estacaoAtual *Estacao

    for estacao = linhaVerde.inicioDaLinha; estacao != nil; estacao = estacao.estacaoSeguinte {

        if estacao.estacaoAnterior != nil && estacao.estacaoSeguinte != nil {

            if estacao.estacaoAnterior.nome == anterior && estacao.estacaoSeguinte.nome == proxima {
                estacaoAtual = estacao
                break
            }
        }
    }
    return estacaoAtual
}
Enter fullscreen mode Exit fullscreen mode

Primeiro instanciamos o Node fazemos um for para percorrer os itens do head ao tail enquanto estacao for diferente de nil. Em seguida uma comparação com os parâmetros anterior e proxima feitos com o tail e head para reconhecer o node entre eles, sendo que a comparação é feita somente se head e tail forem diferentes de nil.

The AddToHead method

O método AddInicioDaLinha define o head para que possamos seguir construindo mais estacões assim como fizemos com o monotrilho.

func (linhaVerde *Metro) AddInicioDaLinha(novaEstacao string) {

    var estacao = &Estacao{}
    estacao.nome = novaEstacao
    estacao.estacaoSeguinte = nil

    if linhaVerde.inicioDaLinha != nil {

        estacao.estacaoSeguinte = linhaVerde.inicioDaLinha
        linhaVerde.inicioDaLinha.estacaoAnterior = estacao
    }
    linhaVerde.inicioDaLinha = estacao
}
Enter fullscreen mode Exit fullscreen mode

AddAfter method

Aqui fazemos um insert de um node após outro node que é passado como parâmetro presente na lista, e para saber se o Node está presente reutilizamos o método ProcuraEstacao() que havíamos feito para a Single Linkedlist.

func (linhaVerde *Metro) AddEstacaoSeguinte(destino string, novaEstacao string) {

    var estacao = &Estacao{}
    estacao.nome = novaEstacao
    estacao.estacaoSeguinte = nil

    var estacaoAtual *Estacao
    estacaoAtual = linhaVerde.ProcuraEstacao(destino)

    if estacaoAtual != nil {

        estacao.estacaoSeguinte = estacaoAtual.estacaoSeguinte
        estacao.estacaoAnterior = estacaoAtual
        estacaoAtual.estacaoSeguinte = estacao
    }
}
Enter fullscreen mode Exit fullscreen mode

Fazemos a instância do Node atribuímos o nome usado como parâmetro e setamos o node seguinte como nil para que se mantenha o conceito de tail.

Fazemos a busca e recuperamos a localização do Node de referência como estacaoAtual.

Então a estacao seguinte do node atual para a ser a estacao seguinte do node recuperado (mindfuck)
Parece um malabarismo de valores e é na verdade isso mesmo, talvez por isso pareça complicado mas basta trackear onde estão os valores e prestar atenção por onde eles estão passando.

The AddToEnd method

AddEstacaoNoFinalDaLinha cujo o nome é bastante descritivo, faz uma nova instância de um node e reutiliza o método UltimaEstacao() para recuperar o ultimo node e passar o valor do Node instanciado como referencia através de ponteiro.

func (linhaVerde *Metro) AddEstacaoNoFinalDaLinha(novaEstacao string) {

    var estacao = &Estacao{}
    estacao.nome = novaEstacao
    estacao.estacaoSeguinte = nil

    var fimDaLinha *Estacao
    fimDaLinha = linhaVerde.UltimaEstacao()

    if fimDaLinha != nil {

        fimDaLinha.estacaoSeguinte = estacao
        estacao.estacaoAnterior = fimDaLinha
    }
}
Enter fullscreen mode Exit fullscreen mode

Main Func

Aqui vou fazer o mesmo processo que usei com o monotrilho pra popular a DLL, e além desses métodos no arquivo doubly.go no repositório tem mais exemplos de métodos para DLL e os métodos reaproveitados da Single Linked List.

func main() {
    var linhaVerde Metro
    linhaVerde = Metro{}

    estacoes := []string{"Tamanduateí", "Sacomã", "Alto do Ipiranga", "Santos-Imigrantes", "Chácara Klabin",
        "Ana Rosa", "Paraíso", "Brigadeiro", "Trianon-MASP", "Consolação", "Clínicas",
        "Sumaré", "Vila Madalena",
    }

    linhaVerde.AddinicioDaLinha("Vila Prudente")
    for i := range estacoes {
        linhaVerde.AddEstacaoNoFinalDaLinha(estacoes[i])
    }

    linhaVerde.ListarEstacoes()
}
Enter fullscreen mode Exit fullscreen mode

Output:

Vila Prudente
Tamanduateí
Sacomã
Alto do Ipiranga
Santos-Imigrantes
Chácara Klabin
Ana Rosa
Paraíso
Brigadeiro
Trianon-MASP
Consolação
Clínicas
Sumaré
Vila Madalena
Enter fullscreen mode Exit fullscreen mode

Doubly vs Single

Vantagens sobre a single linked list

  • Uma DLL pode ser percorrida de trás para frente e vice versa.

  • A operação de delete na DLL é mais eficiente se o ponteiro para o node excluído for fornecido.

  • Podemos usar insert mais rapidamente em referencia a um item tanto a frente quanto trás.

  • Na SLL (single linked list), para excluir um node, é necessário um ponteiro para o node anterior. Para obter esse node anterior, às vezes a lista precisa ser percorrida. Na DLL, podemos obter o node anterior usando o ponteiro anterior.

Desvantagens sobre a singly linked list

  • Cada node da DLL requer espaço extra para um ponteiro anterior. (ainda assim é possível criar uma DLL sem um segundo ponteiro.)

  • Todas as operações requerem um ponteiro extra para ser mantida. Por exemplo, no insert, precisamos modificar os ponteiros anteriores junto com os próximos ponteiros.

💖 💪 🙅 🚩
vapordev
Wagner Abrantes

Posted on October 15, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Queues
computerscience Queues

October 15, 2020

Doubly Linked List
computerscience Doubly Linked List

October 15, 2020

Stack
computerscience Stack

October 15, 2020