Explorando Algoritmos da STL em C++: Uma Visão Abrangente

Estes algoritmos examinam elementos em contêineres sem alterar seu conteúdo.

1.1. Pesquisa: find e find_if

  • find(inicio, fim, valor): Retorna um iterador para a primeira ocorrência de valor no intervalo [inicio, fim). Se não encontrado, retorna fim.
  • find_if(inicio, fim, predicado): Retorna um iterador para o primeiro elemento que satisfaz a condição definida por predicado.
  • find_end(b1, e1, sub_b, sub_e): Busca a última ocorrência de uma sub-sequência definida por [sub_b, sub_e) dentro de [b1, e1).

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

int main() {
   std::vector<int> numeros = {10, 30, 50, 70, 90};

   // Encontrar o valor 50
   auto it_valor = std::find(numeros.begin(), numeros.end(), 50);
   if (it_valor != numeros.end()) {
       std::cout << "Valor encontrado: " << *it_valor << std::endl; // Saída: 50
   }

   // Encontrar o primeiro elemento maior que 60
   auto it_condicao = std::find_if(numeros.begin(), numeros.end(), [](int x) {
       return x > 60;
   });
   if (it_condicao != numeros.end()) {
       std::cout << "Primeiro elemento > 60: " << *it_condicao << std::endl; // Saída: 70
   }

   // Encontrar uma sub-sequência
   std::vector<int> sub = {30, 50};
   auto it_sub = std::find_end(numeros.begin(), numeros.end(), sub.begin(), sub.end());
   if (it_sub != numeros.end()) {
       std::cout << "Sub-sequência começa no índice: " << (it_sub - numeros.begin()) << std::endl; // Saída: 1
   }

   return 0;
}
   

1.2. Contagem: count e count_if

  • count(inicio, fim, valor): Retorna o número de elementos iguais a valor no intervalo [inicio, fim).
  • count_if(inicio, fim, predicado): Retorna o número de elementos que satisfazem a condição predicado.

#include <vector>
#include <algorithm>
#include <numeric> // Necessário para std::count e std::count_if em algumas implementações

std::vector<int> dados = {1, 2, 3, 2, 4, 2, 5};
int contagem_dois = std::count(dados.begin(), dados.end(), 2); // Resultado: 3
int contagem_impares = std::count_if(dados.begin(), dados.end(), [](int x) {
   return x % 2 != 0;
}); // Resultado: 4 (1, 3, 5)
   

1.3. Iteração: for_each

Aplica uma função a cada elemento do intervalo [inicio, fim).


#include <vector>
#include <algorithm>

std::vector<int> valores = {1, 2, 3, 4, 5};
std::for_each(valores.begin(), valores.end(), [](int& elem) {
   elem *= 3; // Multiplica cada elemento por 3
});
// valores agora é {3, 6, 9, 12, 15}
   

1.4. Comparação: equal e mismatch

  • equal(b1, e1, b2): Verifica se os elementos no intervalo [b1, e1) são iguais aos elementos a partir de b2.
  • mismatch(b1, e1, b2): Retorna um par de iteradores apontando para o primeiro par de elementos diferentes nos dois intervalos.

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

std::vector<int> lista_a = {10, 20, 30};
std::vector<int> lista_b = {10, 20, 40};
std::vector<int> lista_c = {10, 20, 30, 40};

// Comparar lista_a com os primeiros 3 elementos de lista_c
bool sao_iguais = std::equal(lista_a.begin(), lista_a.end(), lista_c.begin());
// sao_iguais será true

// Encontrar a primeira diferença entre lista_a e lista_b
auto diferenca = std::mismatch(lista_a.begin(), lista_a.end(), lista_b.begin());
if (diferenca.first != lista_a.end()) {
   std::cout << "Primeira diferença: " << *diferenca.first << " vs " << *diferenca.second << std::endl; // Saída: 30 vs 40
}
   

1.5. Verificação Condicional: all_of, any_of, none_of

  • all_of(inicio, fim, predicado): Retorna true se todos os elementos satisfazem predicado.
  • any_of(inicio, fim, predicado): Retorna true se pelo menos um elemento satisfaz predicado.
  • none_of(inicio, fim, predicado): Retorna true se nenhum elemento satisfaz predicado.

#include <vector>
#include <algorithm>

std::vector<int> numeros = {2, 4, 6, 8};
bool todos_pares = std::all_of(numeros.begin(), numeros.end(), [](int x) { return x % 2 == 0; }); // true
bool algum_impar = std::any_of(numeros.begin(), numeros.end(), [](int x) { return x % 2 != 0; }); // false
bool nenhum_negativo = std::none_of(numeros.begin(), numeros.end(), [](int x) { return x < 0; }); // true
   

