La Biblioteca de Plantillas Estándar (STL) de C++ ofrece un conjunto robusto de algoritmos genéricos que operan sobre rangos de elementos definidos por iteradores. Estos algoritmos son herramientas fundamentales para manipular colecciones de datos de manera eficiente y expresiva. Se categorizan principalmente en algoritmos que no modifican la secuencia, aquellos que sí lo hacen, y otros para ordenación, operaciones numéricas, y más.
1. Algoritmos de Secuencia No Modificadores
Estos algoritmos examinan los elementos en un rango sin alterar su contenido.
1.1. Búsqueda: find, find_if, find_end
std::find(inicio, fin, valor): Busca la primera aparición devalor. Retorna un iterador al elemento encontrado ofinsi no se encuentra.std::find_if(inicio, fin, predicado): Busca el primer elemento que satisface elpredicado(una función o lambda).std::find_end(inicio, fin, sub_inicio, sub_fin): Encuentra la última ocurrencia de una sub-secuencia dentro de un rango.
#include <iostream>
#include <vector>
#include <algorithm> // Para find, find_if, find_end
int main() {
std::vector<int> numeros = {10, 25, 40, 55, 70, 85};
// Buscar el valor 40
auto it_cuarenta = std::find(numeros.begin(), numeros.end(), 40);
if (it_cuarenta != numeros.end()) {
std::cout << "Valor encontrado: " << *it_cuarenta << std::endl; // Salida: 40
}
// Buscar el primer número mayor que 60
auto it_mayor_sesenta = std::find_if(numeros.begin(), numeros.end(), [](int n) {
return n > 60;
});
if (it_mayor_sesenta != numeros.end()) {
std::cout << "Primer valor > 60: " << *it_mayor_sesenta << std::endl; // Salida: 70
}
// Buscar una sub-secuencia {55, 70}
std::vector<int> sub_sec = {55, 70};
auto it_sub = std::find_end(numeros.begin(), numeros.end(), sub_sec.begin(), sub_sec.end());
if (it_sub != numeros.end()) {
std::cout << "Sub-secuencia comienza en el índice: " << (it_sub - numeros.begin()) << std::endl; // Salida: 3
}
return 0;
}
1.2. Conteo: count, count_if
std::count(inicio, fin, valor): Cuenta las veces quevaloraparece en el rango.std::count_if(inicio, fin, predicado): Cuenta los elementos que satisfacen elpredicado.
#include <iostream>
#include <vector>
#include <algorithm> // Para count, count_if
int main() {
std::vector<int> datos = {1, 2, 5, 2, 8, 2, 5};
// Contar cuántas veces aparece el número 2
int conteo_dos = std::count(datos.begin(), datos.end(), 2);
std::cout << "Cantidad de '2': " << conteo_dos << std::endl; // Salida: 3
// Contar cuántos números son mayores que 4
int conteo_mayor_cuatro = std::count_if(datos.begin(), datos.end(), [](int x) {
return x > 4;
});
std::cout << "Cantidad de números > 4: " << conteo_mayor_cuatro << std::endl; // Salida: 3 (5, 8, 5)
return 0;
}
1.3. for_each
Aplica una función o un objeto función a cada elemento en un rango.
#include <iostream>
#include <vector>
#include <algorithm> // Para for_each
int main() {
std::vector<std::string> palabras = {"hola", "mundo", "c++"};
std::cout << "Elementos: ";
std::for_each(palabras.begin(), palabras.end(), [](const std::string& s) {
std::cout << s << " "; // Imprime cada palabra
});
std::cout << std::endl; // Salida: Elementos: hola mundo c++
return 0;
}
1.4. Comparación de Rangos: equal y mismatch
std::equal(b1, e1, b2): Comprueba si dos rangos son idénticos elemento a elemento.std::mismatch(b1, e1, b2): Retorna unstd::pairde iteradores al primer par de elementos desiguales.
#include <iostream>
#include <vector>
#include <algorithm> // Para equal, mismatch
int main() {
std::vector<int> seq1 = {10, 20, 30, 40};
std::vector<int> seq2 = {10, 20, 30, 50};
std::vector<int> seq3 = {10, 20, 30};
// ¿seq1 y seq2 son iguales en sus primeros 3 elementos?
bool son_iguales_parcial = std::equal(seq1.begin(), seq1.begin() + 3, seq2.begin());
std::cout << "seq1 y seq2 son parcialmente iguales? " << std::boolalpha << son_iguales_parcial << std::endl; // Salida: true
// ¿seq1 y seq3 son completamente iguales?
bool son_iguales_completos = std::equal(seq1.begin(), seq1.begin() + seq3.size(), seq3.begin());
std::cout << "seq1 y seq3 son completamente iguales? " << std::boolalpha << son_iguales_completos << std::endl; // Salida: true
// Encontrar la primera diferencia entre seq1 y seq2
auto diferen_par = std::mismatch(seq1.begin(), seq1.end(), seq2.begin());
if (diferen_par.first != seq1.end()) {
std::cout << "Primera diferencia: " << *diferen_par.first << " vs " << *diferen_par.second << std::endl; // Salida: 40 vs 50
}
return 0;
}
1.5. Predicados de Rango: all_of, any_of, none_of (C++11)
Verifican si todos, alguno o ninguno de los elementos en un rango cumplen una condición.
#include <iostream>
#include <vector>
#include <algorithm> // Para all_of, any_of, none_of
int main() {
std::vector<int> valores = {10, 20, 30, 40};
// ¿Todos los valores son múltiplos de 10?
bool todos_mult_diez = std::all_of(valores.begin(), valores.end(), [](int x) {
return x % 10 == 0;
});
std::cout << "Todos múltiplos de 10? " << std::boolalpha << todos_mult_diez << std::endl; // Salida: true
// ¿Alguno de los valores es impar?
bool alguno_impar = std::any_of(valores.begin(), valores.end(), [](int x) {
return x % 2 != 0;
});
std::cout << "Alguno impar? " << std::boolalpha << alguno_impar << std::endl; // Salida: false
// ¿Ninguno de los valores es menor que 5?
bool ninguno_menor_cinco = std::none_of(valores.begin(), valores.end(), [](int x) {
return x < 5;
});
std::cout << "Ninguno < 5? " << std::boolalpha << ninguno_menor_cinco << std::endl; // Salida: true
return 0;
}
2. Algoritmos de Secuencia Modificadores
Estos algoritmos alteran el contenido de los elementos o su orden dentro del rango.
2.1. Copia: copy y copy_if
std::copy(inicio, fin, destino): Copia elementos de un rango a otro. El rango de destino debe tener espacio suficiente.std::copy_if(inicio, fin, destino, predicado): Copia solo los elementos que satisfacen elpredicado.
#include <iostream>
#include <vector>
#include <algorithm> // Para copy, copy_if
#include <iterator> // Para back_inserter
int main() {
std::vector<int> fuente = {100, 200, 300, 400, 500};
std::vector<int> destino_todos(fuente.size()); // El destino debe tener tamaño adecuado
// Copiar todos los elementos
std::copy(fuente.begin(), fuente.end(), destino_todos.begin());
std::cout << "Destino todos: ";
for (int x : destino_todos) std::cout << x << " "; // Salida: 100 200 300 400 500
// Copiar solo elementos mayores a 250 a un nuevo vector
std::vector<int> destino_filtrado;
std::copy_if(fuente.begin(), fuente.end(), std::back_inserter(destino_filtrado), [](int val) {
return val > 250;
});
std::cout << "\nDestino filtrado (>250): ";
for (int x : destino_filtrado) std::cout << x << " "; // Salida: 300 400 500
std::cout << std::endl;
return 0;
}
2.2. Transformación: transform
Aplica una función a cada elemento en un rango y guarda el resultado en otro rango.
#include <iostream>
#include <vector>
#include <algorithm> // Para transform
#include <numeric> // Para std::plus
int main() {
std::vector<int> valores_originales = {1, 2, 3, 4};
std::vector<int> valores_duplicados(valores_originales.size());
// Multiplicar cada elemento por 2
std::transform(valores_originales.begin(), valores_originales.end(), valores_duplicados.begin(), [](int x) {
return x * 2;
});
std::cout << "Valores duplicados: ";
for (int x : valores_duplicados) std::cout << x << " "; // Salida: 2 4 6 8
// Sumar elementos de dos vectores
std::vector<int> v_a = {10, 20, 30};
std::vector<int> v_b = {1, 2, 3};
std::vector<int> v_suma(v_a.size());
std::transform(v_a.begin(), v_a.end(), v_b.begin(), v_suma.begin(), std::plus<int>());
std::cout << "\nSuma de vectores: ";
for (int x : v_suma) std::cout << x << " "; // Salida: 11 22 33
std::cout << std::endl;
return 0;
}
2.3. Reemplazo: replace, replace_if, replace_copy
std::replace(inicio, fin, valor_antiguo, valor_nuevo): Reemplaza todas las ocurrencias devalor_antiguoporvalor_nuevo.std::replace_if(inicio, fin, predicado, valor_nuevo): Reemplaza elementos que cumplen elpredicado.std::replace_copy(inicio, fin, destino, valor_antiguo, valor_nuevo): Copia el rango adestinoreemplazando elementos, sin modificar el original.
#include <iostream>
#include <vector>
#include <algorithm> // Para replace, replace_if, replace_copy
#include <iterator> // Para back_inserter
int main() {
std::vector<char> letras = {'a', 'b', 'c', 'b', 'd', 'e'};
// Reemplazar todas las 'b' por 'X'
std::replace(letras.begin(), letras.end(), 'b', 'X');
std::cout << "Después de replace('b', 'X'): ";
for (char c : letras) std::cout << c << " "; // Salida: a X c X d e
// Reemplazar vocales (minúsculas) por '*'
std::replace_if(letras.begin(), letras.end(), [](char ch) {
return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u';
}, '*');
std::cout << "\nDespués de replace_if(vocal, '*'): ";
for (char c : letras) std::cout << c << " "; // Salida: * X c X d *
// Copiar y reemplazar 'X' por 'Y' sin modificar el original
std::vector<char> copia_reemplazada;
std::replace_copy(letras.begin(), letras.end(), std::back_inserter(copia_reemplazada), 'X', 'Y');
std::cout << "\nCopia con 'X' reemplazadas por 'Y': ";
for (char c : copia_reemplazada) std::cout << c << " "; // Salida: * Y c Y d *
std::cout << std::endl;
return 0;
}
2.4. Eliminación Lógica y Física: remove, remove_if y erase
std::remove(inicio, fin, valor): "Mueve" los elementos iguales avaloral final del rango, retornando un iterador al nuevo final lógico. No cambia el tamaño del contenedor.std::remove_if(inicio, fin, predicado): Similar aremove, pero para elementos que satisfacen unpredicado.
Para la eliminación física (cambiar el tamaño del contenedor), estos algoritmos se usan junto con el método erase del contenedor.
#include <iostream>
#include <vector>
#include <algorithm> // Para remove, remove_if
int main() {
std::vector<int> mi_vector = {1, 7, 2, 7, 3, 7, 4};
// Eliminar lógicamente todos los '7'
auto nuevo_fin = std::remove(mi_vector.begin(), mi_vector.end(), 7);
// En este punto, mi_vector podría ser {1, 2, 3, 4, 7, 7, 7} pero los últimos '7' no son accesibles
std::cout << "Después de remove (lógico): ";
for (int x : mi_vector) std::cout << x << " "; // Salida: 1 2 3 4 7 7 7 (elementos basura o antiguos)
// Eliminar físicamente los elementos extra
mi_vector.erase(nuevo_fin, mi_vector.end());
std::cout << "\nDespués de erase (físico): ";
for (int x : mi_vector) std::cout << x << " "; // Salida: 1 2 3 4
// Eliminar lógicamente y físicamente los números pares
mi_vector = {10, 11, 12, 13, 14, 15};
mi_vector.erase(std::remove_if(mi_vector.begin(), mi_vector.end(), [](int n) {
return n % 2 == 0; // Es par
}), mi_vector.end());
std::cout << "\nDespués de eliminar pares: ";
for (int x : mi_vector) std::cout << x << " "; // Salida: 11 13 15
std::cout << std::endl;
return 0;
}
2.5. Elementos Únicos: unique
Elimina elementos consecutivos duplicados de un rango, moviendo los elementos únicos al principio. Retorna un iterador al nuevo final lógico. Requiere el uso de erase para la eliminación física.
#include <iostream>
#include <vector>
#include <algorithm> // Para unique
int main() {
std::vector<int> datos_duplicados = {1, 1, 1, 2, 2, 3, 4, 4, 5, 5, 5};
std::cout << "Original: ";
for (int x : datos_duplicados) std::cout << x << " "; // Salida: 1 1 1 2 2 3 4 4 5 5 5
auto ultimo_elemento_unico = std::unique(datos_duplicados.begin(), datos_duplicados.end());
// datos_duplicados podría ser {1, 2, 3, 4, 5, 4, 4, 5, 5, 5}
// donde los elementos a partir de ultimo_elemento_unico no son relevantes
datos_duplicados.erase(ultimo_elemento_unico, datos_duplicados.end());
std::cout << "\nDespués de unique + erase: ";
for (int x : datos_duplicados) std::cout << x << " "; // Salida: 1 2 3 4 5
std::cout << std::endl;
return 0;
}
2.6. Inversión: reverse
Invierte el orden de los elementos en un rango.
#include <iostream>
#include <vector>
#include <algorithm> // Para reverse
int main() {
std::vector<char> palabra = {'S', 'T', 'L', ' ', 'C', '+'};
std::cout << "Original: ";
for (char c : palabra) std::cout << c; // Salida: STL C+
std::reverse(palabra.begin(), palabra.end());
std::cout << "\nInvertida: ";
for (char c : palabra) std::cout << c; // Salida: +C LTS
std::cout << std::endl;
return 0;
}
2.7. Rotación: rotate
Rota los elementos en un rango, moviendo el elemento en la posición especificada al inicio.
#include <iostream>
#include <vector>
#include <algorithm> // Para rotate
int main() {
std::vector<int> elementos = {10, 20, 30, 40, 50};
std::cout << "Original: ";
for (int x : elementos) std::cout << x << " "; // Salida: 10 20 30 40 50
// Rotar para que '30' (índice 2) sea el nuevo primer elemento
std::rotate(elementos.begin(), elementos.begin() + 2, elementos.end());
std::cout << "\nRotado (pivot 30): ";
for (int x : elementos) std::cout << x << " "; // Salida: 30 40 50 10 20
std::cout << std::endl;
return 0;
}
2.8. Aleatorización: shuffle (C++11)
Reordena aleatoriamente los elementos en un rango utilizando un generador de números aleatorios.
#include <iostream>
#include <vector>
#include <algorithm> // Para shuffle
#include <random> // Para random_device, mt19937
#include <chrono> // Para system_clock
int main() {
std::vector<int> baraja = {1, 2, 3, 4, 5, 6, 7};
std::cout << "Baraja original: ";
for (int x : baraja) std::cout << x << " ";
// Usar un generador de números aleatorios para mezclar
unsigned semilla = std::chrono::system_clock::now().time_since_epoch().count();
std::mt19937 g(semilla);
std::shuffle(baraja.begin(), baraja.end(), g);
std::cout << "\nBaraja mezclada: ";
for (int x : baraja) std::cout << x << " "; // Salida: Orden aleatorio, e.g., 5 2 7 1 4 3 6
std::cout << std::endl;
return 0;
}
3. Algoritmos de Ordenación y Relacionados
Estos algoritmos organizan los elementos en un orden específico.
3.1. Ordenación: sort, stable_sort, partial_sort
std::sort(inicio, fin, comparador_opcional): Ordena los elementos. Es generalmente inestable y de complejidad O(N log N).std::stable_sort(inicio, fin, comparador_opcional): Ordena los elementos manteniendo el orden relativo de elementos equivalentes. Es estable y de complejidad O(N log N).std::partial_sort(inicio, medio, fin, comparador_opcional): Ordena los primerosmedio - inicioelementos del rango, que serán los más pequeños (o más grandes si se usa un comparador) del rango completo.
#include <iostream>
#include <vector>
#include <algorithm> // Para sort, stable_sort, partial_sort
#include <functional> // Para std::greater
int main() {
std::vector<int> desordenados = {8, 1, 4, 2, 7, 3};
std::cout << "Original: ";
for (int x : desordenados) std::cout << x << " "; // Salida: 8 1 4 2 7 3
std::sort(desordenados.begin(), desordenados.end()); // Orden ascendente por defecto
std::cout << "\nOrdenado (ascendente): ";
for (int x : desordenados) std::cout << x << " "; // Salida: 1 2 3 4 7 8
std::sort(desordenados.begin(), desordenados.end(), std::greater<int>()); // Orden descendente
std::cout << "\nOrdenado (descendente): ";
for (int x : desordenados) std::cout << x << " "; // Salida: 8 7 4 3 2 1
// Demostración de stable_sort con pares
std::vector<std::pair<int, char>> items = {{1, 'c'}, {2, 'a'}, {1, 'b'}, {3, 'd'}, {2, 'e'}};
std::stable_sort(items.begin(), items.end(), [](const auto& p1, const auto& p2) {
return p1.first < p2.first; // Ordenar por el primer elemento
});
std::cout << "\nStable sort: ";
for (const auto& p : items) std::cout << "(" << p.first << "," << p.second << ") ";
// Salida: (1,c) (1,b) (2,a) (2,e) (3,d) - Notar que 'c' sigue a 'b' para el valor 1, y 'a' a 'e' para el valor 2. (Originalmente 'a' venia antes de 'e')
std::vector<int> datos_parciales = {9, 2, 5, 1, 8, 3, 7};
// Queremos los 3 elementos más pequeños ordenados al principio
std::partial_sort(datos_parciales.begin(), datos_parciales.begin() + 3, datos_parciales.end());
std::cout << "\nPartial sort (primeros 3): ";
for (int x : datos_parciales) std::cout << x << " "; // Salida: 1 2 3 9 8 5 7 (los primeros 3 están ordenados, el resto no)
std::cout << std::endl;
return 0;
}
3.2. Búsqueda de n-ésimo Elemento: nth_element
Reorganiza el rango de manera que el elemento en la posición n (especificada por un iterador) sea el mismo que estaría allí si el rango estuviera completamente ordenado. Todos los elementos a su izquierda serán menores o iguales, y todos a su derecha serán mayores o iguales.
#include <iostream>
#include <vector>
#include <algorithm> // Para nth_element
int main() {
std::vector<int> numeros_random = {50, 10, 80, 20, 60, 30, 70, 40};
// Encontrar el elemento que estaría en la posición del 4º (índice 3) si estuviera ordenado
std::nth_element(numeros_random.begin(), numeros_random.begin() + 3, numeros_random.end());
std::cout << "Después de nth_element (índice 3): ";
for (int x : numeros_random) std::cout << x << " ";
// Salida (ejemplo, el orden de los demás varía): 30 10 20 40 60 50 70 80. El elemento en índice 3 es 40.
std::cout << "\nEl elemento en la posición del 4º (índice 3) es: " << numeros_random[3] << std::endl; // Salida: 40
return 0;
}
3.3. Búsqueda Binaria: binary_search, lower_bound, upper_bound
Requieren que el rango esté ordenado.
std::binary_search(inicio, fin, valor): Comprueba sivalorexiste en el rango (retornabool).std::lower_bound(inicio, fin, valor): Retorna un iterador al primer elemento que no es menor quevalor.std::upper_bound(inicio, fin, valor): Retorna un iterador al primer elemento que es mayor quevalor.
#include <iostream>
#include <vector>
#include <algorithm> // Para binary_search, lower_bound, upper_bound
int main() {
std::vector<int> datos_ordenados = {10, 20, 30, 30, 40, 50}; // Debe estar ordenado
// ¿Existe el 30?
bool existe_treinta = std::binary_search(datos_ordenados.begin(), datos_ordenados.end(), 30);
std::cout << "El 30 existe? " << std::boolalpha << existe_treinta << std::endl; // Salida: true
// Encontrar el primer elemento >= 30
auto it_lb = std::lower_bound(datos_ordenados.begin(), datos_ordenados.end(), 30);
std::cout << "lower_bound para 30 en índice: " << (it_lb - datos_ordenados.begin()) << std::endl; // Salida: 2
// Encontrar el primer elemento > 30
auto it_ub = std::upper_bound(datos_ordenados.begin(), datos_ordenados.end(), 30);
std::cout << "upper_bound para 30 en índice: " << (it_ub - datos_ordenados.begin()) << std::endl; // Salida: 4
return 0;
}
3.4. Fusión: merge
Combina dos rangos ordenados en un tercer rango también ordenado. Los rangos de entrada deben estar ordenados.
#include <iostream>
#include <vector>
#include <algorithm> // Para merge
int main() {
std::vector<int> lista1 = {1, 5, 9};
std::vector<int> lista2 = {2, 4, 8};
std::vector<int> lista_fusionada(lista1.size() + lista2.size());
std::merge(lista1.begin(), lista1.end(), lista2.begin(), lista2.end(), lista_fusionada.begin());
std::cout << "Lista fusionada: ";
for (int x : lista_fusionada) std::cout << x << " "; // Salida: 1 2 4 5 8 9
std::cout << std::endl;
return 0;
}
4. Algoritmos de Montón (Heap Algorithms)
Estos algoritmos tratan un rango como una estructura de datos de montón (heap), típicamente un max-heap.
#include <iostream>
#include <vector>
#include <algorithm> // Para make_heap, push_heap, pop_heap, sort_heap
int main() {
std::vector<int> datos_monticulo = {7, 1, 9, 3, 5, 2};
std::cout << "Original: ";
for (int x : datos_monticulo) std::cout << x << " ";
std::make_heap(datos_monticulo.begin(), datos_monticulo.end()); // Crea un max-heap
std::cout << "\nDespués de make_heap: ";
for (int x : datos_monticulo) std::cout << x << " "; // Salida (ejemplo): 9 7 5 3 1 2
datos_monticulo.push_back(10); // Agrega un elemento al final
std::push_heap(datos_monticulo.begin(), datos_monticulo.end()); // Restaura la propiedad de heap
std::cout << "\nDespués de push_back(10) y push_heap: ";
for (int x : datos_monticulo) std::cout << x << " "; // Salida (ejemplo): 10 7 9 3 1 2 5
std::pop_heap(datos_monticulo.begin(), datos_monticulo.end()); // Mueve el elemento más grande al final
int elemento_maximo = datos_monticulo.back(); // El elemento más grande ahora está al final
datos_monticulo.pop_back(); // Elimina el elemento más grande
std::cout << "\nDespués de pop_heap y pop_back (maximo=" << elemento_maximo << "): ";
for (int x : datos_monticulo) std::cout << x << " "; // Salida (ejemplo): 9 7 5 3 1 2
std::sort_heap(datos_monticulo.begin(), datos_monticulo.end()); // Ordena el heap en orden ascendente
std::cout << "\nDespués de sort_heap: ";
for (int x : datos_monticulo) std::cout << x << " "; // Salida: 1 2 3 5 7 9
std::cout << std::endl;
return 0;
}
5. Algoritmos de Mínimo/Máximo
Encuentran los valores o elementos extremos en un rango.
5.1. min y max
Retornan el menor/mayor de dos valores o de una lista de inicializadores.
#include <iostream>
#include <algorithm> // Para min, max
#include <vector> // Necesario para initializer_list
int main() {
int val_a = 15, val_b = 8;
int menor_entre_dos = std::min(val_a, val_b); // 8
int mayor_entre_dos = std::max(val_a, val_b); // 15
std::cout << "Menor entre " << val_a << " y " << val_b << ": " << menor_entre_dos << std::endl;
std::cout << "Mayor entre " << val_a << " y " << val_b << ": " << mayor_entre_dos << std::endl;
auto min_lista = std::min({5, 9, 2, 7, 3}); // 2
auto max_lista = std::max({5, 9, 2, 7, 3}); // 9
std::cout << "Menor de la lista: " << min_lista << std::endl;
std::cout << "Mayor de la lista: " << max_lista << std::endl;
return 0;
}
5.2. min_element y max_element
Retornan un iterador al elemento mínimo/máximo en un rango.
#include <iostream>
#include <vector>
#include <algorithm> // Para min_element, max_element
int main() {
std::vector<double> medidas = {3.14, 1.618, 2.718, 0.577};
auto it_min = std::min_element(medidas.begin(), medidas.end());
auto it_max = std::max_element(medidas.begin(), medidas.end());
std::cout << "Mínimo elemento: " << *it_min << std::endl; // Salida: 0.577
std::cout << "Máximo elemento: " << *it_max << std::endl; // Salida: 3.14
return 0;
}
5.3. minmax_element (C++11)
Retorna un std::pair de iteradores al elemento mínimo y máximo en un rango.
#include <iostream>
#include <vector>
#include <algorithm> // Para minmax_element
int main() {
std::vector<int> calificaciones = {85, 92, 78, 95, 88};
auto par_min_max = std::minmax_element(calificaciones.begin(), calificaciones.end());
std::cout << "Calificación mínima: " << *par_min_max.first << std::endl; // Salida: 78
std::cout << "Calificación máxima: " << *par_min_max.second << std;<< "std::endl; // Salida: 95
return 0;
}
6. Algoritmos Numéricos (en <numeric>)
Proporcionan operaciones matemáticas comunes sobre rangos.
6.1. accumulate
Calcula la suma (o cualquier operación binaria) de los elementos en un rango, empezando por un valor inicial.
#include <iostream>
#include <vector>
#include <numeric> // Para accumulate
#include <functional> // Para std::multiplies
int main() {
std::vector<int> serie = {1, 2, 3, 4};
int suma_total = std::accumulate(serie.begin(), serie.end(), 0); // 0 es el valor inicial
std::cout << "Suma de los elementos: " << suma_total << std::endl; // Salida: 10
int producto_total = std::accumulate(serie.begin(), serie.end(), 1, std::multiplies<int>()); // 1 es el valor inicial
std::cout << "Producto de los elementos: " << producto_total << std::endl; // Salida: 24
std::string frase = std::accumulate(std::next(serie.begin()), serie.end(),
std::to_string(serie[0]),
[](std::string a, int b){ return std::move(a) + "-" + std::to_string(b); });
std::cout << "Concatenación de cadena: " << frase << std::endl; // Salida: 1-2-3-4
return 0;
}
6.2. inner_product
Calcula el producto escalar de dos rangos (o una operación generalizada).
#include <iostream>
#include <vector>
#include <numeric> // Para inner_product
int main() {
std::vector<int> v_x = {1, 2, 3};
std::vector<int> v_y = {5, 6, 7};
// Producto escalar: (1*5) + (2*6) + (3*7) = 5 + 12 + 21 = 38
int prod_escalar = std::inner_product(v_x.begin(), v_x.end(), v_y.begin(), 0);
std::cout << "Producto escalar: " << prod_escalar << std::endl; // Salida: 38
return 0;
}
6.3. iota (C++11)
Rellena un rango con valores incrementales, comenzando desde un valor dado.
#include <iostream>
#include <vector>
#include <numeric> // Para iota
int main() {
std::vector<int> secuencia(7);
std::iota(secuencia.begin(), secuencia.end(), 100); // Rellena con 100, 101, ...
std::cout << "Secuencia generada con iota: ";
for (int x : secuencia) std::cout << x << " "; // Salida: 100 101 102 103 104 105 106
std::cout << std::endl;
return 0;
}
6.4. partial_sum
Calcula la suma parcial de los elementos de un rango y almacena los resultados en otro rango.
#include <iostream>
#include <vector>
#include <numeric> // Para partial_sum
int main() {
std::vector<int> entrada = {1, 2, 3, 4, 5};
std::vector<int> sumas_parciales(entrada.size());
std::partial_sum(entrada.begin(), entrada.end(), sumas_parciales.begin());
std::cout << "Sumas parciales: ";
for (int x : sumas_parciales) std::cout << x << " "; // Salida: 1 3 6 10 15
std::cout << std::endl;
return 0;
}
6.5. adjacent_difference
Calcula la diferencia entre elementos adyacentes en un rango y los almacena en otro rango. El primer elemento se copia sin cambios.
#include <iostream>
#include <vector>
#include <numeric> // Para adjacent_difference
int main() {
std::vector<int> serie_numerica = {10, 12, 15, 19, 24};
std::vector<int> diferencias(serie_numerica.size());
std::adjacent_difference(serie_numerica.begin(), serie_numerica.end(), diferencias.begin());
std::cout << "Diferencias adyacentes: ";
for (int x : diferencias) std::cout << x << " "; // Salida: 10 2 3 4 5
std::cout << std::endl;
return 0;
}
7. Otros Algoritmos Útiles
7.1. Generación: generate
Rellena un rango con valores producidos por una función generadora (típicamente una lambda).
#include <iostream>
#include <vector>
#include <algorithm> // Para generate
int main() {
std::vector<int> vector_generado(6);
int contador = 100;
std::generate(vector_generado.begin(), vector_generado.end(), [&contador]() {
return contador++;
});
std::cout << "Vector generado: ";
for (int x : vector_generado) std::cout << x << " "; // Salida: 100 101 102 103 104 105
std::cout << std::endl;
return 0;
}
7.2. Generación Parcial: generate_n
Rellena los primeros N elementos de un rango con valores producidos por una función generadora.
#include <iostream>
#include <vector>
#include <algorithm> // Para generate_n
int main() {
std::vector<int> vector_parcial(5, 99); // Inicializa con 99
int inicio_val = 1;
// Generar los primeros 3 elementos con valores incrementales
std::generate_n(vector_parcial.begin(), 3, [&inicio_val]() {
return inicio_val++;
});
std::cout << "Vector generado parcialmente: ";
for (int x : vector_parcial) std::cout << x << " "; // Salida: 1 2 3 99 99
std::cout << std::endl;
return 0;
}
7.3. Inclusión de Conjuntos: includes
Comprueba si un rango ordenado contiene todos los elementos de otro rango ordenado.
#include <iostream>
#include <vector>
#include <algorithm> // Para includes
int main() {
std::vector<int> conjunto_mayor = {10, 20, 30, 40, 50};
std::vector<int> subconjunto_valido = {20, 40};
std::vector<int> subconjunto_invalido = {15, 20};
bool incluye_valido = std::includes(conjunto_mayor.begin(), conjunto_mayor.end(),
subconjunto_valido.begin(), subconjunto_valido.end());
std::cout << "El conjunto_mayor incluye {20, 40}? " << std::boolalpha << incluye_valido << std::endl; // Salida: true
bool incluye_invalido = std::includes(conjunto_mayor.begin(), conjunto_mayor.end(),
subconjunto_invalido.begin(), subconjunto_invalido.end());
std::cout << "El conjunto_mayor incluye {15, 20}? " << std::boolalpha << incluye_invalido << std::endl; // Salida: false
return 0;
}
7.4. Operaciones de Conjuntos: set_union, set_intersection, set_difference, set_symmetric_difference
Realizan operaciones estándar de teoría de conjuntos en rangos ordenados.
#include <iostream>
#include <vector>
#include <algorithm> // Para set_union, set_intersection, set_difference, set_symmetric_difference
#include <iterator> // Para back_inserter
int main() {
std::vector<int> s1 = {1, 2, 3, 4, 5};
std::vector<int> s2 = {4, 5, 6, 7, 8};
std::vector<int> resultado;
std::cout << "Conjunto 1: {1 2 3 4 5}" << std::endl;
std::cout << "Conjunto 2: {4 5 6 7 8}" << std::endl;
// Unión (s1 U s2)
std::set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), std::back_inserter(resultado));
std::cout << "Unión: ";
for (int x : resultado) std::cout << x << " "; // Salida: 1 2 3 4 5 6 7 8
std::cout << std::endl;
// Intersección (s1 ∩ s2)
resultado.clear();
std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), std::back_inserter(resultado));
std::cout << "Intersección: ";
for (int x : resultado) std::cout << x << " "; // Salida: 4 5
std::cout << std::endl;
// Diferencia (s1 - s2)
resultado.clear();
std::set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), std::back_inserter(resultado));
std::cout << "Diferencia (s1 - s2): ";
for (int x : resultado) std::cout << x << " "; // Salida: 1 2 3
std::cout << std::endl;
// Diferencia simétrica ((s1 U s2) - (s1 ∩ s2))
resultado.clear();
std::set_symmetric_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), std::back_inserter(resultado));
std::cout << "Diferencia simétrica: ";
for (int x : resultado) std::cout << x << " "; // Salida: 1 2 3 6 7 8
std::cout << std::endl;
return 0;
}
8. Preguntas Comunes sobre Algoritmos STL
8.1. ¿Cuál es la diferencia entre std::sort y std::stable_sort?
std::sortes típicamente más rápido (implementación usualmente Introsort, una combinación de Quicksort, Heapsort y Insertion Sort). No garantiza la estabilidad, lo que significa que el orden relativo de los elementos que se comparan como "iguales" puede cambiar. Su complejidad temporal promedio es O(N log N) y espacial O(log N) o O(N) para casos patológicos.std::stable_sortgarantiza que el orden relativo de los elementos equivalentes se mantendrá. Generalmente utiliza una implementación basada en Merge Sort. Es un poco más lento questd::sorty su complejidad espacial puede ser O(N) en el peor de los casos, pero su complejidad temporal es O(N log N) garantizada.
8.2. ¿Por qué los algoritmos remove (y remove_if) requieren combinarse con erase?
Los algoritmos std::remove y std::remove_if son genéricos y no pueden modificar el tamaño subyacente del contenedor (como std::vector::size() o std::list::size()). Su función es reordenar los elementos de manera que aquellos que no deben ser "eliminados" se muevan al principio del rango. Retornan un iterador al nuevo "final lógico". Los elementos entre este nuevo final lógico y el final original del contenedor son los que deberían ser eliminados. Para una eliminación física que reduzca el tamaño del contenedor y libere memoria, es necesario llamar al método erase() del contenedor con el rango definido por el iterador retornado por remove y el final original del contenedor. Esta combinación se conoce como el "patrón erase-remove": contenedor.erase(std::remove(contenedor.begin(), contenedor.end(), valor), contenedor.end());
8.3. ¿Qué algoritmos requieren que el rango de entrada esté previamente ordenado?
Varios algoritmos dependen de que los rangos estén ordenados para funcionar correctamente o de manera eficiente. Entre ellos se encuentran:
- Algoritmos de búsqueda binaria:
std::binary_search,std::lower_bound,std::upper_bound,std::equal_range. - Algoritmos de fusión:
std::merge. - Algoritmos de operaciones de conjuntos:
std::set_union,std::set_intersection,std::set_difference,std::set_symmetric_difference,std::includes. - Algunas sobrecargas de
std::uniquepueden beneficiarse de un rango ordenado si se quieren eliminar todos los duplicados, no solo los consecutivos. De hecho,std::uniquesolo elimina duplicados *consecutivos*, por lo que para eliminar todos los duplicados en un rango, se suele aplicarstd::sortprimero y luegostd::unique.