Explorando Algoritmos da STL para Manipulação de Sequências

  1. Algoritmos Não Modificadores de Sequência

Estes algoritmos operam sobre sequências sem alterar seus elementos.

1.1 find e find\_if

  • find(inicio, fim, valor): Busca a primeira ocorrência de valor na faixa [inicio, fim) e retorna um iterador para ela. Retorna fim se o valor não for encontrado.
  • find_if(inicio, fim, predicado): Busca o primeiro elemento que satisfaz a condição definida por predicado.
  • find_end(inicio1, fim1, inicio2, fim2): Encontra a última ocorrência de uma sub-sequência [inicio2, fim2) dentro de [inicio1, fim1).

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

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

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

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

// Procurar 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()) {
   // O índice seria (it_sub - numeros.begin()) se quiséssemos o início da correspondência
   std::cout << "Sub-sequência encontrada." << std::endl; // Saída: Sub-sequência encontrada.
}
 

1.2 count e count\_if

  • count(inicio, fim, valor): Conta quantas vezes valor aparece na faixa [inicio, fim).
  • count_if(inicio, fim, predicado): Conta quantos elementos satisfazem a condição predicado.

#include <vector>
#include <algorithm>

std::vector<int> lista = {11, 22, 33, 22, 44, 22};
int contagem_22 = std::count(lista.begin(), lista.end(), 22); // Resultado: 3
int contagem_pares = std::count_if(lista.begin(), lista.end(), [](int x) {
   return x % 2 == 0;
}); // Resultado: 3 (22, 22, 44)
 

1.3 for\_each

Aplica uma função a cada elemento em uma faixa.


#include <vector>
#include <algorithm>

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

1.4 equal e mismatch

  • equal(inicio1, fim1, inicio2): Verifica se os elementos na faixa [inicio1, fim1) são iguais aos elementos a partir de inicio2.
  • mismatch(inicio1, fim1, inicio2): Retorna um par de iteradores para o primeiro elemento que difere entre as duas faixas.

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

std::vector<int> A = {10, 20, 30};
std::vector<int> B = {10, 20, 40};
std::vector<int> C = {10, 20, 30, 40};

bool sao_iguais = std::equal(A.begin(), A.end(), B.begin()); // false
auto diferenca = std::mismatch(A.begin(), A.end(), C.begin());

if (diferenca.first != A.end()) {
   std::cout << "Primeira diferença: " << *diferenca.first
             << " vs " << *diferenca.second << std::endl; // Saída: Primeira diferença: 30 vs 30 (aqui A e C correspondem até o fim de A)
}
 

1.5 all\_of, any\_of, none\_of

Verificam se todos, algum ou nenhum elemento satisfaz uma condição.


#include <vector>
#include <algorithm>

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

Estes algoritmos alteram os elementos dentro das sequências.

2.1 copy e copy\_if

  • copy(inicio1, fim1, inicio_destino): Copia os elementos da faixa [inicio1, fim1) para a posição apontada por inicio_destino.
  • copy_if(inicio1, fim1, inicio_destino, predicado): Copia apenas os elementos que satisfazem predicado.

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

std::vector<int> origem = {11, 22, 33, 44, 55};
std::vector<int> destino(origem.size()); // Precisa ter espaço alocado

std::copy(origem.begin(), origem.end(), destino.begin()); // destino agora é {11, 22, 33, 44, 55}

std::vector<int> pares;
std::copy_if(origem.begin(), origem.end(), std::back_inserter(pares), [](int x){
   return x % 2 == 0;
}); // pares agora é {22, 44}
 

Observação: std::back_inserter cria um iterador que usa push_back, dispensando a alocação prévia de espaço.

2.2 transform

Aplica uma operação a cada elemento e armazena o resultado em outra faixa (ou na mesma).


#include <vector>
#include <algorithm>

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

// Operação unária: calcular quadrados
std::transform(numeros.begin(), numeros.end(), quadrados.begin(), [](int x) {
   return x * x;
}); // quadrados é {1, 4, 9}

// Operação binária: somar elementos de dois vetores
std::vector<int> v1 = {10, 20, 30};
std::vector<int> v2 = {1, 2, 3};
std::vector<int> soma(v1.size());
std::transform(v1.begin(), v1.end(), v2.begin(), soma.begin(), [](int a, int b) {
   return a + b;
}); // soma é {11, 22, 33}
 

