Guía Exhaustiva de los Algoritmos de la STL en C++

  1. Algoritmos de Secuencia No Modificadores

Estas funciones operan sobre los elementos de un contenedor sin alterar su estado ni su orden original.

1.1. Búsqueda de elementos (find)

  • find(inicio, fin, valor): Localiza el primer elemento equivalente a valor. Devuelve un iterador al final si no hay coincidencias.
  • find_if(inicio, fin, predicado): Busca el primer elemento que cumpla con la condición del predicado.
  • find_end(inicio, fin, sub_inicio, sub_fin): Encuentra la última aparición de una subsecuencia específica.
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> data_set = {10, 25, 42, 55, 70};

    // Buscar el valor 42
    auto iter = std::find(data_set.begin(), data_set.end(), 42);
    if (iter != data_set.end()) {
        std::cout << "Elemento localizado: " << *iter << '\n';
    }

    // Localizar el primer valor superior a 50
    auto iter_cond = std::find_if(data_set.begin(), data_set.end(), [](int val) {
        return val > 50;
    });
    std::cout << "Primer valor > 50: " << *iter_cond << '\n';

    // Búsqueda de subsecuencia
    std::vector<int> target_sub = {25, 42};
    auto iter_sub = std::find_end(data_set.begin(), data_set.end(), target_sub.begin(), target_sub.end());
    if (iter_sub != data_set.end()) {
        std::cout << "Subsecuencia inicia en el índice: " << std::distance(data_set.begin(), iter_sub) << '\n';
    }
    return 0;
}

1.2. Conteo de elementos (count)

  • count(inicio, fin, valor): Determina la cantidad de elementos idénticos a valor.
  • count_if(inicio, fin, predicado): Calcula cuántos elementos satisfacen el predicado proporcionado.
std::vector<int> measurements = {4, 7, 7, 2, 9, 7, 3};
int total_sevens = std::count(measurements.begin(), measurements.end(), 7); 

int odd_count = std::count_if(measurements.begin(), measurements.end(), [](int num) { 
    return num % 2 != 0; 
}); 

1.3. Iteración y aplicación (for_each)

Ejecuta una función dada sobre cada elemento dentro del intervalo especificado.

std::vector<int> readings = {2, 4, 6, 8, 10};
std::for_each(readings.begin(), readings.end(), [](int& val) { 
    val += 5; 
});
// readings ahora contiene {7, 9, 11, 13, 15}

1.4. Comparación de rangos (equal y mismatch)

  • equal(ini1, fin1, ini2): Verifica si dos intervalos contienen los mismos elementos en el mismo orden.
  • mismatch(ini1, fin1, ini2): Identifica el primer par de elementos que difieren entre dos rangos.
std::vector<int> seq_a = {10, 20, 30};
std::vector<int> seq_b = {10, 20, 99};
std::vector<int> seq_c = {10, 20, 30, 40};

bool are_equal = std::equal(seq_a.begin(), seq_a.end(), seq_b.begin());

auto diff_pair = std::mismatch(seq_a.begin(), seq_a.end(), seq_c.begin());
if (diff_pair.first != seq_a.end()) {
    std::cout << "Diferencia hallada: " << *diff_pair.first << " contra " << *diff_pair.second << '\n';
}

1.5. Evaluación de condiciones globales (all_of, any_of, none_of)

Permiten validar si todos, alguno o ningún elemento del rango cumple con un criterio.

std::vector<int> scores = {85, 90, 92, 88};
bool all_passing = std::all_of(scores.begin(), scores.end(), [](int s) { return s >= 60; }); 
bool has_perfect = std::any_of(scores.begin(), scores.end(), [](int s) { return s == 100; }); 
bool no_failures = std::none_of(scores.begin(), scores.end(), [](int s) { return s < 60; }); 

  1. Algoritmos de Secuencia Modificadores

Estas herramientas alteran el contenido o la disposición de los elementos en los contenedores.

2.1. Copia de datos (copy y copy_if)

  • copy(ini, fin, destino): Duplica los elementos del rango origen hacia la posición de destino.
  • copy_if(ini, fin, destino, predicado): Transfiere únicamente los elementos que evalúan como verdaderos en el predicado.
std::vector<int> source_data = {11, 22, 33, 44, 55};
std::vector<int> target_data(5); 

std::copy(source_data.begin(), source_data.end(), target_data.begin());

std::vector<int> filtered_data;
std::copy_if(source_data.begin(), source_data.end(), std::back_inserter(filtered_data), [](int x) {
    return x > 30;
}); 

Nota: El uso de std::back_inserter es crucial cuando el contenedor de destino no tiene espacio preasignado, ya que invoca push_back dinámicamente.

2.2. Transformación de elemantos (transform)

Aplica una operación a cada elemento y almacena el resultado en un rango de salida.

std::vector<int> base_vals = {2, 3, 4};
std::vector<int> cubed_vals(3);

