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.
Punteros
Variables que guardan direcciones de memoria. Son la base de toda estructura dinámica.
Nodos
Estructura con datos y puntero al siguiente. El ladrillo fundamental de las listas.
Lista Enlazada
Nodos conectados en cadena. Tamaño dinámico, inserción O(1) al frente.
Con Objetos
Nodos que guardan clases completas. Template lists, std::list, iteradores.
Punteros
*p = 42 · desreferencia (valor)
&x = 0x7FF4 · dirección de x
| Operador | Nombre | Significado |
|---|---|---|
| * (declaración) | asterisco | Declara un puntero |
| * (expresión) | desreferencia | Accede al valor apuntado |
| & | address-of | Obtiene la dirección de una variable |
| -> | flecha | Accede a miembro a través de puntero |
| nullptr | nulo | Puntero 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)
*p cuando p == nullptr). Siempre inicializa punteros a nullptr.
Nodos
struct Node con data y Node*new → obtienes un punterodatanext al nodo siguiente (o nullptr)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;
Lista Enlazada Simple
| Operación | Complejidad | Por qué |
|---|---|---|
| insert front | O(1) | Solo mueve head |
| insert end | O(n) | Hay que llegar al final |
| insert end (con tail) | O(1) | Si guardamos puntero tail |
| delete front | O(1) | Solo mueve head |
| delete by value | O(n) | Búsqueda lineal |
| search | O(n) | Sin índice posible |
| access by index | O(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 */ } };
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.
Lista Doblemente Enlazada
struct DNode { int data; DNode* prev; // ← nuevo DNode* next; DNode(int v) : data(v), prev(nullptr), next(nullptr) {} };
| Operación | Lista Simple | Lista Doble |
|---|---|---|
| delete end | O(n) | O(1) con tail |
| delete given ptr | O(n) | O(1) ← clave |
| recorrer hacia atrás | imposible | O(n) |
| insert before node | O(n) | O(1) |
| memoria por nodo | data + 1 ptr | data + 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; }
Lista Circular
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ó } };
while(curr) es un ciclo infinito. Siempre guarda el nodo inicial y detente cuando vuelvas a él.
Listas con Objetos
data puede ser cualquier struct o clase
#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;
Comparativa
| Característica | Arreglo | Lista Simple | Lista Doble |
|---|---|---|---|
| Tamaño | Fijo (estático) | Dinámico | Dinámico |
| Acceso por índice | O(1) ★ | O(n) | O(n) |
| Insert al frente | O(n) | O(1) ★ | O(1) ★ |
| Insert al final | O(1) | O(n) / O(1)* | O(1) con tail |
| Delete al frente | O(n) | O(1) ★ | O(1) ★ |
| Delete dado puntero | O(n) | O(n) | O(1) ★ |
| Búsqueda | O(n) | O(n) | O(n) |
| Memoria por elemento | Solo dato | dato + 1 ptr | dato + 2 ptrs |
| Cache friendly | ✓ Sí | ✗ No | ✗ No |
| STL | std::vector | std::forward_list | std::list |
- ✓ Necesitas acceso aleatorio O(1)
- ✓ El tamaño es conocido y fijo
- ✓ La cache performance importa
- ✓ Indexas con operaciones matemáticas
- ✓ Insertas/eliminas mucho al frente
- ✓ El tamaño varía drásticamente
- ✓ Implementas Stack o Queue
- ✓ Memoria de puntero extra no es problema
- ✓ Necesitas recorrer en ambas direcciones
- ✓ Eliminas nodos dado su puntero O(1)
- ✓ Implementas Deque o LRU Cache
- ✓ Insertas antes/después de un nodo
Ejercicios
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.
prev, curr, next. Itera redirigiendo cada next.Detectar ciclo (Floyd)
Determina si una lista enlazada contiene un ciclo usando el algoritmo de "tortuga y liebre" sin memoria extra (O(1) espacio).
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.
Intercambio de pares
Intercambia los nodos de a pares: 1→2→3→4 → 2→1→4→3. Debe modificar los nodos, no solo los valores.
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.
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.
Cierre
Lo que aprendieron hoy es la infraestructura de casi todo lo que sigue.
Punteros → Nodos → Listas → Estructuras más complejas