2.3 replace, replace\_if e replace\_copy

  • replace(inicio, fim, antigo, novo): Susbtitui todas as ocorrências de antigo por novo.
  • replace_if(inicio, fim, predicado, novo): Substitui elementos que satisfazem predicado por novo.
  • replace_copy(inicio1, fim1, inicio_destino, antigo, novo): Copia elementos, substituindo antigo por novo durante a cópia.

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

std::vector<int> dados = {5, 15, 25, 15, 35};

// Substituir todos os 15 por 150
std::replace(dados.begin(), dados.end(), 15, 150); // dados é {5, 150, 25, 150, 35}

// Substituir elementos maiores que 100 por 0
std::replace_if(dados.begin(), dados.end(), [](int x){ return x > 100; }, 0); // dados é {5, 0, 25, 0, 35}

// Copiar e substituir 25 por 250
std::vector<int> resultado_copia;
std::replace_copy(dados.begin(), dados.end(), std::back_inserter(resultado_copia), 25, 250); // resultado_copia é {5, 0, 250, 0, 35}
 

2.4 remove, remove\_if e erase

  • remove(inicio, fim, valor): Move todos os elementos iguais a valor para o final da faixa e retorna um iterador para o novo "fim lógico". Não remove os elementos fisicamente.
  • remove_if(inicio, fim, predicado): Faz o mesmo, mas para elementos que satisfazem predicado.
  • erase(iterador_inicio, iterador_fim): Remove fisicamente os elementos no intervalo especificado.

A combinação erase(remove(...), container.end()) é idiomática para remover elementos.


#include <vector>
#include <algorithm>

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

// Remover logicamente os 2s
auto novo_fim = std::remove(numeros.begin(), numeros.end(), 2); // numeros pode ser {1, 3, 4, 2, 2, 2}
// Remover fisicamente os elementos "removidos"
numeros.erase(novo_fim, numeros.end()); // numeros agora é {1, 3, 4}

// Remover elementos pares de forma concisa
numeros = {1, 2, 3, 4, 5, 6};
numeros.erase(std::remove_if(numeros.begin(), numeros.end(), [](int x){ return x % 2 == 0; }), numeros.end()); // numeros agora é {1, 3, 5}
 

2.5 unique

Remove elementos duplicados adjacentes em uma faixa. Semelhante a remove, retorna um novo iterador de fim lógico e precisa ser combinado com erase para remoção física.


#include <vector>
#include <algorithm>

std::vector<int> sequencia = {1, 1, 2, 3, 3, 3, 4, 5, 5};
// Requer que a sequência esteja ordenada para remover todas as duplicatas, ou apenas adjacentes.
// Para remover adjacentes:
auto ultimo_unico = std::unique(sequencia.begin(), sequencia.end());
sequencia.erase(ultimo_unico, sequencia.end()); // sequencia agora é {1, 2, 3, 4, 5}
 

2.6 reverse

Inverte a ordem dos elementos em uma faixa.


#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 rotate

Gira os elementos de forma que o elemento no iterador do meio se torne o primeiro.


#include <vector>
#include <algorithm>

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

2.8 shuffle

Embaralha os elementos da faixa aleatoriamente. Requer C++11 ou superior.


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

std::vector<int> embaralhar = {1, 2, 3, 4, 5};
std::random_device rd;
std::mt19937 gerador(rd()); // Motor de randomização Mersenne Twister

std::shuffle(embaralhar.begin(), embaralhar.end(), gerador);
// embaralhar agora contém os elementos em ordem aleatória
 
  1. Algoritmos de Ordenação e Relacionados

3.1 sort, stable\_sort e partial\_sort

  • sort(inicio, fim): Ordena a faixa. É rápido (introsort), mas não garante a ordem relativa de elementos iguais (instável).
  • stable_sort(inicio, fim): Ordena a faixa mantendo a ordem relativa de elementos iguais (estável).
  • partial_sort(inicio, meio, fim): Ordena apenas os (meio - inicio) primeiros elementos, garantindo que eles sejam os menores do intervalo [inicio, fim).

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

std::vector<int> desordenado = {5, 3, 1, 4, 2};
std::sort(desordenado.begin(), desordenado.end()); // Ordena em ordem crescente: {1, 2, 3, 4, 5}
std::sort(desordenado.begin(), desordenado.end(), std::greater<int>()); // Ordena em ordem decrescente: {5, 4, 3, 2, 1}

