Estructura de Datos · C++11

Punteros·Nodos
Listas·Enlazadas

Desde las direcciones de memoria hasta estructuras dinámicas de tamaño ilimitado. La base de todo lo que viene después.

C++11 o superior
📋 Prereq: arreglos, structs básicos
🧠 new / delete · punteros · nulos
BASE

Punteros

Variables que guardan direcciones de memoria. Son la base de toda estructura dinámica.

* · & · new · delete · nullptr
BLOQUE

Nodos

Estructura con datos y puntero al siguiente. El ladrillo fundamental de las listas.

struct Node { T data; Node* next; }
ESTRUCTURA

Lista Enlazada

Nodos conectados en cadena. Tamaño dinámico, inserción O(1) al frente.

Simple · Doble · Circular · STL
NIVEL +

Con Objetos

Nodos que guardan clases completas. Template lists, std::list, iteradores.

template<T> · std::list<T>
scroll
BLOQUE 01

Punteros

🔑 FUNDAMENTO — Sin punteros, no hay listas dinámicas
memoria RAM — stack frame del programa
0x7FF8
···
0x7FF4
int x = 42 ← x vive aquí
0x7FF0
int* p = 0x7FF4 ← p guarda la dirección de x
0x7FEC
···
p = 0x7FF0  ·  guarda la dirección
*p = 42      ·  desreferencia (valor)
&x = 0x7FF4  ·  dirección de x
Operadores esenciales
OperadorNombreSignificado
* (declaración)asteriscoDeclara un puntero
* (expresión)desreferenciaAccede al valor apuntado
&address-ofObtiene la dirección de una variable
->flechaAccede a miembro a través de puntero
nullptrnuloPuntero que no apunta a nada
// ===== Punteros básicos =====
int x = 42;
int* p = &x;      // p apunta a x

cout << p;          // 0x7FF4  (dirección)
cout << *p;         // 42      (valor)
cout << &x;         // 0x7FF4  (igual)

*p = 99;             // cambia x a 99
cout << x;          // 99 !
// ===== Memoria dinámica =====
int* heap = new int(7); // reserva en heap
cout << *heap;         // 7
delete heap;           // liberar — OBLIGATORIO
heap = nullptr;        // evitar dangling pointer

// Arreglo dinámico
int* arr = new int[5];
arr[0] = 10;
delete[] arr;          // [] para arreglos
arr = nullptr;
// ===== Puntero a struct =====
struct Punto { int x, y; };

Punto pt = {3, 4};
Punto* pp = &pt;

cout << pp->x;          // 3  (flecha = (*pp).x)
cout << (*pp).y;       // 4  (equivalente)
⚠ Errores comunes: Dangling pointer (usar después de delete), memory leak (olvidar delete), nullptr dereference (*p cuando p == nullptr). Siempre inicializa punteros a nullptr.
BLOQUE 02

Nodos

🧱 LADRILLO FUNDAMENTAL — La unidad de construcción de listas
anatomía de un nodo
42
next
data*next
10
──→
20
──→
30
null
head ──→ primer nodo ──→ ··· ──→ nullptr
Proceso para crear y enlazar nodos
1Definir el struct Node con data y Node*
2Reservar memoria con new → obtienes un puntero
3Asignar el valor al campo data
4Conectar next al nodo siguiente (o nullptr)
5Al destruir la lista: delete cada nodo en orden
// ===== Definición de un Nodo =====
struct Node {
    int   data;     // el dato almacenado
    Node* next;     // puntero al siguiente

    // Constructor conveniente
    Node(int val) : data(val), next(nullptr) {}
};
// ===== Crear nodos manualmente =====
Node* n1 = new Node(10);
Node* n2 = new Node(20);
Node* n3 = new Node(30);

// Enlazar: 10 → 20 → 30 → null
n1->next = n2;
n2->next = n3;
// n3->next ya es nullptr (constructor)

Node* head = n1;  // cabeza de la lista

// ===== Recorrer la lista =====
Node* curr = head;
while (curr != nullptr) {
    cout << curr->data << " ";
    curr = curr->next;   // avanzar
}
// Output: 10 20 30
// ===== Liberar la lista =====
Node* curr = head;
while (curr != nullptr) {
    Node* tmp = curr;
    curr = curr->next;
    delete tmp;  // liberar ANTES de perder ref
}
head = nullptr;
BLOQUE 03

Lista Enlazada Simple

➡ SINGLY LINKED LIST — Un puntero next por nodo
visualización interactiva — lista simple
Complejidades
OperaciónComplejidadPor qué
insert frontO(1)Solo mueve head
insert endO(n)Hay que llegar al final
insert end (con tail)O(1)Si guardamos puntero tail
delete frontO(1)Solo mueve head
delete by valueO(n)Búsqueda lineal
searchO(n)Sin índice posible
access by indexO(n)Sin acceso aleatorio
// ===== Clase LinkedList completa =====
class LinkedList {
private:
    Node* head;

public:
    LinkedList() : head(nullptr) {}

