Explorando os Algoritmos da Standard Template Library em C++

A Standard Template Library (STL) do C++ oferece um conjunto poderoso de algoritmos que operam em coleções de dados, como vetores, listas e outros contêineres. Estes algoritmos são definidos em cabeçalhos como <algorithm> e <numeric> e são projetados para serem genéricos, trabalhando com iteradores. Este guia explora os algoritmos mais comuns, categorizando-os por sua funcionalidade principal.

  1. Algoritmos de Sequência Não Modificadores

Estes algoritmos inspecionam elementos dentro de um contêiner, mas não alteram seus valores ou a estrutura da sequência.

1.1 Busca de Elementos (find, find_if, find_end)

  • std::find(inicio, fim, valor): Localiza a primeira ocorrência de valor no intervalo [inicio, fim), retornando um iterador para ele ou fim se não encontrado.
  • std::find_if(inicio, fim, predicado): Encontra o primeiro elemento que satisfaz a condição definida pelo predicado.
  • std::find_end(inicio, fim, sub_inicio, sub_fim): Busca a última ocorrência de uma subsequência.
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

int main() {
    std::vector<std::string> palavras = {"maçã", "banana", "cereja", "maçã", "uva"};

    // Encontrar "cereja"
    auto it_cereja = std::find(palavras.begin(), palavras.end(), "cereja");
    if (it_cereja != palavras.end()) {
        std::cout << "Encontrado: " << *it_cereja << std::endl; // Saída: cereja
    }

    // Encontrar a primeira palavra com 4 letras
    auto it_quatroletras = std::find_if(palavras.begin(), palavras.end(), [](const std::string& s) {
        return s.length() == 4;
    });
    if (it_quatroletras != palavras.end()) {
        std::cout << "Primeira palavra com 4 letras: " << *it_quatroletras << std::endl; // Saída: uva
    }

    // Encontrar a última ocorrência da subsequência {"maçã", "uva"}
    std::vector<std::string> sub_seq = {"maçã", "uva"};
    auto it_sub_seq = std::find_end(palavras.begin(), palavras.end(), sub_seq.begin(), sub_seq.end());
    if (it_sub_seq != palavras.end()) {
        std::cout << "Subsequência começa no índice: " << std::distance(palavras.begin(), it_sub_seq) << std::endl; // Saída: 3
    }
    return 0;
}

1.2 Contagem de Elementos (count, count_if)

  • std::count(inicio, fim, valor): Retorna o número de elementos iguais a valor.
  • std::count_if(inicio, fim, predicado): Conta quantos elementos satisfazem um predicado.
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numeros = {10, 20, 10, 30, 20, 10, 40};
    int contagem_dez = std::count(numeros.begin(), numeros.end(), 10); // Conta ocorrências de 10
    std::cout << "Ocorrências de 10: " << contagem_dez << std::endl; // Saída: 3

    int contagem_pares = std::count_if(numeros.begin(), numeros.end(), [](int n) {
        return n % 2 == 0;
    }); // Conta números pares
    std::cout << "Quantidade de números pares: " << contagem_pares << std::endl; // Saída: 7
    return 0;
}

1.3 Aplicação de Função (for_each)

Aplica uma função ou objeto função a cada elemento dentro de um intervalo.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<double> valores = {1.5, 2.0, 3.5};
    std::cout << "Valores originais: ";
    for (double v : valores) {
        std::cout << v << " ";
    }
    std::cout << std::endl;

    // Duplicar cada valor (modifica os elementos in-place se o lambda receber por referência)
    std::for_each(valores.begin(), valores.end(), [](double& x) {
        x *= 2;
    });

    std::cout << "Valores duplicados: ";
    for (double v : valores) {
        std::cout << v << " "; // Saída: 3 4 7
    }
    std::cout << std::endl;
    return 0;
}

1.4 Comparação de Sequências (equal, mismatch)

  • std::equal(b1, e1, b2): Verifica se dois intervalos são idênticos elemento a elemento.
  • std::mismatch(b1, e1, b2): Retorna um par de iteradores apontando para os primeiros elementos não correspondentes em dois intervalos.
#include <vector>
#include <algorithm>
#include <iostream>
#include <iomanip> // Para std::boolalpha