// Exemplo de stable_sort
struct Item { int chave; std::string valor; };
std::vector<Item> itens = {{2, "a"}, {1, "b"}, {2, "c"}};
std::stable_sort(itens.begin(), itens.end(), [](const Item& a, const Item& b){
   return a.chave < b.chave; // Ordena pela chave, mantendo {2, "a"} antes de {2, "c"}
});

// Exemplo de partial_sort
std::vector<int> dados_partiais = {8, 1, 6, 2, 9, 3, 7, 4, 5};
// Garante que os 3 menores elementos estejam ordenados no início
std::partial_sort(dados_partiais.begin(), dados_partiais.begin() + 3, dados_partiais.end());
// dados_partiais agora tem os 3 menores elementos ordenados no início, ex: {1, 2, 3, ...}
 

3.2 nth\_element

Reorganiza a faixa de modo que o elemento na posição nth seja o que estaria lá se a faixa estivesse totalmente ordenada. Elementos antes de nth são menores ou iguais, e os elementos após são maiores ou iguais.


#include <vector>
#include <algorithm>

std::vector<int> dados = {5, 1, 8, 2, 9, 3, 7, 4, 6};
// Coloca o 5º menor elemento (índice 4) na sua posição correta
std::nth_element(dados.begin(), dados.begin() + 4, dados.end());
// dados[4] agora é 5. Elementos antes são <= 5, depois são >= 5.
 

3.3 binary\_search, lower\_bound, upper\_bound

Requerem que a faixa esteja ordenada.

  • binary_search(inicio, fim, valor): Verifica se valor existe na faixa ordenada. Retorna bool.
  • 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 <iostream>
#include <algorithm>

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

bool contem_20 = std::binary_search(ordenado.begin(), ordenado.end(), 20); // true

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

auto it_superior = std::upper_bound(ordenado.begin(), ordenado.end(), 20);
// it_superior aponta para o 30 (índice 3)

std::cout << "Índice lower_bound para 20: " << (it_inferior - ordenado.begin()) << std::endl; // Saída: 1
std::cout << "Índice upper_bound para 20: " << (it_superior - ordenado.begin()) << std::endl; // Saída: 3
 

3.4 merge

Mescla dois intervalos odrenados em um único intervalo ordenado.


#include <vector>
#include <algorithm>

std::vector<int> v1 = {1, 3, 5};
std::vector<int> v2 = {2, 4, 6};
std::vector<int> resultado_merge(v1.size() + v2.size());

std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), resultado_merge.begin());
// resultado_merge agora é {1, 2, 3, 4, 5, 6}
 
  1. Algoritmos de Heap

A STL fornece algoritmos para manipular sequências como heaps (geralmente heaps binários máximos por padrão).

  • make_heap: Transforma uma faixa em um heap.
  • push_heap: Adiciona um novo elemento ao heap (deve estar no final da faixa).
  • pop_heap: Move o maior elemento (raiz do heap) para o final da faixa e reorganiza o restante como um heap.
  • sort_heap: Ordena um heap.

#include <vector>
#include <algorithm>

std::vector<int> dados_heap = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
std::make_heap(dados_heap.begin(), dados_heap.end());
// dados_heap agora representa um heap máximo, ex: {16, 14, 10, 8, 7, 9, 3, 2, 4, 1}

dados_heap.push_back(15); // Adiciona um novo elemento
std::push_heap(dados_heap.begin(), dados_heap.end());
// Heap atualizado, ex: {16, 15, 10, 14, 7, 9, 3, 2, 4, 1, 8}

// Extrair o maior elemento
std::pop_heap(dados_heap.begin(), dados_heap.end());
int maior = dados_heap.back(); // maior = 16
dados_heap.pop_back(); // Remove o maior elemento do vetor

// Ordenar o heap restante
std::sort_heap(dados_heap.begin(), dados_heap.end());
// dados_heap agora está ordenado em ordem crescente
 
  1. Algoritmos de Mínimo/Máximo

5.1 min e max

Retornam o menor ou maior de dois valores ou de uma lista inicializadora.


#include <algorithm>
#include <initializer_list>

int a = 5, b = 10;
int minimo = std::min(a, b); // 5
int maximo = std::max(a, b); // 10

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

5.2 min\_element e max\_element

Retornam iteradores para o menor e o maior elemento em uma faixa.


#include <vector>
#include <algorithm>

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

5.3 minmax\_element (C++11)

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


#include <vector>
#include <algorithm>