Estes algoritmos alteram o conteúdo dos contêineres.

2.1. Cópia: copy e copy_if

  • copy(inicio_orig, fim_orig, inicio_dest): Copia elementos do intervalo de origem para o destino. O destino deve ter espaço suficiente.
  • copy_if(inicio_orig, fim_orig, inicio_dest, predicado): Copia apenas os elementos que satisfazem predicado.

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

std::vector<int> fonte = {1, 2, 3, 4, 5, 6};
std::vector<int> destino(fonte.size());

// Copiar todos os elementos
std::copy(fonte.begin(), fonte.end(), destino.begin());
// destino agora é {1, 2, 3, 4, 5, 6}

// Copiar apenas os elementos pares para um novo vetor
std::vector<int> pares;
std::copy_if(fonte.begin(), fonte.end(), std::back_inserter(pares), [](int x) {
   return x % 2 == 0;
});
// pares agora é {2, 4, 6}
// std::back_inserter é útil pois gerencia o redimensionamento do contêiner destino.
   

2.2. Transformação: transform

Aplica uma operação a cada elemento e armazena o resultado em outro local.


#include <vector>
#include <algorithm>

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

// Calcula o quadrado de cada elemento
std::transform(numeros.begin(), numeros.end(), quadrados.begin(), [](int x) {
   return x * x;
});
// quadrados agora é {1, 4, 9, 16}

// Soma elementos de dois vetores
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
std::vector<int> soma(v1.size());
std::transform(v1.begin(), v1.end(), v2.begin(), soma.begin(), [](int x, int y) {
   return x + y;
});
// soma agora é {5, 7, 9}
   

2.3. Substituição: replace, replace_if, replace_copy

  • replace(inicio, fim, antigo, novo): Substitui todas as ocorrências de antigo por novo.
  • replace_if(inicio, fim, predicado, novo): Substitui elementos que satisfazem predicado por novo.
  • replace_copy(inicio_orig, fim_orig, inicio_dest, antigo, novo): Copia elementos, substituindo antigo por novo, sem modificar o original.

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

std::vector<int> dados = {1, 5, 2, 5, 3, 5};

// Substituir todos os 5 por 50
std::replace(dados.begin(), dados.end(), 5, 50);
// dados agora é {1, 50, 2, 50, 3, 50}

// Substituir elementos maiores que 10 por 0
std::vector<int> mais_dados = {5, 12, 3, 15, 8};
std::replace_if(mais_dados.begin(), mais_dados.end(), [](int x) {
   return x > 10;
}, 0);
// mais_dados agora é {5, 0, 3, 0, 8}

// Copiar e substituir 3 por 300
std::vector<int> copia_substituida;
std::replace_copy(dados.begin(), dados.end(), std::back_inserter(copia_substituida), 50, 300);
// copia_substituida agora é {1, 300, 2, 300, 3, 300}
   

2.4. Remoção Lógica: remove, remove_if (e erase)

Estes algoritmos movem os elementos a serem "removidos" para o final do intervalo e retornam um iterador para o novo fim lógico. Eles não alteram o tamanho do contêiner; erase deve ser usado para a remoção física.

  • remove(inicio, fim, valor): Move todos os elementos iguais a valor para o final.
  • remove_if(inicio, fim, predicado): Move elementos que satisfazem predicado para o final.

#include <vector>
#include <algorithm>

std::vector<int> sequencia = {1, 2, 3, 2, 4, 2, 5};

// Mover todos os 2s para o final
auto novo_fim_logico = std::remove(sequencia.begin(), sequencia.end(), 2);
// sequencia agora é {1, 3, 4, 5, 2, 2, 2} (o conteúdo após novo_fim_logico é indefinido)

// Remover fisicamente os elementos "removidos"
sequencia.erase(novo_fim_logico, sequencia.end());
// sequencia agora é {1, 3, 4, 5}

// Combinação comum: remover elementos ímpares
sequencia = {1, 2, 3, 4, 5, 6};
sequencia.erase(std::remove_if(sequencia.begin(), sequencia.end(), [](int x) {
   return x % 2 != 0; // Remover ímpares
}), sequencia.end());
// sequencia agora é {2, 4, 6}
   

2.5. Remoção de Duplicatas Contíguas: unique

Remove elementos duplicados adjacentes. Requer que o contêiner esteja ordenado para remover todas as duplicatas. Funciona de forma semelhante a remove, exigindo erase para redimensionamento.