std::transform(base_vals.begin(), base_vals.end(), cubed_vals.begin(), [](int v) {
    return v * v * v;
}); 

std::vector<int> weights = {10, 20, 30};
std::vector<int> multipliers = {2, 3, 4};
std::vector<int> results(3);
std::transform(weights.begin(), weights.end(), multipliers.begin(), results.begin(), [](int w, int m) {
    return w * m;
}); 

2.3. Reemplazo de valores (replace)

  • replace: Sustituye un valor específico por otro nuevo.
  • replace_if: Cambia los elementos que cumplen una condición.
  • replace_copy: Genera una copia con los reemplazos aplicados, dejando el original intacto.
std::vector<int> codes = {1, 2, 3, 2, 5};

std::replace(codes.begin(), codes.end(), 2, 200); 

std::replace_if(codes.begin(), codes.end(), [](int c) {
    return c > 100;
}, 0); 

std::vector<int> modified_codes;
std::replace_copy(codes.begin(), codes.end(), std::back_inserter(modified_codes), 3, 300); 

2.4. Eliminación lógica (remove y erase)

Las funciones remove y remove_if no reducen el tamaño del contenedor; simplemente desplazan los elementos no eliminados al principio y devuelven un nuevo iterador final lógico. Para redimensionar, se debe invocar erase.

std::vector<int> items = {10, 20, 30, 20, 40};

auto logical_end = std::remove(items.begin(), items.end(), 20); 
items.erase(logical_end, items.end()); 

items = {1, 2, 3, 4, 5, 6};
items.erase(std::remove_if(items.begin(), items.end(), [](int i) {
    return i % 2 != 0;
}), items.end()); 

2.5. Eliminación de duplicados consecutivos (unique)

Compacta el rango eliminando elementos adyacentes repetidos. Requiere erase para ajustar el tamaño.

std::vector<int> raw_stream = {5, 5, 8, 8, 8, 9, 12, 12};
auto new_boundary = std::unique(raw_stream.begin(), raw_stream.end());
raw_stream.erase(new_boundary, raw_stream.end()); 

2.6. Inversión y Rotación (reverse y rotate)

std::vector<int> timeline = {1, 2, 3, 4, 5};
std::reverse(timeline.begin(), timeline.end()); 

std::vector<int> cycle = {10, 20, 30, 40, 50};
std::rotate(cycle.begin(), cycle.begin() + 3, cycle.end()); 

2.7. Mezcla aleatoria (shuffle)

Reorganiza los elementos de forma pseudoaleatoria utilizando un generador de números aleatorios.

#include <random>

std::vector<int> deck = {1, 2, 3, 4, 5, 6};
std::random_device entropy_source;
std::mt19937 rng_engine(entropy_source());
std::shuffle(deck.begin(), deck.end(), rng_engine);

  1. Algoritmos de Ordenación y Búsqueda

3.1. Ordenamiento (sort, stable_sort, partial_sort)

  • sort: Ordenamiento rápido e inestable (Introsort).
  • stable_sort: Mantiene el orden relativo de elementos equivalentes.
  • partial_sort: Ordena solo una porción inicial del contenedor.
std::vector<int> unsorted = {45, 12, 89, 33, 7};
std::sort(unsorted.begin(), unsorted.end()); 
std::sort(unsorted.begin(), unsorted.end(), std::greater<int>()); 

struct Record { int id; int priority; };
std::vector<Record> records = {{1, 5}, {2, 5}, {3, 2}};
std::stable_sort(records.begin(), records.end(), [](const Record& a, const Record& b) {
    return a.priority < b.priority;
});

std::vector<int> large_set = {99, 14, 52, 3, 28, 71};
std::partial_sort(large_set.begin(), large_set.begin() + 3, large_set.end());

3.2. Selección de enésimo elemento (nth_element)

Reordena el contenedor de modo que el elemento en la posición N sea el que estaría allí si el rango estuviera completamente ordenado.

std::vector<int> metrics = {15, 42, 8, 23, 99, 4};
std::nth_element(metrics.begin(), metrics.begin() + 2, metrics.end());
// metrics[2] ahora contiene el tercer valor más pequeño (15)

3.3. Búsqueda binaria (binary_search, lower_bound, upper_bound)

Estos algoritmos exigen que el rango de entrada esté previamente ordenado.

std::vector<int> sorted_data = {10, 20, 20, 30, 50};

bool is_present = std::binary_search(sorted_data.begin(), sorted_data.end(), 20);

auto lower_it = std::lower_bound(sorted_data.begin(), sorted_data.end(), 20);
auto upper_it = std::upper_bound(sorted_data.begin(), sorted_data.end(), 20);

3.4. Fusión de rangos (merge)

Combina dos secuencias ordenadas en una sola, preservando el orden.

std::vector<int> group_alpha = {2, 4, 6};
std::vector<int> group_beta = {1, 3, 5};
std::vector<int> combined(group_alpha.size() + group_beta.size());