std::vector<int> valores = {3, 1, 4, 1, 5, 9, 2, 6};
auto par_minmax = std::minmax_element(valores.begin(), valores.end());
// par_minmax.first aponta para o primeiro 1
// par_minmax.second aponta para 9
 
  1. Algoritmos Numéricos (em <numeric>)

6.1 accumulate

Calcula a soma (ou outra operação acumulativa) dos elementos em uma faixa.


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

std::vector<int> numeros = {1, 2, 3, 4, 5};
// Soma com valor inicial 0
int soma_total = std::accumulate(numeros.begin(), numeros.end(), 0); // 15

// Produto com valor inicial 1
int produto_total = std::accumulate(numeros.begin(), numeros.end(), 1, std::multiplies<int>()); // 120
 

6.2 inner\_product

Calcula o produto escalar de dois intervalos.


#include <vector>
#include <numeric>

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
// (1*4) + (2*5) + (3*6)
int produto_escalar = std::inner_product(v1.begin(), v1.end(), v2.begin(), 0); // 32
 

6.3 iota

Preenche uma faixa com valores sequencialmente incrementais.


#include <vector>
#include <numeric>

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

6.4 partial\_sum

Calcula as somas parciais de uma faixa.


#include <vector>
#include <numeric>

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

6.5 adjacent\_difference

Calcula a diferença entre elementos adjacentes.


#include <vector>
#include <numeric>

std::vector<int> fonte = {1, 2, 3, 4, 5};
std::vector<int> destino_diff(fonte.size());
std::adjacent_difference(fonte.begin(), fonte.end(), destino_diff.begin()); // destino_diff é {1, 1, 1, 1, 1}
 
  1. Outros Algoritmos Úteis

7.1 generate

Preenche uma faixa usando uma função geradora.


#include <vector>
#include <algorithm>

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

7.2 generate\_n

Gera n elementos e os insere a partir de um iterador de destino.


#include <vector>
#include <algorithm>

std::vector<int> destino(5);
int valor_inicial = 10;
std::generate_n(destino.begin(), 3, [&valor_inicial]() {
   return valor_inicial += 2; // Gera 10, 12, 14
}); // destino é {10, 12, 14, 0, 0} (os dois últimos permanecem inalterados)
 

7.3 includes

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


#include <vector>
#include <algorithm>

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

7.4 Operações de Conjunto

Estes algoritmos requerem que os intervalos de entrada estejam ordenados.

  • set_union: União.
  • set_intersection: Interseção.
  • set_difference: Diferença (elementos no primeiro, mas não no segundo).
  • set_symmetric_difference: Diferença simétrica (elementos em um ou outro, mas não em ambos).

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

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

// União
resultado.clear();
std::set_union(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(resultado));
// resultado: {1, 2, 3, 4, 5, 6, 7}

// Interseção
resultado.clear();
std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(resultado));
// resultado: {3, 4, 5}

// Diferença (set1 - set2)
resultado.clear();
std::set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(resultado));
// resultado: {1, 2}

// Diferença Simétrica
resultado.clear();
std::set_symmetric_difference(set1.begin(), set1.end(), set2.begin(), set2.end(), std::back_inserter(resultado));
// resultado: {1, 2, 6, 7}
 
  1. Perguntas Frequentes

  1. Diferença entre sort e stable_sort?sort usa introsort (uma combinação de quicksort, heapsort e insertion sort) e é geralmente mais rápido, mas não garante a ordem relaitva de elementos iguais. stable_sort usa mergesort (ou similar) e garante que elementos iguais mantenham sua ordem relativa original, o que pode ser crucial em certos cenários, embora geralmente consuma mais memória e seja ligeiramente mais lento.
  2. Por que remove precisa ser usado com erase?remove não altera o tamanho do container; ele apenas move os elementos que devem ser "removidos" para o final e retorna um iterador para o primeiro desses elementos. O container ainda contém os elementos originais, mas com os que foram "removidos" movidos para o final. erase é o que efetivamente remove os elementos do container, redimensionando-o. A combinação container.erase(std::remove(begin, end, value), container.end()) é a maneira padrão de remover todos os elementos com um valor específico.
  3. **Quais algoritmos exigem que o container esteja ordenado?**Algoritmos de busca binária como binary_search, lower_bound, upper_bound, equal_range, e também algoritmos de operação de conjuntos como set_intersection, set_union, merge, etc., dependem da ordenação para funcionar corretamente e de forma eficiente (geralmente com complexidade logarítmica).

Tags: STL C++ Algoritmos contêineres vector

Publicado em 6-10 04:53 por Thomas