Lista duplamente encadeada

Percorra passo a passo uma lista duplamente encadeada em C++: inserção nas duas pontas, remoção, inversão e a regra dos cinco.

Tudo é simulado neste navegador; nada é enviado a nenhum servidor.

Operações

O construtor é chamado

O construtor é chamado

l1Não construída
∅
  • Atual
  • Ponteiro

Passo 1 / 34

l1
l2
l3

Código

#include <iostream>
#include <utility>
class Nodo { // Um nó guarda o dado e os ponteiros para o anterior e o próximo
private: // Atributos privados: só os métodos acessam
int dato_;
Nodo* anterior_;
Nodo* siguiente_;
public:
Nodo(int dato, Nodo* anterior = nullptr, Nodo* siguiente = nullptr) {
dato_ = dato;
anterior_ = anterior;
siguiente_ = siguiente;
}
int getDato() const { return dato_; }
void setDato(int dato) { dato_ = dato; }
Nodo* getAnterior() const { return anterior_; }
void setAnterior(Nodo* anterior) { anterior_ = anterior; }
Nodo* getSiguiente() const { return siguiente_; }
void setSiguiente(Nodo* siguiente) { siguiente_ = siguiente; }
};
class Lista {
private:
Nodo* cabeza_; // Primeiro nó da lista
Nodo* cola_; // Último nó da lista
int n_; // Número de nós
public:
Lista() { // Construtor padrão: cria uma lista vazia
cabeza_ = nullptr;
cola_ = nullptr;
n_ = 0;
}
~Lista() { // Destrutor: libera todos os nós
while (cabeza_ != nullptr) {
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
}
Lista(const Lista& origin) { // Construtor de cópia: faz uma cópia profunda
cabeza_ = nullptr;
cola_ = nullptr;
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarAlFinal(aux->getDato()); // Inserir no fim mantém a ordem original
aux = aux->getSiguiente();
}
}
Lista& operator=(const Lista& origin) { // Atribuição por cópia: libera os nós antigos e depois copia
if (this != &origin) { // Protege contra a autoatribuição
while (cabeza_ != nullptr) { // Libera os nós que a lista já tinha
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarAlFinal(aux->getDato());
aux = aux->getSiguiente();
}
}
return *this;
}
Lista(Lista&& origin) noexcept { // Construtor de movimentação: toma os nós da origem
cabeza_ = origin.cabeza_;
cola_ = origin.cola_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Deixa a origem vazia
origin.cola_ = nullptr;
origin.n_ = 0;
}
Lista& operator=(Lista&& origin) noexcept { // Atribuição por movimentação: libera os nós antigos e depois toma os novos
if (this != &origin) { // Protege contra a autoatribuição
while (cabeza_ != nullptr) { // Libera os nós que a lista já tinha
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
cabeza_ = origin.cabeza_;
cola_ = origin.cola_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Deixa a origem vazia
origin.cola_ = nullptr;
origin.n_ = 0;
}
return *this;
}
int getTamano() const { return n_; }
Nodo* getCabeza() const { return cabeza_; }
Nodo* getCola() const { return cola_; }
void insertarElemento(int valor) { // Inserir no início: O(1)
Nodo* nodo = new Nodo(valor);
n_ += 1;
nodo->setSiguiente(cabeza_);
if (cabeza_ != nullptr) {
cabeza_->setAnterior(nodo);
} else {
cola_ = nodo;
}
cabeza_ = nodo;
}
void insertarAlFinal(int valor) { // Inserir no fim: O(1) graças a cola_
Nodo* nodo = new Nodo(valor);
n_ += 1;
nodo->setAnterior(cola_);
if (cola_ != nullptr) {
cola_->setSiguiente(nodo);
} else {
cabeza_ = nodo;
}
cola_ = nodo;
}
void imprimirLista() const { // Imprime da cabeça à cauda
Nodo* actual = cabeza_;
while (actual != nullptr) {
std::cout << actual->getDato() << " ";
actual = actual->getSiguiente();
}
std::cout << std::endl;
}
void imprimirListaAtras() const { // Imprime da cauda à cabeça
Nodo* actual = cola_;
while (actual != nullptr) {
std::cout << actual->getDato() << " ";
actual = actual->getAnterior();
}
std::cout << std::endl;
}
void eliminarNodo(int indice) { // Remove o nó da posição indicada
if (indice < 0 || indice >= n_) return; // Índice inválido: nada acontece
Nodo* eliminar = cabeza_;
for (int i = 0; i < indice; i++) {
eliminar = eliminar->getSiguiente();
}
if (eliminar->getAnterior() != nullptr) {
eliminar->getAnterior()->setSiguiente(eliminar->getSiguiente());
} else {
cabeza_ = eliminar->getSiguiente();
}
if (eliminar->getSiguiente() != nullptr) {
eliminar->getSiguiente()->setAnterior(eliminar->getAnterior());
} else {
cola_ = eliminar->getAnterior();
}
delete eliminar;
n_--;
}
void invertirLista() { // Inverte a lista no lugar: cada nó troca anterior_ e siguiente_
Nodo* actual = cabeza_;
Nodo* temporal = nullptr;
while (actual != nullptr) {
temporal = actual->getAnterior();
actual->setAnterior(actual->getSiguiente());
actual->setSiguiente(temporal);
actual = actual->getAnterior(); // anterior_ agora é o antigo siguiente_
}
temporal = cabeza_; // Por fim, troca-se a cabeça e a cauda
cabeza_ = cola_;
cola_ = temporal;
}
};
int main() {
Lista l1;
l1.insertarElemento(10);
l1.insertarElemento(20);
l1.insertarAlFinal(5);
l1.insertarElemento(30);
Lista l2;
l2.insertarElemento(100);
l2 = l1; // Atribuição por cópia (sem swap)
l2.imprimirLista();
l2.imprimirListaAtras();
Lista l3;
l3 = std::move(l1); // Atribuição por movimentação (sem swap)
l3.invertirLista();
l3.imprimirLista();
return 0;
}

Este visualizador de lista duplamente encadeada mostra como cada nó em C++ guarda um ponteiro para o anterior e outro para o próximo, e como a lista mantém cabeza_, cola_ e n_. Três listas, l1, l2 e l3, convivem na tela para que você possa inserir pela cabeça ou pela cauda, remover por índice, inverter a lista e imprimi-la para a frente e para trás. Depois experimente os cinco membros especiais da regra dos cinco: destrutor, construtor de cópia, atribuição por cópia, construtor de movimentação e atribuição por movimentação. Cada operação é reproduzida passo a passo com a linha de C++ destacada, endereços simulados e o nó solto enquanto os dois ponteiros são reconectados. Tudo é simulado no seu navegador.

Como usar