int main() {
    std::vector<int> v1 = {1, 2, 3, 4};
    std::vector<int> v2 = {1, 2, 3, 5};
    std::vector<int> v3 = {1, 2, 3};

    // v1 e v2 são iguais nos primeiros 3 elementos?
    bool iguais = std::equal(v1.begin(), v1.begin() + 3, v2.begin());
    std::cout << "v1[0..2] == v2[0..2]? " << std::boolalpha << iguais << std::endl; // Saída: true

    // v1 e v2 são completamente iguais?
    iguais = std::equal(v1.begin(), v1.end(), v2.begin());
    std::cout << "v1 == v2? " << std::boolalpha << iguais << std::endl; // Saída: false

    // Encontrar a primeira diferença entre v1 e v2
    auto divergencia = std::mismatch(v1.begin(), v1.end(), v2.begin());
    if (divergencia.first != v1.end()) {
        std::cout << "Primeira diferença: " << *divergencia.first << " vs " << *divergencia.second << std::endl; // Saída: 4 vs 5
    }
    return 0;
}

1.5 Verificação de Condições (all_of, any_of, none_of)

Verificam se todos, algum ou nenhum dos elementos em um intervalo satisfazem uma condição.

#include <vector>
#include <algorithm>
#include <iostream>
#include <iomanip>

int main() {
    std::vector<int> dados = {2, 4, 6, 8};

    bool todos_pares = std::all_of(dados.begin(), dados.end(), [](int n) { return n % 2 == 0; });
    std::cout << "Todos são pares? " << std::boolalpha << todos_pares << std::endl; // Saída: true

    bool algum_impar = std::any_of(dados.begin(), dados.end(), [](int n) { return n % 2 != 0; });
    std::cout << "Algum é ímpar? " << std::boolalpha << algum_impar << std::endl; // Saída: false

    bool nenhum_negativo = std::none_of(dados.begin(), dados.end(), [](int n) { return n < 0; });
    std::cout << "Nenhum é negativo? " << std::boolalpha << nenhum_negativo << std::endl; // Saída: true
    return 0;
}

  1. Algoritmos de Sequência Modificadores

Estes algoritmos alteram os elementos ou a ordem dos elementos dentro de um contêiner.

2.1 Cópia de Elementos (copy, copy_if)

  • std::copy(inicio, fim, destino): Copia elementos do intervalo [inicio, fim) para um novo local começando em destino.
  • std::copy_if(inicio, fim, destino, predicado): Copia apenas os elementos que satisfazem o predicado.
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator> // Para std::back_inserter

int main() {
    std::vector<int> fonte = {10, 20, 30, 40, 50};
    std::vector<int> destino(fonte.size()); // Alocar espaço suficiente

    // Copia todos os elementos
    std::copy(fonte.begin(), fonte.end(), destino.begin());
    std::cout << "Destino (todos): ";
    for (int n : destino) std::cout << n << " "; // Saída: 10 20 30 40 50
    std::cout << std::endl;

    // Copia apenas números maiores que 25 para um novo vetor
    std::vector<int> maiores_que_vinte_e_cinco;
    std::copy_if(fonte.begin(), fonte.end(), std::back_inserter(maiores_que_vinte_e_cinco), [](int n) {
        return n > 25;
    });
    std::cout << "Maiores que 25: ";
    for (int n : maiores_que_vinte_e_cinco) std::cout << n << " "; // Saída: 30 40 50
    std::cout << std::endl;
    return 0;
}

Nota: Usar std::back_inserter é útil para contêineres que suportam push_back, pois ele redimensiona o contêiner automaticamente.

2.2 Transformação de Elementos (transform)

Aplica uma função a cada elemento de um ou dois interavlos de entrada e armazena os resultados em um intervalo de saída.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> valores_originais = {1, 2, 3, 4};
    std::vector<int> quadrados(valores_originais.size());

    // Calcular o quadrado de cada número
    std::transform(valores_originais.begin(), valores_originais.end(), quadrados.begin(), [](int n) {
        return n * n;
    });
    std::cout << "Quadrados: ";
    for (int n : quadrados) std::cout << n << " "; // Saída: 1 4 9 16
    std::cout << std::endl;

    // Somar elementos de dois vetores
    std::vector<int> a = {1, 2, 3};
    std::vector<int> b = {5, 5, 5};
    std::vector<int> soma(a.size());
    std::transform(a.begin(), a.end(), b.begin(), soma.begin(), [](int x, int y) {
        return x + y;
    });
    std::cout << "Soma de A e B: ";
    for (int n : soma) std::cout << n << " "; // Saída: 6 7 8
    std::cout << std::endl;
    return 0;
}