#include <vector>
#include <algorithm>

std::vector<int> repetidos = {1, 1, 2, 3, 3, 3, 4, 4, 5};
auto fim_unico = std::unique(repetidos.begin(), repetidos.end());
repetidos.erase(fim_unico, repetidos.end());
// repetidos agora é {1, 2, 3, 4, 5}
   

2.6. Inversão: reverse

Inverte a ordem dos elementos no intervalo [inicio, fim).


#include <vector>
#include <algorithm>

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

2.7. Rotação: rotate

Gira os elementos de forma que o elemento apontado pelo segundo iterador se torne o primeiro.


#include <vector>
#include <algorithm>

std::vector<int> dados = {1, 2, 3, 4, 5};
// Rotaciona para que 3 seja o primeiro elemento
std::rotate(dados.begin(), dados.begin() + 2, dados.end());
// dados agora é {3, 4, 5, 1, 2}
   

2.8. Embaralhamento: shuffle (C++11+)

Reordena aleatoriamente os elementos do intervalo usando um gerador de números aleatórios.


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

std::vector<int> elementos = {1, 2, 3, 4, 5};
std::random_device rd; // Fonte de sementes aleatórias
std::mt19937 gerador(rd()); // Gerador Mersenne Twister

std::shuffle(elementos.begin(), elementos.end(), gerador);
// elementos agora contém uma permutação aleatória de {1, 2, 3, 4, 5}
   

3.1. Ordenação: sort, stable_sort, partial_sort

  • sort(inicio, fim): Ordena o intervalo. Geralmente usa Introsort (uma combinação de QuickSort, HeapSort e InsertionSort), com complexidade média O(N log N). Não garante estabilidade.
  • stable_sort(inicio, fim): Garante que a ordem relativa de elementos iguais seja preservada. Geralmente usa MergeSort, com complexidade O(N log N) e potencialmente maior uso de memória.
  • partial_sort(inicio, meio, fim): Ordena apenas os primeiros (meio - inicio) elementos, colocando os menores do intervalo completo nessas posições.

#include <vector>
#include <algorithm>
#include <functional> // Para std::greater

std::vector<int> numeros = {5, 2, 8, 1, 9, 4};

// Ordenação ascendente padrão
std::sort(numeros.begin(), numeros.end());
// numeros agora é {1, 2, 4, 5, 8, 9}

// Ordenação descendente
std::sort(numeros.begin(), numeros.end(), std::greater<int>());
// numeros agora é {9, 8, 5, 4, 2, 1}

// Ordenação estável (útil com objetos complexos)
std::vector<std::pair<int, char>> pares = {{2, 'b'}, {1, 'a'}, {2, 'c'}, {1, 'd'}};
std::stable_sort(pares.begin(), pares.end(), [](const auto& p1, const auto& p2) {
   return p1.first < p2.first; // Ordena pelo primeiro elemento, mantendo a ordem original para chaves iguais
});
// pares agora é {{1, 'a'}, {1, 'd'}, {2, 'b'}, {2, 'c'}}

// Ordenação parcial
std::vector<int> nums_parcial = {8, 3, 1, 9, 4, 2, 7};
// Coloca os 3 menores elementos ordenados no início
std::partial_sort(nums_parcial.begin(), nums_parcial.begin() + 3, nums_parcial.end());
// nums_parcial agora é {1, 2, 3, ...} (os 3 primeiros estão ordenados, o resto não está garantido)
   

3.2. Posição N-ésima: nth_element

Reorganiza o intervalo de forma que o elemento na posição n seja o que estaria lá se o intervalo estivesse totalmente ordenado. Todos os elementos antes de n são menores ou iguais, e todos os elementos depois são maiores ou iguais.


#include <vector>
#include <algorithm>

std::vector<int> dados = {7, 2, 9, 1, 5, 3, 8};
// Encontrar o 4º menor elemento (índice 3)
std::nth_element(dados.begin(), dados.begin() + 3, dados.end());
// O elemento em dados[3] é agora 5.
// Os elementos em dados[0..2] são <= 5.
// Os elementos em dados[4..6] são >= 5.
// A ordem dentro das partições não é garantida.
   

3.3. Busca Binária (em intervalos ordenados): binary_search, lower_bound, upper_bound

Estes algoritmos operam eficientemente em contêineres já ordenados.

  • binary_search(inicio, fim, valor): Retorna true se valor existe no intervalo.
  • lower_bound(inicio, fim, valor): Retorna um iterador para o primeiro elemento que não é menor que valor.
  • upper_bound(inicio, fim, valor): Retorna um iterador para o primeiro elemento que é maior que valor.

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

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