  1. Escolha um exemplo da lista ou comece pelo primeiro, que constrói uma lista e a destrói.
  2. Adicione operações a qualquer uma das três listas: inserir pela cabeça ou pelo fim, remover por índice, inverter ou imprimir nos dois sentidos.
  3. Escolha a lista de origem ao lado de cada linha para cópias e movimentações e digite um valor ou um índice quando for preciso.
  4. Clique em Reproduzir ou avance passo a passo e acompanhe a linha de C++ destacada, os dois ponteiros de cada nó e seus endereços.

Perguntas frequentes

O que é uma lista duplamente encadeada?
É uma lista em que cada nó guarda um ponteiro para o nó anterior e outro para o próximo. Isso permite percorrê-la nos dois sentidos e, com um ponteiro para a cauda, inserir no fim em O(1).
Qual a vantagem de guardar cola_?
Com cola_ insere-se no fim sem percorrer a lista, e é possível imprimir de trás para a frente começando pelo último nó.
Qual a diferença entre copiar e mover uma lista?
Uma cópia cria nós novos com endereços novos e deixa a origem intacta. Uma movimentação toma os nós da origem, mantém seus endereços e deixa a origem vazia, com cabeza_ e cola_ em nullptr.
O código C++ é realmente compilado?
Não. A página simula cada operação no seu navegador e destaca a linha de código correspondente, então nada é enviado a nenhum servidor.

Ferramentas relacionadas