2.3 Substituição de Elementos (replace, replace_if, replace_copy)

  • std::replace(inicio, fim, valor_antigo, valor_novo): Substitui todas as ocorrências de valor_antigo por valor_novo.
  • std::replace_if(inicio, fim, predicado, valor_novo): Substitui elementos que satisfazem o predicado.
  • std::replace_copy(inicio, fim, destino, valor_antigo, valor_novo): Copia elementos para destino, substituindo-os durante a cópia sem modificar o intervalo original.
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>

int main() {
    std::vector<char> letras = {'a', 'b', 'c', 'a', 'd', 'e'};

    // Substituir 'a' por 'x'
    std::replace(letras.begin(), letras.end(), 'a', 'x');
    std::cout << "Após replace 'a' por 'x': ";
    for (char c : letras) std::cout << c << " "; // Saída: x b c x d e
    std::cout << std::endl;

    // Substituir consoantes (não-vogais) por '_'
    std::replace_if(letras.begin(), letras.end(), [](char c) {
        return !(c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'x');
    }, '_');
    std::cout << "Após replace_if consoantes por '_': ";
    for (char c : letras) std::cout << c << " "; // Saída: x _ _ x _ _
    std::cout << std::endl;

    std::vector<int> original = {10, 20, 30, 20, 40};
    std::vector<int> resultado_copia;
    // Copiar e substituir 20 por 200, sem alterar 'original'
    std::replace_copy(original.begin(), original.end(), std::back_inserter(resultado_copia), 20, 200);
    std::cout << "Original: ";
    for (int n : original) std::cout << n << " "; // Saída: 10 20 30 20 40
    std::cout << std::endl;
    std::cout << "Resultado da cópia com substituição: ";
    for (int n : resultado_copia) std::cout << n << " "; // Saída: 10 200 30 200 40
    std::cout << std::endl;
    return 0;
}

2.4 Remoção de Elementos (remove, remove_if, erase)

  • std::remove(inicio, fim, valor): "Move" elementos iguais a valor para o final do contêiner, retornando um iterador para o novo "final lógico". Não altera o tamanho físico do contêiner.
  • std::remove_if(inicio, fim, predicado): Move elementos que satisfazem o predicado para o final.
  • Para remover elementos fisicamente e redimensionar o contêiner, é preciso combinar remove (ou remove_if) com o método erase() do contêiner.
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> dados = {10, 20, 30, 20, 40, 20, 50};
    std::cout << "Dados originais: ";
    for (int n : dados) std::cout << n << " ";
    std::cout << std::endl;

    // Remover logicamente todos os 20s
    auto novo_fim = std::remove(dados.begin(), dados.end(), 20);
    std::cout << "Após std::remove(20) (lógico): ";
    for (int n : dados) std::cout << n << " "; // Saída: 10 30 40 50 40 20 50 (elementos indesejados no final)
    std::cout << std::endl;

    // Remover fisicamente os elementos excedentes
    dados.erase(novo_fim, dados.end());
    std::cout << "Após erase (físico): ";
    for (int n : dados) std::cout << n << " "; // Saída: 10 30 40 50
    std::cout << std::endl;

    // Exemplo combinado: remover elementos ímpares
    std::vector<int> numeros = {1, 2, 3, 4, 5, 6, 7, 8};
    std::cout << "Numeros originais: ";
    for (int n : numeros) std::cout << n << " ";
    std::cout << std::endl;

    numeros.erase(std::remove_if(numeros.begin(), numeros.end(), [](int n) {
        return n % 2 != 0; // Remover ímpares
    }), numeros.end());
    std::cout << "Após remover ímpares: ";
    for (int n : numeros) std::cout << n << " "; // Saída: 2 4 6 8
    std::cout << std::endl;
    return 0;
}

2.5 Eliminação de Duplicatas (unique)

Remove elementos duplicados consecutivos de um intervalo, retornando um iterador para o novo "final lógico". Requer combinação com erase() para remoção física.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> itens = {1, 1, 2, 2, 2, 3, 4, 4, 5};
    std::cout << "Itens com duplicatas: ";
    for (int n : itens) std::cout << n << " ";
    std::cout << std::endl;

    auto novo_fim = std::unique(itens.begin(), itens.end());
    itens.erase(novo_fim, itens.end());
    std::cout << "Itens sem duplicatas consecutivas: ";
    for (int n : itens) std::cout << n << " "; // Saída: 1 2 3 4 5
    std::cout << std::endl;
    return 0;
}

2.6 Inversão de Ordem (reverse)

