Lista Ligada (ou Encadeada)

RESUMO

Uma lista ligada é uma estrutura de dados linear com nós que apontam para o próximo. Na versão efêmera, as operações alteram diretamente a lista, perdendo estados anteriores. Na versão persistente, cada modificação gera uma nova lista, preservando versões antigas. Ideal para controle de versões e programação funcional.

Uma lista ligada é uma estrutura de dados linear que consiste em uma sequência de elementos, onde cada elemento aponta para o próximo.

Existem duas versões principais de listas ligadas: efêmera e persistente. A versão efêmera altera diretamente a lista, perdendo o estado anterior, enquanto a versão persistente preserva todas as versões anteriores após cada modificação.

História e Origem

As listas ligadas foram introduzidas por Allen Newell, Cliff Shaw e Herbert A. Simon em 1956, no desenvolvimento do Logic Theorist, o primeiro programa de inteligência artificial. As estruturas de dados persistentes foram propostas mais tarde, em 1986, por Driscoll, Sarnak, Sleator e Tarjan no artigo “Making Data Structures Persistent”.

Aplicações

As listas ligadas são amplamente utilizadas em várias aplicações:

  • Implementação de outras estruturas de dados: como filas e pilhas.
  • Sistemas de gerenciamento de memória: onde o uso de ponteiros permite alocações dinâmicas eficientes.
  • Controle de versão: especialmente em sua forma persistente.
  • Ambientes de programação funcional: que favorecem a imutabilidade das estruturas de dados.

Funcionamento

Na lista ligada efêmera, as operações alteram diretamente a estrutura, resultando na perda do estado anterior.

  • Inserção: Adiciona um novo nó no início da lista.
  • Remoção: Remove um nó ajustando os ponteiros dos nós adjacentes.
  • Acesso: Navega pelos ponteiros da lista para encontrar o elemento desejado.

Na lista ligada persistente, cada modificação gera uma nova versão da lista, preservando as versões anteriores através do “compartilhamento de estrutura”. Cada operação retorna uma nova instância da lista.

  • Inserção: Cria um novo nó que aponta para o primeiro nó da versão anterior.
  • Remoção: Cria uma nova lista excluindo o nó desejado, reutilizando os nós não afetados.
  • Acesso: Navega pelos ponteiros, semelhante à lista ligada efêmera.

Complexidade

  • Inserção: O(1) – Adiciona o novo nó no início.
  • Remoção: O(n) – Pode exigir a busca pelo nó a ser removido.
  • Acesso: O(n) – Deve navegar pelos nós.

Implementação

Lista Ligada Efêmera em Python

Python
class Node:
    def __init__(self, value, next_node=None):
        self.value = value
        self.next = next_node

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

    def insert(self, value):
        new_node = Node(value, self.head)
        self.head = new_node

    def delete(self, value):
        current = self.head
        prev = None
        while current:
            if current.value == value:
                if prev:
                    prev.next = current.next
                else:
                    self.head = current.next
                return
            prev = current
            current = current.next

    def print_list(self):
        current = self.head
        while current:
            print(current.value, end=" -> ")
            current = current.next
        print("None")

Lista Ligada Persistente em Python

Python
class Node:
    def __init__(self, value, next_node=None):
        self.value = value
        self.next = next_node

class PersistentLinkedList:
    def __init__(self, head=None):
        self.head = head

    def insert(self, value):
        new_head = Node(value, self.head)
        return PersistentLinkedList(new_head)

    def delete(self, value):
        current = self.head
        new_head = None
        tail = None
        while current:
            if current.value != value:
                new_node = Node(current.value)
                if not new_head:
                    new_head = new_node
                    tail = new_node
                else:
                    tail.next = new_node
                    tail = new_node
            current = current.next
        return PersistentLinkedList(new_head)

    def print_list(self):
        current = self.head
        while current:
            print(current.value, end=" -> ")
            current = current.next
        print("None")

