Explorando os Algoritmos da Standard Template Library em C++

Estas rotinas examinam os dados sem alterar o estado original dos contêineres.

1.1. Localização de Elementos

As funções de busca permitem encontrar itens específicos ou que atendam a certas condições.

  • std::find: Localiza a primeira ocorrência de um valor exato.
  • std::find_if: Localiza o primeiro elemento que satisfaça uma condição (função booleana).
  • std::find_end: Identifica a última ocorrência de uma subsequência.
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> pontuacoes = {10, 25, 30, 45, 50};

    // Buscar um valor exato
    auto alvo = std::find(pontuacoes.begin(), pontuacoes.end(), 30);
    if (alvo != pontuacoes.end()) {
        std::cout << "Valor exato encontrado: " << *alvo << "\n";
    }

    // Buscar com condição
    auto maior_que_40 = std::find_if(pontuacoes.begin(), pontuacoes.end(), [](int p) {
        return p > 40;
    });
    std::cout << "Primeiro valor > 40: " << *maior_que_40 << "\n";

    // Buscar subsequência
    std::vector<int> subseq = {25, 30};
    auto fim_subseq = std::find_end(pontuacoes.begin(), pontuacoes.end(), subseq.begin(), subseq.end());
    if (fim_subseq != pontuacoes.end()) {
        std::cout << "Índice da subsequência: " << std::distance(pontuacoes.begin(), fim_subseq) << "\n";
    }
    return 0;
}

1.2. Contagem e Validação

Permitem quantificar elementos ou verificar propriedades lógicas sobre o intervalo.

  • std::count e std::count_if: Contam ocorrências de um valor ou de elementos que passem em um teste.
  • std::all_of, std::any_of, std::none_of: Verificam se todos, algum ou nenhum elemento atende a um predicado.
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> idades = {20, 22, 19, 25, 21};

    int maiores_de_20 = std::count_if(idades.begin(), idades.end(), [](int i) { return i > 20; });
    std::cout << "Pessoas com mais de 20 anos: " << maiores_de_20 << "\n";

    bool todos_maiores_de_idade = std::all_of(idades.begin(), idades.end(), [](int i) { return i >= 18; });
    std::cout << "Todos maiores de idade? " << (todos_maiores_de_idade ? "Sim" : "Nao") << "\n";

    bool algum_idoso = std::any_of(idades.begin(), idades.end(), [](int i) { return i > 60; });
    std::cout << "Há alguém com mais de 60? " << (algum_idoso ? "Sim" : "Nao") << "\n";

    return 0;
}

1.3. Comparação de Intervalos

Utilizadas para verificar igualdade ou encontrar divergências entre duas sequências.

  • std::equal: Compara se dois intervalos possuem os mesmos elementos.
  • std::mismatch: Retorna um par de iteradores apontando para o primeiro elemento diferente.
#include <vector>
#include <algorithm>
#include <iostream>

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

    bool iguais = std::equal(senha1.begin(), senha1.end(), senha2.begin());
    std::cout << "Senhas iguais? " << std::boolalpha << iguais << "\n";

    auto diff = std::mismatch(senha1.begin(), senha1.end(), senha2.begin());
    if (diff.first != senha1.end()) {
        std::cout << "Divergência: '" << *diff.first << "' vs '" << *diff.second << "'\n";
    }
    return 0;
}

  1. Operações de Mutação de Sequências

Estas funções alteram o conteúdo ou a ordem dos elementos nos contêineres de origem ou destino.

2.1. Cópia e Transformação

Perimtem transferir dados e aplicar operações matemáticas ou lógicas durante o processo.

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

int main() {
    std::vector<double> precos = {10.5, 20.0, 15.75, 30.2};
    std::vector<double> precos_com_desconto;

    // Copiar apenas valores maiores que 15
    std::copy_if(precos.begin(), precos.end(), std::back_inserter(precos_com_desconto), 
                 [](double p) { return p > 15.0; });

    // Transformar aplicando um desconto de 10%
    std::vector<double> precos_finais(precos_com_desconto.size());
    std::transform(precos_com_desconto.begin(), precos_com_desconto.end(), precos_finais.begin(),
                   [](double p) { return p * 0.9; });

    for(double p : precos_finais) std::cout << p << " ";
    return 0;
}