Inverte a ordem dos elementos em um intervalo.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<char> chars = {'A', 'B', 'C', 'D', 'E'};
    std::cout << "Chars originais: ";
    for (char c : chars) std::cout << c << " ";
    std::cout << std::endl;

    std::reverse(chars.begin(), chars.end());
    std::cout << "Chars invertidos: ";
    for (char c : chars) std::cout << c << " "; // Saída: E D C B A
    std::cout << std::endl;
    return 0;
}

2.7 Rotação de Elementos (rotate)

Rotaciona os elementos em um intervalo, movendo o elemento na posição meio para o início.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numeros = {1, 2, 3, 4, 5};
    std::cout << "Numeros originais: ";
    for (int n : numeros) std::cout << n << " ";
    std::cout << std::endl;

    // Rotacionar de modo que '3' (índice 2) se torne o primeiro elemento
    std::rotate(numeros.begin(), numeros.begin() + 2, numeros.end());
    std::cout << "Numeros rotacionados: ";
    for (int n : numeros) std::cout << n << " "; // Saída: 3 4 5 1 2
    std::cout << std::endl;
    return 0;
}

2.8 Reordenação Aleatória (shuffle)

Reorganiza aleatoriamente os elementos em um intervalo. Requer um gerador de números aleatórios (C++11+).

#include <vector>
#include <algorithm>
#include <iostream>
#include <random> // Para std::random_device e std::mt19937
#include <chrono> // Para semente baseada no tempo

int main() {
    std::vector<int> baralho = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::cout << "Baralho original: ";
    for (int n : baralho) std::cout << n << " ";
    std::cout << std::endl;

    // Usar um gerador de números pseudo-aleatórios semeado com o tempo atual
    unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    std::shuffle(baralho.begin(), baralho.end(), std::default_random_engine(seed));
    
    std::cout << "Baralho embaralhado: ";
    for (int n : baralho) std::cout << n << " "; // Saída varia
    std::cout << std::endl;
    return 0;
}

  1. Algoritmos de Ordenação e Relacionados

Esses algoritmos organizam os elementos de um contêiner de acordo com uma ordem específica.

3.1 Ordenação Principal (sort, stable_sort, partial_sort)

  • std::sort(inicio, fim): Ordena os elementos em ordem crescente (padrão), utilizando um algoritmo instável (elementos iguais podem ter sua ordem relativa alterada). Geralmente, implementa Introsort.
  • std::stable_sort(inicio, fim): Ordena os elementos de forma estável, preservando a ordem relativa de elementos iguais. Tipicamente usa Mergesort.
  • std::partial_sort(inicio, meio, fim): Ordena o subintervalo [inicio, meio) de modo que contenha os menores elementos do intervalo completo [inicio, fim), e esses elementos estejam ordenados.
#include <vector>
#include <algorithm>
#include <iostream>
#include <functional> // Para std::greater

int main() {
    std::vector<int> dados = {5, 2, 8, 1, 9, 4};
    std::cout << "Dados originais: ";
    for (int n : dados) std::cout << n << " ";
    std::cout << std::endl;

    // Ordenação ascendente padrão
    std::sort(dados.begin(), dados.end());
    std::cout << "Após sort (ascendente): ";
    for (int n : dados) std::cout << n << " "; // Saída: 1 2 4 5 8 9
    std::cout << std::endl;

    // Ordenação descendente usando std::greater
    std::vector<int> dados_desc = {5, 2, 8, 1, 9, 4};
    std::sort(dados_desc.begin(), dados_desc.end(), std::greater<int>());
    std::cout << "Após sort (descendente): ";
    for (int n : dados_desc) std::cout << n << " "; // Saída: 9 8 5 4 2 1
    std::cout << std::endl;

    // Exemplo de stable_sort com pares (para mostrar estabilidade)
    std::vector<std::pair<int, std::string>> itens = {{3, "laranja"}, {1, "banana"}, {3, "maçã"}, {2, "uva"}};
    std::stable_sort(itens.begin(), itens.end(), [](const auto& a, const auto& b) {
        return a.first < b.first; // Ordenar pelo primeiro elemento, mantendo a ordem relativa de "laranja" e "maçã"
    });
    std::cout << "Após stable_sort: ";
    for (const auto& p : itens) std::cout << "(" << p.first << ", " << p.second << ") ";
    std::cout << std::endl; // Saída: (1, banana) (2, uva) (3, laranja) (3, maçã)

    // Exemplo de partial_sort
    std::vector<int> grandes_dados = {10, 3, 8, 1, 5, 2, 9, 4, 7, 6};
    // Colocar os 3 menores elementos no início e ordenados
    std::partial_sort(grandes_dados.begin(), grandes_dados.begin() + 3, grandes_dados.end());
    std::cout << "Após partial_sort (3 menores): ";
    for (int n : grandes_dados) std::cout << n << " "; // Saída: 1 2 3 [resto não ordenado]
    std::cout << std::endl;
    return 0;
}