    // Insertar al frente — O(1) ★
    void insertFront(int val) {
        Node* nodo = new Node(val);
        nodo->next = head;
        head = nodo;
    }

    // Insertar al final — O(n)
    void insertEnd(int val) {
        Node* nodo = new Node(val);
        if (!head) { head = nodo; return; }
        Node* curr = head;
        while (curr->next) curr = curr->next;
        curr->next = nodo;
    }

    // Eliminar del frente — O(1)
    void deleteFront() {
        if (!head) return;
        Node* tmp = head;
        head = head->next;
        delete tmp;
    }

    // Buscar un valor — O(n)
    bool search(int val) {
        Node* curr = head;
        while (curr) {
            if (curr->data == val) return true;
            curr = curr->next;
        }
        return false;
    }

    ~LinkedList() { /* liberar nodos */ }
};
Casos de uso reales
🔙

Historial de navegación

Cada página visitada se inserta al frente en O(1). El botón "atrás" recorre la lista.

🎵

Lista de reproducción

Agregar canciones al final, mover a cualquier posición sin costo de desplazamiento de memoria.

Movimientos de juego

Guarda movimientos para implementar undo. Cada movimiento se inserta O(1) al frente.

BLOQUE 04

Lista Doblemente Enlazada

↔ DOUBLY LINKED LIST — prev y next por nodo
visualización interactiva — lista doble
Nodo de Lista Doble
struct DNode {
    int    data;
    DNode* prev;  // ← nuevo
    DNode* next;
    DNode(int v)
      : data(v), prev(nullptr), next(nullptr) {}
};
Ventajas sobre lista simple
OperaciónLista SimpleLista Doble
delete endO(n)O(1) con tail
delete given ptrO(n)O(1) ← clave
recorrer hacia atrásimposibleO(n)
insert before nodeO(n)O(1)
memoria por nododata + 1 ptrdata + 2 ptrs
// Insertar al frente — O(1)
void insertFront(int val) {
    DNode* n = new DNode(val);
    n->next = head;
    if (head) head->prev = n; // ← actualizar prev
    head = n;
    if (!tail) tail = n;
}

// Eliminar un nodo dado su puntero — O(1) ★
void deleteNode(DNode* node) {
    if (node->prev) node->prev->next = node->next;
    else           head = node->next;
    if (node->next) node->next->prev = node->prev;
    else           tail = node->prev;
    delete node;
}
BLOQUE 05

Lista Circular

🔄 CIRCULAR — El último nodo apunta de vuelta al primero
lista circular — el ciclo no termina
10 20 30 HEAD
last->next = head  ·  no hay nullptr al final
Casos de uso reales
🎮

Round-Robin Scheduling

Procesos en cola circular. El OS itera indefinidamente asignando CPU time.

🎵

Playlist en loop

Al llegar al final, vuelve automáticamente a la primera canción.

// Definición con puntero tail en lugar de head
class CircularList {
    Node* tail = nullptr; // apunta al último

public:
    // Insertar al frente — O(1)
    void insertFront(int val) {
        Node* n = new Node(val);
        if (!tail) {
            tail = n;
            n->next = n;  // apunta a sí mismo
        } else {
            n->next = tail->next; // nuevo → head actual
            tail->next = n;   // tail → nuevo
        }
    }

    // Recorrer — detectar fin con puntero inicial
    void print() {
        if (!tail) return;
        Node* curr = tail->next; // head
        do {
            cout << curr->data << " ";
            curr = curr->next;
        } while (curr != tail->next); // volvió
    }
};
⚠ Cuidado al recorrer: Como no hay nullptr al final, el loop normal while(curr) es un ciclo infinito. Siempre guarda el nodo inicial y detente cuando vuelvas a él.
BLOQUE 06

Listas con Objetos

🧩 GENÉRICO — Nodos que almacenan cualquier tipo
nodo con objeto complejo
Estudiante
Ana · 7.8
● next
──→
Estudiante
Bob · 6.5
● next
──→
null
El data puede ser cualquier struct o clase
std::list<T> — STL (doble enlazada)
#include <list>

list<int> lst;
lst.push_front(10);    // O(1)
lst.push_back(20);     // O(1)
lst.pop_front();       // O(1)
lst.pop_back();        // O(1)

// Iterador — no hay índices
for (auto& val : lst)
    cout << val;

// std::forward_list = singly linked
// ===== Lista genérica con template =====
template <typename T>
struct Node {
    T       data;
    Node<T>* next;
    Node(T val) : data(val), next(nullptr) {}
};