2.2. Substituição e Remoção

Alteram valores específicos ou removem elementos com base em critérios.

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> registros = {1, 2, 0, 4, 0, 5};

    // Substituir 0 por -1
    std::replace(registros.begin(), registros.end(), 0, -1);

    // Remover valores negativos (lógica de remoção)
    auto novo_fim = std::remove_if(registros.begin(), registros.end(), [](int r) { return r < 0; });
    registros.erase(novo_fim, registros.end()); // Remoção física (Erase-Remove Idiom)

    // Remover duplicatas consecutivas
    std::vector<int> dados = {1, 1, 2, 2, 2, 3};
    auto ultimo = std::unique(dados.begin(), dados.end());
    dados.erase(ultimo, dados.end());

    return 0;
}

2.3. Reorganização

Alteram a disposição dos elementos sem necessariamente mudar seus valores.

#include <vector>
#include <algorithm>
#include <random>

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

    // Inverter a ordem
    std::reverse(fila.begin(), fila.end());

    // Rotacionar para que o terceiro elemento seja o primeiro
    std::rotate(fila.begin(), fila.begin() + 2, fila.end());

    // Embaralhar aleatoriamente
    std::random_device rd;
    std::mt19937 gen(rd());
    std::shuffle(fila.begin(), fila.end(), gen);

    return 0;
}

  1. Ordenação e Busca em Dados Ordenados

3.1. Algoritmos de Ordenação

Organizam os elementos com base em critérios de comparação.

  • std::sort: Ordenação rápida (não estável).
  • std::stable_sort: Mantém a ordem relativa de elementos equivalentes.
  • std::partial_sort: Ordena apenas os primeiros N elementos.
  • std::nth_element: Posiciona o N-ésimo elemanto corretamente, particionando o restante.
#include <vector>
#include <algorithm>
#include <string>

struct Produto {
    std::string nome;
    float preco;
};

int main() {
    std::vector<Produto> catalogo = {
        {"Teclado", 150.0f}, {"Mouse", 50.0f}, {"Monitor", 800.0f}, {"Webcam", 120.0f}
    };

    // Ordenar por preço (crescente)
    std::sort(catalogo.begin(), catalogo.end(), [](const Produto& a, const Produto& b) {
        return a.preco < b.preco;
    });

    // Ordenação estável por nome
    std::stable_sort(catalogo.begin(), catalogo.end(), [](const Produto& a, const Produto& b) {
        return a.nome < b.nome;
    });

    // Colocar os 2 mais baratos no início
    std::partial_sort(catalogo.begin(), catalogo.begin() + 2, catalogo.end(), [](const Produto& a, const Produto& b) {
        return a.preco < b.preco;
    });

    return 0;
}

3.2. Busca Binária e Fusão

Exigem que o contêiner esteja previamente ordenado.

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

int main() {
    std::vector<int> ids_ordenados = {10, 20, 30, 40, 50};

    bool existe = std::binary_search(ids_ordenados.begin(), ids_ordenados.end(), 30);
    
    auto limite_inf = std::lower_bound(ids_ordenados.begin(), ids_ordenados.end(), 25);
    auto limite_sup = std::upper_bound(ids_ordenados.begin(), ids_ordenados.end(), 30);

    std::vector<int> lista_a = {1, 3, 5};
    std::vector<int> lista_b = {2, 4, 6};
    std::vector<int> combinada(lista_a.size() + lista_b.size());
    
    std::merge(lista_a.begin(), lista_a.end(), lista_b.begin(), lista_b.end(), combinada.begin());

    return 0;
}

  1. Estruturas de Heap e Extremos

4.1. Manipulação de Heaps