3.2 Localização do n-ésimo Elemento (nth_element)

Reorganiza um intervalo de forma que o elemento na posição n seja o valor que estaria lá se o intervalo completo estivesse ordenado. Todos os elementos à esquerda de n são menores ou iguais a ele, e todos à direita são maiores ou iguais.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> amostra = {7, 3, 9, 1, 5, 8, 2, 6, 4};
    std::cout << "Amostra original: ";
    for (int n : amostra) std::cout << n << " ";
    std::cout << std::endl;

    // Encontrar o elemento que estaria na 4ª posição (índice 3) se ordenado
    std::nth_element(amostra.begin(), amostra.begin() + 3, amostra.end());
    std::cout << "Após nth_element (índice 3): ";
    for (int n : amostra) std;<< n << " "; // Saída: [valores menores ou iguais a amostra[3]] amostra[3] [valores maiores ou iguais a amostra[3]]
    std::cout << std::endl;
    std::cout << "Elemento na 4ª posição ordenada: " << amostra[3] << std::endl; // O valor pode ser 4
    return 0;
}

3.3 Busca em Intervalo Ordenado (binary_search, lower_bound, upper_bound)

Estes algoritmos exigem que o contêiner esteja **previamente ordenado**.

  • std::binary_search(inicio, fim, valor): Verifica se valor existe no intervalo (retorna bool).
  • std::lower_bound(inicio, fim, valor): Retorna um iterador para o primeiro elemento que não é menor que valor.
  • std::upper_bound(inicio, fim, valor): Retorna um iterador para o primeiro elemento que é maior que valor.
#include <vector>
#include <algorithm>
#include <iostream>
#include <iomanip>

int main() {
    std::vector<int> dados_ordenados = {10, 20, 20, 30, 40, 50}; // Deve estar ordenado!

    // Verificar se 30 existe
    bool encontrado = std::binary_search(dados_ordenados.begin(), dados_ordenados.end(), 30);
    std::cout << "30 existe? " << std::boolalpha << encontrado << std::endl; // Saída: true

    // Primeiro elemento >= 20
    auto lb_it = std::lower_bound(dados_ordenados.begin(), dados_ordenados.end(), 20);
    std::cout << "lower_bound para 20 no índice: " << std::distance(dados_ordenados.begin(), lb_it) << std::endl; // Saída: 1

    // Primeiro elemento > 20
    auto ub_it = std::upper_bound(dados_ordenados.begin(), dados_ordenados.end(), 20);
    std::cout << "upper_bound para 20 no índice: " << std::distance(dados_ordenados.begin(), ub_it) << std::endl; // Saída: 3
    return 0;
}

3.4 Fusão de Intervalos (merge)

Combina dois intervalos **ordenados** em um único intervalo de saída também ordenado.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> lista_a = {1, 3, 5, 7};
    std::vector<int> lista_b = {2, 4, 6, 8};
    std::vector<int> lista_fundida(lista_a.size() + lista_b.size());

    // As listas A e B devem estar ordenadas
    std::merge(lista_a.begin(), lista_a.end(), lista_b.begin(), lista_b.end(), lista_fundida.begin());
    std::cout << "Lista fundida: ";
    for (int n : lista_fundida) std::cout << n << " "; // Saída: 1 2 3 4 5 6 7 8
    std::cout << std::endl;
    return 0;
}

  1. Algoritmos de Heap

