Lista doblemente enlazada

Recorre paso a paso una lista doblemente enlazada de C++: inserción por ambos extremos, borrado, inversión y la regla del cinco.

Todo se simula en este navegador; no se envía nada a ningún servidor.

Operaciones

Se llama al constructor

Se llama al constructor

l1Sin construir
∅
  • Actual
  • Puntero

Paso 1 / 34

l1
l2
l3

Código

#include <iostream>
#include <utility>
class Nodo { // Un nodo guarda el dato y los punteros al anterior y al siguiente
private: // Atributos privados: solo se tocan con los métodos
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_; // Primer nodo de la lista
Nodo* cola_; // Último nodo de la lista
int n_; // Número de nodos
public:
Lista() { // Constructor por defecto: crea una lista vacía
cabeza_ = nullptr;
cola_ = nullptr;
n_ = 0;
}
~Lista() { // Destructor: libera todos los nodos
while (cabeza_ != nullptr) {
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
}
Lista(const Lista& origin) { // Constructor de copia: hace una copia profunda
cabeza_ = nullptr;
cola_ = nullptr;
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarAlFinal(aux->getDato()); // Insertar por la cola conserva el orden original
aux = aux->getSiguiente();
}
}
Lista& operator=(const Lista& origin) { // Asignación por copia: libera los nodos antiguos y luego copia
if (this != &origin) { // Protege contra la autoasignación
while (cabeza_ != nullptr) { // Libera los nodos que ya tenía la lista
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 { // Constructor de movimiento: roba los nodos del origen
cabeza_ = origin.cabeza_;
cola_ = origin.cola_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Deja el origen vacío
origin.cola_ = nullptr;
origin.n_ = 0;
}
Lista& operator=(Lista&& origin) noexcept { // Asignación por movimiento: libera los nodos antiguos y roba los nuevos
if (this != &origin) { // Protege contra la autoasignación
while (cabeza_ != nullptr) { // Libera los nodos que ya tenía la lista
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cola_ = nullptr;
cabeza_ = origin.cabeza_;
cola_ = origin.cola_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Deja el origen vacío
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) { // Inserta al inicio: 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) { // Inserta al final: O(1) gracias 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 de la cabeza a la cola
Nodo* actual = cabeza_;
while (actual != nullptr) {
std::cout << actual->getDato() << " ";
actual = actual->getSiguiente();
}
std::cout << std::endl;
}
void imprimirListaAtras() const { // Imprime de la cola a la cabeza
Nodo* actual = cola_;
while (actual != nullptr) {
std::cout << actual->getDato() << " ";
actual = actual->getAnterior();
}
std::cout << std::endl;
}
void eliminarNodo(int indice) { // Elimina el nodo de la posición indicada
if (indice < 0 || indice >= n_) return; // Índice no válido: no hace nada
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() { // Invierte la lista in situ: cada nodo intercambia anterior_ y siguiente_
Nodo* actual = cabeza_;
Nodo* temporal = nullptr;
while (actual != nullptr) {
temporal = actual->getAnterior();
actual->setAnterior(actual->getSiguiente());
actual->setSiguiente(temporal);
actual = actual->getAnterior(); // El anterior_ ya es el antiguo siguiente_
}
temporal = cabeza_; // Por último se intercambian la cabeza y la cola
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; // Asignación por copia (sin swap)
l2.imprimirLista();
l2.imprimirListaAtras();
Lista l3;
l3 = std::move(l1); // Asignación por movimiento (sin swap)
l3.invertirLista();
l3.imprimirLista();
return 0;
}

Este visualizador de la lista doblemente enlazada muestra cómo cada nodo de C++ guarda un puntero al anterior y otro al siguiente, y cómo la lista mantiene cabeza_, cola_ y n_. Tres listas, l1, l2 y l3, conviven en el lienzo para que puedas insertar por la cabeza o por la cola, borrar por índice, invertir la lista e imprimirla hacia delante y hacia atrás. Después prueba los cinco miembros especiales de la regla del cinco: destructor, constructor de copia, asignación por copia, constructor de movimiento y asignación por movimiento. Cada operación se reproduce paso a paso con la línea de C++ resaltada, direcciones simuladas y el nodo suelto al reconectar los punteros. Todo se simula en tu navegador.

Cómo se usa

  1. Elige un ejemplo de la lista o empieza por el primero, que construye una lista y la destruye.
  2. Añade operaciones a cualquiera de las tres listas: insertar por la cabeza o por el final, borrar por índice, invertir o imprimir en ambos sentidos.
  3. Elige la lista origen junto a cada fila para las copias y los movimientos, y escribe un valor o un índice cuando haga falta.
  4. Pulsa Reproducir o avanza paso a paso y sigue la línea resaltada de C++, los dos punteros de cada nodo y sus direcciones.

Preguntas frecuentes

¿Qué es una lista doblemente enlazada?
Es una lista en la que cada nodo guarda un puntero al nodo anterior y otro al siguiente. Eso permite recorrerla en ambos sentidos y, con un puntero a la cola, insertar por el final en O(1).
¿Qué ventaja tiene guardar cola_?
Con cola_ se inserta por el final sin recorrer la lista, y se puede imprimir de atrás hacia delante empezando por el último nodo.
¿Qué diferencia hay entre copiar y mover una lista?
Una copia crea nodos nuevos con direcciones nuevas y deja el origen intacto. Un movimiento toma los nodos del origen, conserva sus direcciones y deja el origen vacío, con cabeza_ y cola_ en nullptr.
¿Se compila el código C++ de verdad?
No. La página simula cada operación en tu navegador y resalta la línea correspondiente del código, así que no se envía nada a ningún servidor.

Herramientas relacionadas