Ao trabalhar com dados sequenciais, costumamos utilizar listas/arrays para armazenar e aceder a esses dados. No entanto, em alguns casos, pode ser mais rápido utilizar uma lista ligada. Isto torna-se especialmente útil quando o programa precisa de adicionar ou remover elementos no início da lista muitas vezes, pois nesse cenário as listas ligadas podem ser mais eficientes.
A lista ligada é uma das estruturas de dados mais elementares. Ela guarda uma sequência de elementos, permite adicionar novos elementos e aceder aos já existentes – tudo de forma parecida com uma lista/array convencional. Quando trabalhamos com listas ligadas, chamamos cada elemento de Node, que contém dados e um link para o próximo Node. Numa lista ligada simples, cada elemento (Node) tem uma referência para o próximo nó, e o último nó tem uma referência para nada – null. Na literatura, o primeiro elemento de uma lista ligada simples é chamado de cabeça (head), enquanto o último é chamado de cauda (tail). Uma das formas de implementar uma lista ligada pode ser:
class Node:
def __init__(self, data):
self.data = data # Armazena o dado
self.after = None # Armazena uma referência para o próximo elemento
class LinkedList:
def __init__(self):
self.head = None # Inicialmente não há elementos
def append(self, data): # Adiciona um novo elemento à lista
new_node = Node(data) # Cria um nó que contém data
if self.head is None: # Se não existirem elementos na lista
self.head = new_node # Define a cabeça (head) como o novo nó
return
current = self.head # Inicia a travessia da lista a partir da cabeça
while current.after: # Enquanto não chegamos à cauda
current = current.after # Avança para o próximo nó
current.after = new_node # Atualiza a cauda definindo-a como o novo nó
def print(self): # Imprime todos os elementos da lista
current = self.head # Começa a partir da cabeça
while current: # Enquanto não chegamos ao fim
print(current.data, end='\n' if current.after is None else ' --> ')
current = current.after # Avança para o próximo nó
names = LinkedList() # Cria uma lista ligada vazia
names.append('Anna') # Adiciona Anna à lista
names.append('Bob') # Adiciona Bob à lista
names.append('Sona') # Adiciona Sona à lista
names.print() # Anna --> Bob --> Sona
Nesta implementação, temos duas classes: Node e LinkedList. A classe Node possui dois atributos: data e after. O atributo data guarda o valor do nó, enquanto after guarda a referência para o próximo nó na lista. A classe LinkedList tem um atributo: head, que guarda a referência para o primeiro nó da lista. O método append() serve para adicionar um novo nó no final da lista, e o método print() permite imprimir todos os elementos da lista.
Lista Duplamente Ligada
Adicionalmente, também existem listas duplamente ligadas, nas quais cada elemento tem uma referência para o próximo e para o anterior. Isso permite percorrer a lista em ambas as direções.
Análise da Lista Ligada
Alguns pontos importantes a ter em conta quando se trabalha com listas ligadas:
Aceder a um elemento numa lista ligada demora , pois é necessário percorrer a lista desde a cabeça para encontrar o elemento.
Se soubermos qual é o Node após o qual precisamos inserir um novo elemento, a inserção demora , porque basta atualizar as referências dos nós vizinhos.
Se soubermos qual é o Node que precisamos remover, a remoção demora , pois novamente basta atualizar as referências dos nós vizinhos.
Listas ligadas usam mais memória do que arrays devido ao armazenamento de uma referência adicional em cada elemento.
Desafio
Dadas n instruções (queries), tens de as executar. Existem 2 tipos de instruções:
Inserir um número no início da lista.
Imprimir a lista completa.
Entrada
A primeira linha da entrada contém um único inteiro n (1 ≤ n ≤ 1000), o número de instruções.
As n linhas seguintes contêm as instruções – print se for para imprimir a lista completa, e insert x se for para inserir o número x no início da lista ( ≤ x ≤ ).
Saída
O programa deve executar corretamente todas as instruções e imprimir a lista, separando os elementos por espaços.