A STL oferece algoritmos para tratar um intervalo como uma estrutura de dados de heap (fila de prioridade baseada em árvore). Os algoritmos mais comuns são make_heap, push_heap, pop_heap e sort_heap.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> elementos = {4, 1, 7, 2, 5, 3};
    std::cout << "Elementos originais: ";
    for (int n : elementos) std::cout << n << " ";
    std::cout << std::endl;

    // 1. Construir um heap máximo a partir dos elementos
    std::make_heap(elementos.begin(), elementos.end());
    std::cout << "Após make_heap (heap máximo): ";
    for (int n : elementos) std::cout << n << " "; // Saída: 7 5 4 2 1 3 (ordem de heap)
    std::cout << std::endl;

    // 2. Adicionar um novo elemento ao heap
    elementos.push_back(8); // Adiciona no final
    std::push_heap(elementos.begin(), elementos.end()); // Restaura a propriedade do heap
    std::cout << "Após push_heap(8): ";
    for (int n : elementos) std::cout << n << " "; // Saída: 8 5 7 2 1 3 4
    std::cout << std::endl;

    // 3. Remover o maior elemento (raiz) do heap
    std::pop_heap(elementos.begin(), elementos.end()); // Move o maior para o final e restaura o heap
    int maior_val = elementos.back(); // O maior elemento está no final
    elementos.pop_back(); // Remove-o fisicamente
    std::cout << "Maior elemento extraído: " << maior_val << std::endl; // Saída: 8
    std::cout << "Heap após pop_heap: ";
    for (int n : elementos) std::cout << n << " "; // Saída: 7 5 4 2 1 3
    std::cout << std::endl;

    // 4. Transformar o heap em um intervalo ordenado
    std::sort_heap(elementos.begin(), elementos.end());
    std::cout << "Após sort_heap: ";
    for (int n : elementos) std::cout << n << " "; // Saída: 1 2 3 4 5 7 (ordenado)
    std::cout << std::endl;
    return 0;
}

  1. Algoritmos de Mínimo/Máximo

Para encontrar os maiores ou menores valores em coleções.

5.1 min e max (entre valores diretos)

Retornam o menor ou maior valor entre dois argumentos ou em uma lista de inicialização.

#include <algorithm>
#include <iostream>
#include <vector> // Para std::initializer_list

int main() {
    int x = 15, y = 8;
    int menor = std::min(x, y); // 8
    int maior = std::max(x, y); // 15
    std::cout << "Menor entre " << x << " e " << y << ": " << menor << std::endl;
    std::cout << "Maior entre " << x << " e " << y << ": " << maior << std::endl;

    auto min_na_lista = std::min({50, 10, 90, 30}); // 10
    auto max_na_lista = std::max({50, 10, 90, 30}); // 90
    std::cout << "Menor na lista: " << min_na_lista << std::endl;
    std::cout << "Maior na lista: " << max_na_lista << std::endl;
    return 0;
}

5.2 min_element e max_element (em intervalos)

Retornam iteradores para o menor ou maior elemento em um intervalo.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<double> temperaturas = {25.3, 18.9, 30.1, 22.5, 28.0};
    
    auto it_min = std::min_element(temperaturas.begin(), temperaturas.end());
    std::cout << "Menor temperatura: " << *it_min << std::endl; // Saída: 18.9

    auto it_max = std::max_element(temperaturas.begin(), temperaturas.end());
    std::cout << "Maior temperatura: " << *it_max << std::endl; // Saída: 30.1
    return 0;
}

5.3 minmax_element (C++11)

Retorna um par de iteradores para o menor e maior elemento em um único passo, o que é mais eficiente do que chamar min_element e max_element separadamante.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<std::string> palavras = {"zebra", "macaco", "elefante", "coelho"};

    auto par_minmax = std::minmax_element(palavras.begin(), palavras.end());
    std::cout << "Menor palavra (lexicograficamente): " << *par_minmax.first << std::endl; // Saída: coelho
    std::cout << "Maior palavra (lexicograficamente): " << *par_minmax.second << std::endl; // Saída: zebra
    return 0;
}

  1. Algoritmos Numéricos (em <numeric>)

Para operações matemáticas em sequências de números.

6.1 Acumulação (accumulate)

Calcula a soma (ou outra operação binária) de todos os elementos em um intervalo, começando com um valor inicial.

#include <vector>
#include <numeric> // Para std::accumulate
#include <iostream>
#include <functional> // Para std::multiplies

int main() {
    std::vector<int> notas = {85, 90, 78, 92, 88};
    
    int soma_total = std::accumulate(notas.begin(), notas.end(), 0); // Soma, começando com 0
    std::cout << "Soma das notas: " << soma_total << std::endl; // Saída: 433

    std::vector<double> fatores = {1.0, 1.5, 2.0, 0.5};
    // Produto, começando com 1.0, usando std::multiplies
    double produto_total = std::accumulate(fatores.begin(), fatores.end(), 1.0, std::multiplies<double>());
    std::cout << "Produto dos fatores: " << produto_total << std::endl; // Saída: 1.0 * 1.5 * 2.0 * 0.5 = 1.5
    return 0;
}

6.2 Produto Interno (inner_product)

Calcula o produto interno (ou outra operação) de dois intervalos.

#include <vector>
#include <numeric>
#include <iostream>