std::merge(group_alpha.begin(), group_alpha.end(), group_beta.begin(), group_beta.end(), combined.begin());

  1. Operaciones con Montículos (Heaps)

La STL permite tratar rangos contiguos como estructuras de montículo (heap).

std::vector<int> heap_data = {30, 10, 50, 20, 40};
std::make_heap(heap_data.begin(), heap_data.end()); 

heap_data.push_back(60);
std::push_heap(heap_data.begin(), heap_data.end()); 

std::pop_heap(heap_data.begin(), heap_data.end()); 
int top_element = heap_data.back();
heap_data.pop_back();

std::sort_heap(heap_data.begin(), heap_data.end()); 

  1. Algoritmos de Mínimos y Máximos

5.1. Comparaciones directas (min y max)

int lowest = std::min(15, 8); 
int highest = std::max(15, 8); 

auto min_val = std::min({100, 25, 50, 12}); 

5.2. Búsqueda en rangos (min_element, max_element, minmax_element)

std::vector<int> temperatures = {22, 18, 35, 29, 15};
auto coldest = std::min_element(temperatures.begin(), temperatures.end()); 
auto hottest = std::max_element(temperatures.begin(), temperatures.end()); 

auto extremes = std::minmax_element(temperatures.begin(), temperatures.end());
// extremes.first apunta a 15, extremes.second apunta a 35

  1. Algoritmos Numéricos (<numeric>)

6.1. Acumulación y producto interno

#include <numeric>

std::vector<int> values = {1, 2, 3, 4, 5};
int total_sum = std::accumulate(values.begin(), values.end(), 0); 
int total_product = std::accumulate(values.begin(), values.end(), 1, std::multiplies<int>()); 

std::vector<int> vector_a = {1, 2, 3};
std::vector<int> vector_b = {10, 20, 30};
int dot_product = std::inner_product(vector_a.begin(), vector_a.end(), vector_b.begin(), 0); 

6.2. Generación de secuencias y sumas parciales

std::vector<int> sequence(5);
std::iota(sequence.begin(), sequence.end(), 100); 

std::vector<int> inputs = {2, 4, 6, 8};
std::vector<int> prefix_sums(inputs.size());
std::partial_sum(inputs.begin(), inputs.end(), prefix_sums.begin()); 

std::vector<int> diffs(inputs.size());
std::adjacent_difference(inputs.begin(), inputs.end(), diffs.begin()); 

  1. Algoritmos de Generación y Conjuntos

7.1. Generación de valores (generate)

std::vector<int> generated(5);
int counter = 100;
std::generate(generated.begin(), generated.end(), [&counter]() { 
    return counter -= 10; 
}); 

std::generate_n(generated.begin(), 3, [&counter]() { 
    return counter += 5; 
});

7.2. Operaciones de conjuntos

Estas funciones requeiren que los rangos de entrada estén ordenados.

std::vector<int> set_one = {1, 3, 5, 7, 9};
std::vector<int> set_two = {3, 5, 7, 11, 13};
std::vector<int> output;

bool is_subset = std::includes(set_one.begin(), set_one.end(), set_two.begin(), set_two.begin() + 3);

std::set_union(set_one.begin(), set_one.end(), set_two.begin(), set_two.end(), std::back_inserter(output));

output.clear();
std::set_intersection(set_one.begin(), set_one.end(), set_two.begin(), set_two.end(), std::back_inserter(output));

output.clear();
std::set_difference(set_one.begin(), set_one.end(), set_two.begin(), set_two.end(), std::back_inserter(output));

output.clear();
std::set_symmetric_difference(set_one.begin(), set_one.end(), set_two.begin(), set_two.end(), std::back_inserter(output));

  1. Preguntas Frecuentes y Consideraciones Técnicas

  1. ¿Cuándo elegir stable_sort sobre sort?
    Utilice stable_sort cuando el orden relativo original de los elementos con claves equivalentes deba preservarse (por ejemplo, al ordenar por una columna secundaria). Tenga en cuenta que stable_sort puede requerir memoria adicional O(N), mientras que sort opera in-place.
  2. ¿Por qué std::remove no elimina elementos físicamente?
    Los algoritmos de la STL operan mediente iteradores y no tienen conocimiento directo del contenedor subyacente. Por lo tanto, remove solo puede reorganizar los elementos y devolver un nuevo límite lógico. La eliminación física de la memoria es responsabilidad del contenedor a través de su método erase.
  3. ¿Qué algoritmos exigen rangos preordenados?
    Cualquier algoritmo basado en búsqueda binaria (binary_search, lower_bound, upper_bound, equal_range), así como las operaciones de conjuntos (set_union, set_intersection, etc.) y merge, requieren estrictamente que los datos de entrada estén ordenados para garantizar un comportamiento correcto y una complejidad temporal óptima.

Etiquetas: C++ STL algoritmos estructuras de datos iteradores

Publicado el 8-7 20:06