¿Qué es una Lista doblemente Enlazada?
Una lista doblemente enlazada es una estructura de datos compuesta por nodos, donde cada nodo contiene no solo una referencia al siguiente nodo en la lista, sino también una referencia al nodo anterior. Esto permite que la lista se recorra en ambas direcciones, lo que facilita ciertas operaciones, como la inserción y eliminación de elementos en cualquier posición.
Las listas doblemente enlazadas son útiles cuando necesitas más flexibilidad al navegar por la lista o realizar operaciones en ambos extremos de la lista. A continuación, vamos a ver cómo están estructuradas las listas doblemente enlazadas y algunos ejemplos en diferentes lenguajes de programación.
Estructura de una Lista Doblemente Enlazada
Cada nodo de una lista doblemente enlazada contiene tres partes:
Dato: El valor o la información que almacena el nodo.
Referencia al Siguiente Nodo: Un enlace que apunta al nodo siguiente.
Referencia al Nodo Anterior: Un enlace que apunta al nodo anterior.
La lista empieza con un nodo inicial (llamado "head") y puede tener un nodo final (llamado "tail"), que hace referencia al último nodo. Si un nodo no tiene nodo anterior (es el primero), su referencia anterior es null y, si no tiene nodo siguiente (es el último), su referencia siguiente también es null.
Ejemplos de Listas Doblemente Enlazadas en Diferentes Lenguajes
JavaScript
JavaScript no tiene una lista doblemente enlazada nativa, pero podemos implementarla con clases.
// Clase que representa un nodo de una lista doblemente enlazada
class Nodo {
constructor(dato) {
this.dato = dato; // Dato almacenado en el nodo
this.siguiente = null; // Puntero al siguiente nodo (inicia como null)
this.anterior = null; // Puntero al nodo anterior (inicia como null)
}
}
// Clase que representa una lista doblemente enlazada
class ListaDoblementeEnlazada {
constructor() {
this.head = null; // Nodo inicial (cabeza) de la lista, inicia como null
this.tail = null; // Nodo final (cola) de la lista, inicia como null
}
// Método para agregar un nuevo nodo al final de la lista
agregar(dato) {
const nuevoNodo = new Nodo(dato); // Crea un nuevo nodo con el dato proporcionado
if (!this.head) {
// Si la lista está vacía, el nuevo nodo será tanto la cabeza como la cola
this.head = this.tail = nuevoNodo;
} else {
// Si la lista no está vacía:
nuevoNodo.anterior = this.tail; // Establece que el nodo anterior al nuevo es el nodo actual en la cola
this.tail.siguiente = nuevoNodo; // Enlaza el nodo actual en la cola con el nuevo nodo
this.tail = nuevoNodo; // Actualiza la cola para que sea el nuevo nodo
}
}
}
// Crea una nueva lista doblemente enlazada
const lista = new ListaDoblementeEnlazada();
// Agrega nodos a la lista
lista.agregar(1); // Lista: 1
lista.agregar(5); // Lista: 1 <-> 5
lista.agregar(3); // Lista: 1 <-> 5 <-> 3
// Muestra el valor del primer nodo (cabeza) de la lista
console.log(lista.head.dato); // Salida: 1
// Muestra el valor del último nodo (cola) de la lista
console.log(lista.tail.dato); // Salida: 3
PHP
En PHP, no hay listas enlazadas nativas, pero podemos crearlas usando clases.
// Clase que representa un nodo en una lista doblemente enlazada
class Nodo {
public $dato; // Almacena el valor del nodo
public $siguiente; // Apunta al siguiente nodo en la lista
public $anterior; // Apunta al nodo anterior en la lista
// Constructor que inicializa el nodo con un dato
public function __construct($dato) {
$this->dato = $dato; // Asigna el valor al nodo
$this->siguiente = null; // Inicialmente no tiene nodo siguiente
$this->anterior = null; // Inicialmente no tiene nodo anterior
}
}
// Clase que representa una lista doblemente enlazada
class ListaDoblementeEnlazada {
public $head; // Nodo inicial (cabeza) de la lista
public $tail; // Nodo final (cola) de la lista
// Constructor que inicializa la lista como vacía
public function __construct() {
$this->head = null; // La cabeza comienza vacía
$this->tail = null; // La cola comienza vacía
}
// Método para agregar un nodo al final de la lista
public function agregar($dato) {
$nuevoNodo = new Nodo($dato); // Crea un nuevo nodo con el dato proporcionado
if (!$this->head) {
// Si la lista está vacía, el nuevo nodo será tanto la cabeza como la cola
$this->head = $this->tail = $nuevoNodo;
} else {
// Si la lista no está vacía:
$nuevoNodo->anterior = $this->tail; // Conecta el nodo actual de la cola al nuevo nodo como su anterior
$this->tail->siguiente = $nuevoNodo; // Conecta el nodo actual de la cola al nuevo nodo como su siguiente
$this->tail = $nuevoNodo; // Actualiza la cola para que sea el nuevo nodo
}
}
}
// Crear una nueva lista doblemente enlazada
$lista = new ListaDoblementeEnlazada();
// Agregar nodos a la lista
$lista->agregar(1); // Lista: 1
$lista->agregar(2); // Lista: 1 <-> 2
$lista->agregar(3); // Lista: 1 <-> 2 <-> 3
// Mostrar el valor del primer nodo (cabeza) de la lista
echo $lista->head->dato; // Muestra: 1
// Mostrar el valor del último nodo (cola) de la lista
echo $lista->tail->dato; // Muestra: 3
C#
En C#, puedes implementar listas doblemente enlazadas usando clases. También hay una estructura de LinkedList<T> en System.Collections.Generic, que es doblemente enlazada.
using System; // Espacio de nombres que proporciona clases básicas como Console
using System.Collections.Generic; // Espacio de nombres para estructuras de datos como LinkedList
// Crea una nueva lista enlazada que almacenará enteros
LinkedList<int> lista = new LinkedList<int>();
// Agrega elementos al final de la lista
lista.AddLast(1); // Agrega el valor 1 como el último nodo: lista = [1]
lista.AddLast(5); // Agrega el valor 2 como el último nodo: lista = [1, 5]
lista.AddLast(3); // Agrega el valor 3 como el último nodo: lista = [1, 5, 3]
// Muestra el valor del primer nodo en la lista
Console.WriteLine(lista.First.Value); // Imprime: 1
// Muestra el valor del último nodo en la lista
Console.WriteLine(lista.Last.Value); // Imprime: 3
JAVA
En Java, puedes implementar listas doblemente enlazadas manualmente o usar la clase LinkedList en java.util, que es una lista doblemente enlazada.
import java.util.LinkedList;
public class EjemploListaDoblementeEnlazada {
public static void main(String[] args) {
LinkedList<Integer> lista = new LinkedList<>();
lista.add(1);
lista.add(2);
lista.add(3);
System.out.println(lista.getFirst()); // Muestra el primer valor: 1
System.out.println(lista.getLast()); // Muestra el último valor: 3
}
}
C
En C, las listas doblemente enlazadas se implementan mediante estructuras y punteros.
#include <stdio.h>
#include <stdlib.h>
struct Nodo {
int dato;
struct Nodo* siguiente;
struct Nodo* anterior;
};
void agregar(struct Nodo** head, struct Nodo** tail, int dato) {
struct Nodo* nuevoNodo = (struct Nodo*)malloc(sizeof(struct Nodo));
nuevoNodo->dato = dato;
nuevoNodo->siguiente = NULL;
nuevoNodo->anterior = *tail;
if (*tail) {
(*tail)->siguiente = nuevoNodo;
} else {
*head = nuevoNodo;
}
*tail = nuevoNodo;
}
int main() {
struct Nodo* head = NULL;
struct Nodo* tail = NULL;
agregar(&head, &tail, 1);
agregar(&head, &tail, 2);
agregar(&head, &tail, 3);
printf("%d\n", head->dato); // Muestra el primer valor: 1
printf("%d\n", tail->dato); // Muestra el último valor: 3
return 0;
}
C++
En C++, puedes implementar listas doblemente enlazadas usando estructuras o usando std::list de la biblioteca estándar, que es doblemente enlazada.
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lista;
lista.push_back(1);
lista.push_back(2);
lista.push_back(3);
cout << lista.front() << endl; // Muestra el primer valor: 1
cout << lista.back() << endl; // Muestra el último valor: 3
return 0;
}
Una lista doblemente enlazada es una estructura de datos útil para situaciones en las que se requiere una navegación eficiente en ambas direcciones (hacia adelante y hacia atrás) o cuando se necesitan frecuentes inserciones y eliminaciones en cualquier posición de la lista. Aunque algunos lenguajes, como Java y C#, ofrecen listas doblemente enlazadas listas para usar, en otros lenguajes se requiere implementarlas manualmente. A continuación, te presento algunos ejemplos de aplicaciones prácticas:
1. Sistema de navegación en un reproductor multimedia
Contexto: En un reproductor multimedia (como Spotify o VLC), el usuario puede navegar entre canciones o videos.
Uso: Cada nodo puede representar una canción o video. Las referencias al nodo anterior y siguiente permiten moverse fácilmente hacia adelante o hacia atrás en la lista.
Beneficio: La lista doblemente enlazada facilita una navegación eficiente sin necesidad de volver a recorrer toda la lista.
2. Historial de navegación en un navegador web
Contexto: Los navegadores web (como Chrome o Firefox) tienen un historial que permite al usuario ir hacia adelante o hacia atrás entre páginas visitadas.
Uso: Cada nodo puede representar una página web con referencias a la página anterior y la siguiente.
Beneficio: Permite un acceso rápido al historial tanto hacia atrás como hacia adelante.
3. Editor de texto con funciones de deshacer/rehacer
Contexto: En aplicaciones como Word o Notepad++, el usuario puede deshacer o rehacer acciones realizadas (insertar, eliminar texto, etc.).
Uso: Cada nodo puede representar un estado o acción en el documento. Los punteros doblemente enlazados facilitan moverse entre los estados pasados y futuros.
Beneficio: Permite implementar la funcionalidad de deshacer y rehacer eficientemente.
4. Gestión de tareas en sistemas operativos
Contexto: Los sistemas operativos suelen usar listas para manejar procesos en ejecución o en espera.
Uso: Cada nodo puede representar un proceso, y las referencias a nodos adyacentes permiten moverse hacia el proceso anterior o siguiente en la cola de tareas.
Beneficio: Facilita la eliminación o inserción de procesos de manera eficiente.
5. Modelado de una deque (doble cola)
Contexto: Una deque (cola doblemente terminada) permite insertar y eliminar elementos tanto por el frente como por el final.
Uso: Una lista doblemente enlazada puede implementar una deque de manera eficiente.
Beneficio: Inserciones y eliminaciones en ambas direcciones son rápidas gracias a las referencias a los nodos anterior y siguiente.
6. Sistemas de cache (LRU - Least Recently Used)
Contexto: En sistemas de caché, como en memoria o bases de datos, se debe eliminar el elemento menos recientemente utilizado cuando la caché alcanza su capacidad.
Uso: Una lista doblemente enlazada se utiliza junto con un mapa hash para rastrear y actualizar elementos de caché rápidamente.
Beneficio: Inserciones y eliminaciones eficientes hacen que el manejo de la caché sea más rápido.
7. Juego tipo "Snake"
Contexto: En juegos como el clásico Snake, la serpiente crece al consumir alimentos y debe moverse en ambas direcciones.
Uso: Cada nodo puede representar un segmento del cuerpo de la serpiente, y las referencias a los nodos adyacentes permiten moverse o eliminar el segmento de la cola.
Beneficio: Facilita la adición y eliminación de segmentos de manera eficiente.
8. Implementación de estructuras de datos complejas
Árboles AVL o Red-Black: Las listas doblemente enlazadas pueden ser útiles como estructura base en la implementación de nodos en estos árboles.
Grafos: Para representar listas de adyacencia en grafos dirigidos y no dirigidos.
9. Sistemas de impresión o procesamiento en lote
Contexto: En aplicaciones como una impresora, se procesa un lote de tareas (documentos) en orden, pero a veces se debe priorizar o revisar documentos anteriores.
Uso: Cada nodo puede representar un trabajo en la cola de impresión, con referencias al trabajo anterior y siguiente.
Beneficio: Permite reorganizar trabajos o acceder rápidamente al trabajo anterior.
10. Estructuras de juegos de cartas o fichas
Contexto: Juegos como Solitario o fichas de dominó donde las cartas o fichas pueden moverse hacia adelante y hacia atrás.
Uso: Cada nodo puede representar una carta o ficha, permitiendo navegar entre ellas y reorganizarlas.
Beneficio: Inserciones, eliminaciones y movimientos se realizan de manera eficiente.
Aprender a trabajar con listas doblemente enlazadas te dará un mejor control sobre el manejo de datos dinámicos, siendo una excelente herramienta para el desarrollo de aplicaciones complejas.