int main() {
    std::vector<int> vetor_a = {1, 2, 3};
    std::vector<int> vetor_b = {10, 100, 1000};
    
    // Produto interno: (1*10) + (2*100) + (3*1000) = 10 + 200 + 3000 = 3210
    int resultado = std::inner_product(vetor_a.begin(), vetor_a.end(), vetor_b.begin(), 0);
    std::cout << "Produto interno: " << resultado << std::endl; // Saída: 3210
    return 0;
}

6.3 Preenchimento Sequencial (iota)

Preenche um intervalo com valores sequenciais crescentes, começando de um valor inicial.

#include <vector>
#include <numeric>
#include <iostream>

int main() {
    std::vector<int> serie(7);
    std::iota(serie.begin(), serie.end(), 100); // Preenche com 100, 101, ..., 106
    std::cout << "Série gerada por iota: ";
    for (int n : serie) std::cout << n << " "; // Saída: 100 101 102 103 104 105 106
    std::cout << std::endl;
    return 0;
}

6.4 Soma Parcial (partial_sum)

Calcula as somas parciais dos elementos de um intervalo e as armazena em um intervalo de destino.

#include <vector>
#include <numeric>
#include <iostream>

int main() {
    std::vector<int> valores_entrada = {1, 2, 3, 4, 5};
    std::vector<int> somas_parciais(valores_entrada.size());

    // 1, (1+2)=3, (1+2+3)=6, ...
    std::partial_sum(valores_entrada.begin(), valores_entrada.end(), somas_parciais.begin());
    std::cout << "Somas parciais: ";
    for (int n : somas_parciais) std::cout << n << " "; // Saída: 1 3 6 10 15
    std::cout << std::endl;
    return 0;
}

6.5 Diferença Adjacente (adjacent_difference)

Calcula as diferenças entre elementos adjacentes de um intervalo, armazenando-as em um intervalo de destino. O primeiro elemento do destino é o mesmo do original.

#include <vector>
#include <numeric>
#include <iostream>

int main() {
    std::vector<int> dados_serie = {10, 12, 15, 11, 14};
    std::vector<int> diferencas_adj(dados_serie.size());

    // Primeiro elemento: 10
    // Diferenças: (12-10)=2, (15-12)=3, (11-15)=-4, (14-11)=3
    std::adjacent_difference(dados_serie.begin(), dados_serie.end(), diferencas_adj.begin());
    std::cout << "Diferenças adjacentes: ";
    for (int n : diferencas_adj) std::cout << n << " "; // Saída: 10 2 3 -4 3
    std::cout << std::endl;
    return 0;
}

  1. Outros Algoritmos Úteis

7.1 Geração de Elementos (generate)

Preenche um intervalo com valores gerados por uma função fornecida (gerador).

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

int main() {
    std::vector<std::string> tarefas(5);
    int tarefa_id = 1;
    // Preenche com "Tarefa 1", "Tarefa 2", ...
    std::generate(tarefas.begin(), tarefas.end(), [&tarefa_id]() {
        return "Tarefa " + std::to_string(tarefa_id++);
    });
    std::cout << "Tarefas geradas: ";
    for (const std::string& s : tarefas) std::cout << s << ", ";
    std::cout << std::endl; // Saída: Tarefa 1, Tarefa 2, Tarefa 3, Tarefa 4, Tarefa 5, 
    return 0;
}

7.2 Geração de N Elementos (generate_n)

Preenche os primeiros n elementos de um intervalo com valores gerados por uma função.

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numeros(10, 0); // Vetor de 10 zeros
    int sequencia = 100;
    // Gerar 5 números na sequência, começando do índice 0
    std::generate_n(numeros.begin(), 5, [&sequencia]() {
        return sequencia++;
    });
    std::cout << "Numeros após generate_n: ";
    for (int n : numeros) std::cout << n << " "; // Saída: 100 101 102 103 104 0 0 0 0 0
    std::cout << std::endl;
    return 0;
}

7.3 Verificação de Inclusão (includes)

Verifica se um intervalo **ordenado** contém todos os elementos de outro intervalo **ordenado**.

#include <vector>
#include <algorithm>
#include <iostream>
#include <iomanip>