// Verificar se 20 existe
bool existe_20 = std::binary_search(ordenado.begin(), ordenado.end(), 20); // true

// Encontrar o primeiro elemento >= 20
auto it_lower = std::lower_bound(ordenado.begin(), ordenado.end(), 20);
// it_lower aponta para o primeiro 20 (índice 1)

// Encontrar o primeiro elemento > 20
auto it_upper = std::upper_bound(ordenado.begin(), ordenado.end(), 20);
// it_upper aponta para o 30 (índice 3)

// Contar ocorrências de 20: std::distance(it_lower, it_upper) = 2
   

3.4. Mesclagem: merge

Combina dois intervlaos ordenados em um único intervalo ordenado de destino. O destino deve ter espaço suficiente.


#include <vector>
#include <algorithm>

std::vector<int> parte1 = {1, 3, 5};
std::vector<int> parte2 = {2, 4, 6};
std::vector<int> resultado(parte1.size() + parte2.size());

std::merge(parte1.begin(), parte1.end(), parte2.begin(), parte2.end(), resultado.begin());
// resultado agora é {1, 2, 3, 4, 5, 6}
   

A STL fornece algoritmos para gerenciar dados como um heap (geralmente um max-heap por padrão).

  • make_heap(inicio, fim): Constrói um heap a partir do intervalo.
  • push_heap(inicio, fim): Adiciona o último elemento (assumido como não pertencente ao heap) ao heap.
  • pop_heap(inicio, fim): Move o elemento do topo do heap para o final do intervalo e reorganiza o restante como um heap.
  • sort_heap(inicio, fim): Transforma um heap em uma sequência ordanada.

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

std::vector<int> dados_heap = {3, 1, 4, 1, 5, 9, 2};
std::make_heap(dados_heap.begin(), dados_heap.end());
// dados_heap agora representa um max-heap. O maior elemento (9) está em dados_heap.front().

// Adicionar um novo elemento
dados_heap.push_back(6);
std::push_heap(dados_heap.begin(), dados_heap.end());
// O heap é atualizado. O novo maior elemento é o topo.

// Remover o maior elemento
std::pop_heap(dados_heap.begin(), dados_heap.end());
int maior = dados_heap.back(); // Pega o maior elemento que foi movido para o fim
dados_heap.pop_back(); // Remove-o do vetor
// maior é 9. dados_heap agora tem o restante como um heap.

// Ordenar o heap
std::sort_heap(dados_heap.begin(), dados_heap.end());
// dados_heap agora está ordenado em ordem ascendente.
   

5.1. Comparação Simples: min e max

Retorna o menor ou maior de dois valores ou de uma lista inicializada.


#include <algorithm>
#include <initializer_list>

int a = 10, b = 20;
int menor = std::min(a, b); // 10
int maior = std::max(a, b); // 20

int min_lista = std::min({5, 1, 8, 3}); // 1
int max_lista = std::max({5, 1, 8, 3}); // 8
   

5.2. Elemento Mínimo/Máximo: min_element e max_element

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


#include <vector>
#include <algorithm>

std::vector<int> valores = {4, 1, 7, 3, 9, 2};
auto it_min = std::min_element(valores.begin(), valores.end()); // Aponta para 1
auto it_max = std::max_element(valores.begin(), valores.end()); // Aponta para 9
   

5.3. Mínimo e Máximo Juntos: minmax_element (C++11+)

Retorna um par de iteradores para o menor e o maior elemento simultaneamente.


#include <vector>
#include <algorithm>

std::vector<int> dados = {4, 1, 7, 3, 9, 2};
auto resultado_minmax = std::minmax_element(dados.begin(), dados.end());
// resultado_minmax.first aponta para 1
// resultado_minmax.second aponta para 9
   

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> // Necessário para std::accumulate
#include <functional> // Para std::multiplies

std::vector<int> nums = {1, 2, 3, 4, 5};

// Soma dos elementos com valor inicial 0
int soma = std::accumulate(nums.begin(), nums.end(), 0); // 15

// Produto dos elementos com valor inicial 1
int produto = std::accumulate(nums.begin(), nums.end(), 1, std::multiplies<int>()); // 120
   

6.2. Produto Escalar: inner_product

Calcula o produto escalar de dois intervalos.


#include <vector>
#include <numeric>

std::vector<int> v_a = {1, 2, 3};
std::vector<int> v_b = {4, 5, 6};

// Produto escalar: (1*4) + (2*5) + (3*6)
int prod_escalar = std::inner_product(v_a.begin(), v_a.end(), v_b.begin(), 0); // 32
   