template <typename T>
class LinkedList {
    Node<T>* head = nullptr;
public:
    void insertFront(T val) {
        Node<T>* n = new Node<T>(val);
        n->next = head;
        head = n;
    }
    // ... demás métodos idénticos
};
// ===== Ejemplo con objetos propios =====
struct Estudiante {
    string nombre;
    double nota;
    Estudiante(string n, double nota)
      : nombre(n), nota(nota) {}
};

LinkedList<Estudiante> curso;
curso.insertFront(Estudiante("Ana", 7.8));
curso.insertFront(Estudiante("Bob", 6.5));

// También funciona con int, string, etc.
LinkedList<int> nums;
LinkedList<string> palabras;
BLOQUE 07

Comparativa

Característica Arreglo Lista Simple Lista Doble
TamañoFijo (estático)DinámicoDinámico
Acceso por índiceO(1) ★O(n)O(n)
Insert al frenteO(n)O(1) ★O(1) ★
Insert al finalO(1)O(n) / O(1)*O(1) con tail
Delete al frenteO(n)O(1) ★O(1) ★
Delete dado punteroO(n)O(n)O(1) ★
BúsquedaO(n)O(n)O(n)
Memoria por elementoSolo datodato + 1 ptrdato + 2 ptrs
Cache friendly✓ Sí✗ No✗ No
STLstd::vectorstd::forward_liststd::list
Usa Arreglo cuando…
  • ✓ Necesitas acceso aleatorio O(1)
  • ✓ El tamaño es conocido y fijo
  • ✓ La cache performance importa
  • ✓ Indexas con operaciones matemáticas
Usa Lista Simple cuando…
  • ✓ Insertas/eliminas mucho al frente
  • ✓ El tamaño varía drásticamente
  • ✓ Implementas Stack o Queue
  • ✓ Memoria de puntero extra no es problema
Usa Lista Doble cuando…
  • ✓ Necesitas recorrer en ambas direcciones
  • ✓ Eliminas nodos dado su puntero O(1)
  • ✓ Implementas Deque o LRU Cache
  • ✓ Insertas antes/después de un nodo
BLOQUE 08

Ejercicios

EJ 01Lista Simple

Invertir una lista enlazada

Dada una lista, invertir el orden de sus nodos in-place sin arreglos auxiliares. La lista 1→2→3→null debe quedar 3→2→1→null.

💡 Hint: usa tres punteros: prev, curr, next. Itera redirigiendo cada next.
EJ 02Lista Simple

Detectar ciclo (Floyd)

Determina si una lista enlazada contiene un ciclo usando el algoritmo de "tortuga y liebre" sin memoria extra (O(1) espacio).

💡 Hint: dos punteros, uno avanza 1 paso, el otro 2. Si se encuentran, hay ciclo.
EJ 03Lista Doble

Implementar LRU Cache

Caché de tamaño k. Al acceder a un elemento, va al frente. Si el caché está lleno, elimina el último. Complejidad O(1) para get y put.

💡 Hint: Lista doble + HashMap. La lista guarda el orden de uso, el map permite O(1) búsqueda.
EJ 04Punteros

Intercambio de pares

Intercambia los nodos de a pares: 1→2→3→42→1→4→3. Debe modificar los nodos, no solo los valores.

💡 Hint: itera de dos en dos. En cada par, redirige los punteros next correctamente.
EJ 05 — TAREALista Doble

Lista doblemente enlazada ordenada

Implementa una lista doble que siempre inserte en orden (ascendente). El método insert(val) ubica el nodo en su posición correcta.

💡 Hint: recorre hasta encontrar el primer nodo mayor. Inserta antes de él usando los punteros prev/next.
EJ 06 — DESAFÍOTemplate

Stack y Queue sobre LinkedList

Implementa Stack y Queue usando tu LinkedList<T> genérica como clase base. Demuestra que ambas son casos especiales de una lista enlazada.

💡 Hint: Stack = insertFront + deleteFront. Queue = insertEnd + deleteFront (o vice-versa con tail pointer).
Práctica adicional — LeetCode
#206 Reverse Linked List
#141 Linked List Cycle
#146 LRU Cache
#21 Merge Two Sorted Lists
#24 Swap Nodes in Pairs
BLOQUE 09

Cierre

Lo que aprendieron hoy es la infraestructura de casi todo lo que sigue.

Punteros → Nodos → Listas → Estructuras más complejas

"¿Cómo se crean estructuras de tamaño dinámico?"
Punteros + new/delete
"¿Cómo insertar en O(1) sin índices?"
Lista enlazada al frente
"¿Cómo eliminar un nodo en O(1)?"
Lista doblemente enlazada
Estructuras que se construyen sobre lo visto hoy
Lista Simple →
Stack · Queue · Undo · Forward Iterator
Lista Doble →
Deque · LRU Cache · Bidirectional Iterator
Lista Circular →
Round-Robin · Buffers circulares · Algoritmo de Josephus
Nodos →
Árboles BST · Heaps enlazados · Grafos con adj-list