int main() {
    std::vector<int> conjunto_principal = {10, 20, 30, 40, 50, 60};
    std::vector<int> subconjunto_a = {20, 40, 60}; // Contido
    std::vector<int> subconjunto_b = {20, 70}; // Não contido

    bool contem_a = std::includes(conjunto_principal.begin(), conjunto_principal.end(),
                                   subconjunto_a.begin(), subconjunto_a.end());
    std::cout << "Conjunto principal inclui subconjunto A? " << std::boolalpha << contem_a << std::endl; // Saída: true

    bool contem_b = std::includes(conjunto_principal.begin(), conjunto_principal.end(),
                                   subconjunto_b.begin(), subconjunto_b.end());
    std::cout << "Conjunto principal inclui subconjunto B? " << std::boolalpha << contem_b << std::endl; // Saída: false
    return 0;
}

7.4 Operações de Conjuntos (set_union, set_intersection, set_difference, set_symmetric_difference)

Esses algoritmos realizam operações de teoria dos conjuntos em dois intervalos **ordenados**, armazenando o resultado em um terceiro intervalo de saída.

#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator> // Para std::back_inserter

void print_vector(const std::string& label, const std::vector<int>& v) {
    std::cout << label;
    for (int n : v) std::cout << n << " ";
    std::cout << std::endl;
}

int main() {
    std::vector<int> set1 = {1, 2, 3, 6, 7};
    std::vector<int> set2 = {3, 4, 5, 6};
    std::vector<int> result;

    print_vector("Set1: ", set1);
    print_vector("Set2: ", set2);

    // União: elementos que estão em set1 OU set2
    std::set_union(set1.begin(), set1.end(), set2.begin(), set2.end(),
                   std::back_inserter(result));
    print_vector("União: ", result); // Saída: 1 2 3 4 5 6 7

    // Interseção: elementos que estão em set1 E set2
    result.clear();
    std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),
                          std::back_inserter(result));
    print_vector("Interseção: ", result); // Saída: 3 6

    // Diferença (Set1 - Set2): elementos em set1 mas NÃO em set2
    result.clear();
    std::set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(),
                        std::back_inserter(result));
    print_vector("Diferença (Set1 - Set2): ", result); // Saída: 1 2 7

    // Diferença Simétrica: elementos que estão em set1 OU set2, mas NÃO em AMBOS
    result.clear();
    std::set_symmetric_difference(set1.begin(), set1.end(), set2.begin(), set2.end(),
                                  std::back_inserter(result));
    print_vector("Diferença Simétrica: ", result); // Saída: 1 2 4 5 7
    return 0;
}

  1. Perguntas Frequentes (FAQ)

1. Qual a diferença entre std::sort e std::stable_sort?

  • std::sort: Geralmente implementado como Introsort (uma combinação de quicksort, heapsort e insertion sort). Possui complexidade de tempo média O(N log N) e é instável, o que significa que a ordem relativa de elementos com valores iguais pode não ser mantida após a ordenação.
  • std::stable_sort: Frequentemente implementado como Mergesort. Também possui complexidade de tempo O(N log N), mas garante a estabilidade, ou seja, elementos com valores iguais manterão sua ordem relativa original. A custo, pode ter um consumo de memória ligeiramente maior.

2. Por que o algoritmo std::remove precisa ser usado em conjunto com erase()?

Os algoritmos como std::remove e std::remove_if são projetados para funcionar em qualquer tipo de iterador, inclusive os que não permitem a modificação do tamanho do contêiner (como iteradores de array ou listas encadeadas sem métodos de redimensionamento eficiente). Eles operam "logicamente" movendo os elementos a serem mantiods para o início do intervalo e retornando um iterador para o ponto onde o "novo final" da sequência efetivamente terminaria. Os elementos a serem removidos ficam no final, mas o tamanho do contêiner não é alterado. Para realizar uma remoção "física" e redimensionar o contêiner (especialmente para std::vector), é necessário chamar o método erase() do próprio contêiner, passando o iterador retornado por remove/remove_if até o end() original do contêiner: container.erase(std::remove(...), container.end());.

3. Quais algoritmos exigem que o contêiner esteja ordenado?

Vários algoritmos dependem da propriedade de ordenação para funcionar corretamente ou de forma eficiente. Isso inclui:

  • Algoritmos de busca binária: std::binary_search, std::lower_bound, std::upper_bound.
  • Algoritmos de conjunto: std::set_union, std::set_intersection, std::set_difference, std::set_symmetric_difference, std::includes.
  • Algoritmo de fusão: std::merge.
  • Algoritmo std::unique só remove duplicatas consecutivas; para remover todas as duplicatas em um contêiner não ordenado, você precisaria primeiro ordená-lo.

O uso desses algoritmos em intervalos não ordenados pode levar a resultados incorretos ou comportamento indefinido.

Tags: C++ STL Algoritmos StandardLibrary iteradores

Publicado em 7-26 09:03