6.3. Preenchimento Sequencial: iota

Preenche um intervalo com valores sequencialmente incrementados, começando de um valor especificado.


#include <vector>
#include <numeric>

std::vector<int> sequencia(5);
std::iota(sequencia.begin(), sequencia.end(), 10); // sequencia agora é {10, 11, 12, 13, 14}
   

6.4. Soma Parcial: partial_sum

Calcula as somas parciais de um intervalo e armazena os resultados em outro. [a, b, c] -> [a, a+b, a+b+c].


#include <vector>
#include <numeric>

std::vector<int> entrada = {1, 2, 3, 4, 5};
std::vector<int> saida(entrada.size());
std::partial_sum(entrada.begin(), entrada.end(), saida.begin());
// saida agora é {1, 3, 6, 10, 15}
   

6.5. Diferença Adjacente: adjacent_difference

Calcula a diferença entre elementos adjacentes. [a, b, c] -> [a, b-a, c-b].


#include <vector>
#include <numeric>

std::vector<int> entrada = {1, 2, 4, 8, 16};
std::vector<int> saida(entrada.size());
std::adjacent_difference(entrada.begin(), entrada.end(), saida.begin());
// saida agora é {1, 1, 2, 4, 8}
   

7.1. Geração de Valores: generate

Preenche um intervalo com valores gerados por uma função.


#include <vector>
#include <algorithm>

std::vector<int> dados(5);
int contador = 0;
std::generate(dados.begin(), dados.end(), [&contador]() {
   return contador++; // Gera 0, 1, 2, 3, 4
});
   

7.2. Geração de N Valores: generate_n

Gera n valores usando uma função e os insere em um iterador de saída.


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

std::vector<int> saida(5);
int valor_inicial = 100;
// Gera 3 valores a partir de valor_inicial e os insere em 'saida'
std::generate_n(std::back_inserter(saida), 3, [&valor_inicial]() {
   return valor_inicial++;
});
// saida agora é {100, 101, 102, 0, 0} (os últimos dois permanecem não inicializados ou com seu valor padrão)
   

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>

std::vector<int> maior_conjunto = {1, 2, 3, 4, 5, 6};
std::vector<int> sub_conjunto = {2, 4};
bool contem = std::includes(maior_conjunto.begin(), maior_conjunto.end(), sub_conjunto.begin(), sub_conjunto.end()); // true
   

7.4. Operações de Conjunto (em intervalos ordenados): set_union, set_intersection, set_difference, set_symmetric_difference

Executam operações de teoria dos conjuntos em dois intervalos ordenados e gravam o resultado em um iterador de saída.


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

std::vector<int> c1 = {1, 2, 3, 4, 5};
std::vector<int> c2 = {3, 4, 5, 6, 7};
std::vector<int> res;

// União (todos os elementos únicos)
std::set_union(c1.begin(), c1.end(), c2.begin(), c2.end(), std::back_inserter(res));
// res: {1, 2, 3, 4, 5, 6, 7}

// Interseção (elementos comuns)
res.clear();
std::set_intersection(c1.begin(), c1.end(), c2.begin(), c2.end(), std::back_inserter(res));
// res: {3, 4, 5}

// Diferença (c1 - c2)
res.clear();
std::set_difference(c1.begin(), c1.end(), c2.begin(), c2.end(), std::back_inserter(res));
// res: {1, 2}

// Diferença Simétrica (elementos em um ou outro, mas não em ambos)
res.clear();
std::set_symmetric_difference(c1.begin(), c1.end(), c2.begin(), c2.end(), std::back_inserter(res));
// res: {1, 2, 6, 7}
   
  1. Diferença entre sort e stable_sort?sort é geralmente mais rápido, mas não garante a ordem relativa de elementos iguais. stable_sort preserva essa ordem, o que pode ser crucial ao ordenar objetos com base em múltiplos critérios.
  2. Por que remove precisa de erase?remove apenas reorganiza os elementos, movendo os que não devem ser removidos para o início e retornando um iterader para o novo fim "lógico". O tamanho do contêiner não muda. erase é então necessário para realmente redimensionar o contêiner, removendo os elementos indesejados do final.
  3. **Quais algoritmos exigem um contêiner ordenado?**Algoritmos de busca binária (binary_search, lower_bound, upper_bound), operações de conjunto (set_intersection, etc.) e merge dependem da ordenação para sua eficiência e correção. A ordenação permite reduzir a complexidade de busca de O(N) para O(log N).

Tags: C++ STL Algoritmos ordenacao busca

Publicado em 6-25 18:08