Listas Encadeadas em Python: Simples e Duplamente Encadeadas

No universo da programação, as estruturas de dados são ferramentas cruciais. Elas oferecem métodos para organizar informações, permitindo um uso mais eficiente e otimizado.

Este guia tem como objetivo explorar duas estruturas de dados lineares: a lista encadeada simples e a lista duplamente encadeada.

Uma lista encadeada, diferentemente de um array, não aloca os dados em espaços de memória contíguos. Cada elemento é denominado nó e a conexão entre eles é feita por meio de ponteiros. O primeiro nó da lista é chamado de cabeça.

O tamanho de uma lista encadeada é flexível, adaptando-se à necessidade de alocação de mais ou menos nós, sempre respeitando os limites de memória disponíveis no dispositivo.

Existem duas variações principais de listas encadeadas, que serão exploradas em detalhe a seguir.

#1. Lista Encadeada Simples

Uma lista encadeada simples se caracteriza por possuir um único ponteiro em cada nó, que aponta para o próximo nó na sequência. Cada nó armazena um dado e um ponteiro para o próximo elemento da lista.

O último nó da lista tem um ponteiro nulo (None), indicando o término da cadeia.

Veja uma representação gráfica de uma lista encadeada simples:

Após essa breve introdução, vamos aprender a implementar uma lista encadeada simples utilizando Python.

Implementação da Lista Encadeada Simples

1. O primeiro passo é definir a estrutura do nó.

Como fazer isso?

Em Python, a criação de um nó é simplificada pelo uso de classes. A classe nó terá dois atributos: um para armazenar os dados e outro para o ponteiro para o próximo nó.

Segue o código para criação do nó:

class Node:
  def __init__(self, data):
    self.data = data   
    self.next = None

Com a classe acima, podemos gerar nós de diferentes tipos. Veremos como fazer isso em breve.

Agora que temos o nó, podemos criar uma lista encadeada com múltiplos nós. Para isso, criaremos uma outra classe chamada `LinkedList`.

2. A classe `LinkedList` é inicializada com o atributo head configurado como None. Veja o código abaixo.

class LinkedList:
  def __init__(self):
    self.head = None

3. Já temos as classes `Node` e `LinkedList`. Mas como inserimos um novo nó na lista encadeada? Uma solução é criar um método na classe `LinkedList`. Vamos criar um método para essa finalidade.

Para inserir um nó, devemos seguir os seguintes passos:

  • Verificar se a cabeça (head) da lista está vazia.
  • Se estiver vazia, atribuir o novo nó à cabeça.
  • Se a cabeça não estiver vazia, encontrar o último nó da lista.
  • Atribuir o novo nó ao ponteiro next do último nó.

A seguir, o código que implementa a inserção de um novo nó:

class LinkedList:
  def insert(self, new_node):
    if self.head:
      last_node = self.head
      while last_node.next:
        last_node = last_node.next
      last_node.next = new_node
    else:
      self.head = new_node

Maravilha! Concluímos o método para inserir nós. Mas como podemos acessar os dados de cada nó da lista?

Para percorrer a lista, devemos usar o ponteiro next, da mesma forma como fizemos para encontrar o último nó. Vamos criar um método na classe `LinkedList` para exibir todos os dados dos nós.

4. Abaixo, os passos para exibir os dados:

  • Iniciar uma variável com a cabeça da lista (head).
  • Criar um loop para percorrer a lista até o final.
    • Exibir os dados do nó.
    • Avançar para o próximo nó usando o ponteiro next.

Veja o código:

class LinkedList:
  def display(self):
    temp_node = self.head
    while temp_node:
      print(temp_node.data, end='->')
      temp_node = temp_node.next
    print('Null')

Concluimos a criação da lista com os métodos essenciais. Para testar, vamos instanciar a lista com alguns dados.

Podemos criar nós usando o construtor Node(valor). O código completo com exemplos de uso está abaixo:

class Node:
  def __init__(self, data):
    self.data = data
    self.next = None

class LinkedList:
  def __init__(self):
    self.head = None

  def insert(self, new_node):
    if self.head:
      last_node = self.head
      while last_node.next:
        last_node = last_node.next
      last_node.next = new_node
    else:
      self.head = new_node

  def display(self):
    temp_node = self.head
    while temp_node:
      print(temp_node.data, end='->')
      temp_node = temp_node.next
    print('Null')

if __name__ == '__main__':
  linked_list = LinkedList()
  linked_list.insert(Node(1))
  linked_list.insert(Node(2))
  linked_list.insert(Node(3))
  linked_list.insert(Node(4))
  linked_list.insert(Node(5))
  linked_list.insert(Node(6))
  linked_list.insert(Node(7))
  linked_list.display()

Ao executar o código acima, o resultado será:

1->2->3->4->5->6->7->Null

Com isso, você aprendeu a criar e utilizar uma lista encadeada simples. Agora, o próximo passo é explorar a lista duplamente encadeada, que se baseia nos conceitos que acabamos de aprender.

#2. Lista Duplamente Encadeada

A lista duplamente encadeada, ao contrário da simples, usa dois ponteiros por nó: um que aponta para o nó anterior e outro para o próximo nó. Assim, cada nó armazena um dado e dois ponteiros.

O ponteiro para o nó anterior do primeiro nó da lista e o ponteiro para o próximo nó do último nó são nulos, indicando as extremidades da lista.

Veja abaixo uma representação de uma lista duplamente encadeada:

A implementação de uma lista duplamente encadeada é semelhante à da lista simples. Ao invés de explicar os mesmos passos novamente, o código será apresentado em etapas e você verá como é fácil de entender.

Implementação da Lista Duplamente Encadeada

1. Criação do nó para a lista duplamente encadeada, com ponteiros para o nó anterior, os dados e o ponteiro para o próximo nó.

class Node:
  def __init__(self, data):
    self.prev = None
    self.data = data
    self.next = None

2. Classe da lista duplamente encadeada.

class LinkedList:
  def __init__(self):
    self.head = None

3. Método para inserir um novo nó na lista duplamente encadeada.

class LinkedList:
  def insert(self, new_node):
    if self.head:
      last_node = self.head
      while last_node.next:
        last_node = last_node.next
      new_node.prev = last_node
      last_node.next = new_node

4. Método para exibir os dados da lista duplamente encadeada.

class LinkedList:
  def display(self):
    print("Ordem Normal: ", end='')
    temp_node = self.head
    while temp_node:
      print(temp_node.data, end=' ')
      temp_node = temp_node.next
    print()

    print("Ordem Inversa: ", end='')
    last_node = self.head
    while last_node.next:
      last_node = last_node.next
    temp_node = last_node
    while temp_node:
      print(temp_node.data, end=' ')
      temp_node = temp_node.prev
    print()

O código para a lista duplamente encadeada foi apresentado. Para testar, crie a lista com alguns dados, de forma similar ao que fizemos para a lista simples. Use sua criatividade para experimentar 🙂

Conclusão

Existem inúmeros problemas que podem ser resolvidos com o uso de listas encadeadas. O conhecimento básico da implementação dessas estruturas, como apresentado neste tutorial, é fundamental para que você possa aprofundar seus estudos. Esperamos que este conteúdo tenha sido útil e agradável.

Bons estudos e boa codificação! 🙂