Transformam um contêiner em uma estrutura de heap (árvore binária completa), útil para filas de prioridade.

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> tarefas = {3, 1, 4, 1, 5, 9};

    std::make_heap(tarefas.begin(), tarefas.end()); // Cria um max-heap

    tarefas.push_back(10);
    std::push_heap(tarefas.begin(), tarefas.end()); // Adiciona e reorganiza

    std::pop_heap(tarefas.begin(), tarefas.end()); // Move o maior para o final
    tarefas.pop_back(); // Remove o maior

    std::sort_heap(tarefas.begin(), tarefas.end()); // Ordena o heap

    return 0;
}

4.2. Encontrar Mínimos e Máximos

Localizam os valores extremos em um intervalo.

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

int main() {
    std::vector<int> temperaturas = {15, 22, 9, 30, 12};

    auto minima = std::min_element(temperaturas.begin(), temperaturas.end());
    auto maxima = std::max_element(temperaturas.begin(), temperaturas.end());
    
    auto extremos = std::minmax_element(temperaturas.begin(), temperaturas.end());
    std::cout << "Min: " << *extremos.first << ", Max: " << *extremos.second << "\n";

    return 0;
}

  1. Operações Numéricas e Conjuntos

Localizadas no cabeçalho <numeric> e algoritmos de teoria dos conjuntos.

5.1. Cálculos e Preenchimento

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

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

    // Soma acumulada
    int total = std::accumulate(valores.begin(), valores.end(), 0);

    // Produto acumulado
    int produto = std::accumulate(valores.begin(), valores.end(), 1, std::multiplies<int>());

    // Preencher com sequência
    std::vector<int> sequencia(5);
    std::iota(sequencia.begin(), sequencia.end(), 100); // 100, 101, 102...

    // Somas parciais
    std::vector<int> somas_parciais(5);
    std::partial_sum(valores.begin(), valores.end(), somas_parciais.begin());

    // Diferenças adjacentes
    std::vector<int> diffs(5);
    std::adjacent_difference(valores.begin(), valores.end(), diffs.begin());

    return 0;
}

5.2. Operações de Conjuntos

Requerem intervalos ordenados para realizar união, interseção e diferença.

#include <vector>
#include <algorithm>
#include <iterator>

int main() {
    std::vector<int> conjunto_a = {1, 2, 3, 4, 5};
    std::vector<int> conjunto_b = {4, 5, 6, 7, 8};
    std::vector<int> resultado;

    std::set_union(conjunto_a.begin(), conjunto_a.end(), 
                   conjunto_b.begin(), conjunto_b.end(), 
                   std::back_inserter(resultado));

    resultado.clear();
    std::set_intersection(conjunto_a.begin(), conjunto_a.end(), 
                          conjunto_b.begin(), conjunto_b.end(), 
                          std::back_inserter(resultado));

    resultado.clear();
    std::set_difference(conjunto_a.begin(), conjunto_a.end(), 
                        conjunto_b.begin(), conjunto_b.end(), 
                        std::back_inserter(resultado));

    return 0;
}

  1. Perguntas Frequantes

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

O std::sort utiliza uma variante do QuickSort (Introsort) e não garante a preservação da ordem relativa de elementos com chaves iguais. O std::stable_sort baseia-se no MergeSort, garantindo estabilidade, mas geralmente consome mais memória.

Por que o std::remove não diminui o tamanho do contêiner?

O algoritmo std::remove apenas desloca os elementos não removidos para o início do contêiner, retornando um novo iterador de fim lógico. Para alterar o tamanho físico e liberar memória, é obrigatório chamar o método erase do contêiner com o iterador retornado e o fim original (Idioma Erase-Remove).

Quais algoritmos exigem dados ordenados?

Algoritmos de busca binária (binary_search, lower_bound, upper_bound), fusão (merge) e operações de conjuntos (set_union, set_intersection, etc.) dependem da ordenação prévia para funcionar corretamente e manter sua complexidade de tempo otimizada.

Tags: C++ STL Standard Template Library Algoritmos C++ Programação Genérica

Publicado em 6-12 22:31 por Thomas