Exemplos

Exemplo Básico com Lista Ligada Efêmera

Python
ll = LinkedList()
ll.insert(1)
ll.insert(2)
ll.insert(3)
ll.print_list()  # 3 -> 2 -> 1 -> None
ll.delete(2)
ll.print_list()  # 3 -> 1 -> None

Exemplo Básico com Lista Ligada Persistente

Python
pll = PersistentLinkedList()
pll = pll.insert(1)
pll = pll.insert(2)
pll = pll.insert(3)
pll.print_list()  # 3 -> 2 -> 1 -> None
pll2 = pll.delete(2)
pll2.print_list()  # 3 -> 1 -> None
pll.print_list()   # 3 -> 2 -> 1 -> None (original list remains unchanged)

Comparações

Vantagens da Lista Ligada Efêmera:
A principal vantagem de uma lista ligada efêmera é a sua simplicidade. Ela é mais fácil de implementar e usar, o que a torna uma escolha adequada para casos onde a complexidade mínima é desejada. Além disso, a eficiência de memória é uma grande vantagem, pois a lista efêmera utiliza a memória de forma mais eficiente ao não preservar estados anteriores, economizando espaço.

Desvantagens da Lista Ligada Efêmera:
A maior desvantagem da lista ligada efêmera é a perda de estado. Ela não preserva versões anteriores da lista, o que pode ser uma limitação em contextos onde é necessário acessar ou rastrear estados passados da estrutura de dados. Isso pode complicar operações que envolvem backtracking ou desfazer ações.

Vantagens da Lista Ligada Persistente:
Por outro lado, a lista ligada persistente oferece a grande vantagem da imutabilidade. Ela preserva estados anteriores, o que é extremamente útil para controle de versão, depuração e operações que exigem acesso a versões passadas dos dados. Essa característica facilita a depuração, pois permite rastrear e inspecionar estados anteriores da estrutura de dados.

Desvantagens da Lista Ligada Persistente:
No entanto, a lista ligada persistente pode consumir mais memória devido à necessidade de preservar múltiplas versões da lista. Além disso, sua implementação e gerenciamento são mais complexos, exigindo um entendimento mais profundo de estruturas de dados persistentes e técnicas de manipulação de memória.

Melhores Práticas

  • Uso em contextos de acesso sequencial: Onde a navegação é linear.
  • Evitar uso excessivo em contextos de buscas frequentes: Considerar estruturas alternativas como árvores (persistentes e efêmeras).
  • Gerenciamento de versões: Manter um controle eficiente das versões para evitar uso excessivo de memória.

Referências

  • Driscoll, J. R., Sarnak, N., Sleator, D. D., & Tarjan, R. E. (1986). Making data structures persistent. Proceedings of the eighteenth annual ACM symposium on Theory of computing – STOC ’86.
  • Newell, A., Shaw, J. C., & Simon, H. A. (1956). Report on a General Problem-Solving Program.

Conclusão

As listas ligadas são estruturas de dados versáteis, com variantes efêmeras e persistentes oferecendo diferentes benefícios. A escolha entre elas depende dos requisitos específicos da aplicação, como necessidade de preservação de estados anteriores ou eficiência de memória. Para estudos futuros, explorar estruturas de dados persistentes mais complexas pode proporcionar benefícios adicionais em termos de desempenho e funcionalidade.

Gostaria de mais informações?

Se você tem interesse neste assunto ou gostaria de mais informações sobre como a EximiaCo pode ajudar a sua empresa a utilizar a tecnologia para gerar mais resultados, entre em contato conosco.

0
Gostaríamos de ouvir sua opinião!x

Tenho interesse em conversar

Se você está querendo gerar mais resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

Área de colaboradores

Esse ambiente é de acesso restrito à equipe de colaboradores da EximiaCo.

Trabalha na EximiaCo? Então conecte-se com sua conta: