Algoritmos y estructuras de datos en C++: de la teoría a proyectos reales
Dominar los algoritmos y las estructuras de datos es la base que distingue a un programador principiante de un ingeniero profesional. En C++, uno de los lenguajes más potentes y eficientes, comprender estos conceptos permite escribir código no solo funcional, sino rápido, escalable y fiable. En este artículo, analizaremos las estructuras de datos y algoritmos clave en la práctica, con ejemplos listos que podrá usar de inmediato en sus proyectos.
No profundizaremos en teoría abstracta: cada sección contiene código C++ concreto y explicaciones de cómo y por qué funciona cada construcción. El material está dirigido a desarrolladores que ya conocen la sintaxis de C++, pero desean sistematizar sus conocimientos sobre algoritmos.
1. Estructuras de datos básicas: arreglos, listas y estructuras dinámicas
Empecemos por lo esencial. En C++, la biblioteca estándar (STL) proporciona potentes contenedores, pero comprender su funcionamiento interno es fundamental para la optimización.
Arreglo dinámico (std::vector)
std::vector es el contenedor más utilizado. Representa un arreglo dinámico que se expande automáticamente al añadir elementos. Sin embargo, es importante recordar la complejidad amortizada: la inserción al final es O(1) en promedio, pero la inserción en medio es O(n).
#include <iostream>#include <vector>
int main() { std::vector<int> numbers = {10, 20, 30, 40}; // Adición al final numbers.push_back(50); // Inserción al inicio (operación costosa) numbers.insert(numbers.begin(), 5); // Eliminación del último elemento numbers.pop_back(); for (int num : numbers) { std::cout << num << " "; } // Salida: 5 10 20 30 40 return 0;}Lista enlazada (std::list)
A diferencia del vector, std::list se implementa como una lista doblemente enlazada. Es ideal cuando se insertan o eliminan elementos con frecuencia en medio del contenedor: estas operaciones se realizan en O(1). El costo es la falta de acceso directo por índice.
#include <list>
std::list<std::string> tasks = {"Escribir código", "Realizar pruebas"};tasks.push_front("Planificar arquitectura");tasks.push_back("Documentar");
// Inserción en medioauto it = tasks.begin();std::advance(it, 2); // Movemos el iterador al 2º elementotasks.insert(it, "Refactorización");Pila y cola (std::stack, std::queue)
Son adaptadores de contenedores que limitan la funcionalidad del contenedor base. La pila funciona bajo el principio LIFO (Last In, First Out), la cola bajo FIFO (First In, First Out).
#include <stack>#include <queue>
// Pilastd::stack<int> st;st.push(1);st.push(2);st.pop(); // Elimina 2
// Colastd::queue<int> q;q.push(10);q.push(20);q.pop(); // Elimina 10 (el primero)2. Algoritmos de ordenamiento y búsqueda: desde burbuja hasta búsqueda binaria
Ordenar es un problema clásico, pero en la práctica es importante saber cuándo usar algoritmos integrados y cuándo escribir los propios.
Ordenamiento rápido (QuickSort) — implementación en C++
STL utiliza ordenamiento introspectivo (un híbrido de QuickSort, HeapSort e InsertionSort). Pero para entenderlo, escribamos un QuickSort clásico:
#include <iostream>#include <vector>
int partition(std::vector<int>& arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; std::swap(arr[i], arr[j]); } } std::swap(arr[i + 1], arr[high]); return i + 1;}
void quickSort(std::vector<int>& arr, int low, int high) { if (low < hi