A Standard Template Library (STL) do C++ oferece um vasto conjunto de algoritmos genéricos que operam em diferentes tipos de containers, utilizando iteradores para abstrair a estrutura de dados subjacente. Esses algoritmos são poderosos e otimizados, permitindo aos desenvolvedores realizar operações complexas de forma concisa e eficiente. Este guia explora as categorias principais de algoritmos da STL, apresentando exemplos práticos para ilustrar seu uso.
Algoritmos de Sequência Não Modificadores
Esta categoria de algoritmos examina elementos em um intervalo sem alterar seu conteúdo.
1. Busca: std::find, std::find_if, std::find_end
std::find(inicio, fim, valor): Localiza a primeira ocorrência devalor. Retorna um iterador para o elemento oufimse não encontrado.std::find_if(inicio, fim, predicado): Encontra o primeiro elemento que satisfaz a condição definida pelopredicado(uma função ou lambda).std::find_end(inicio, fim, sub_inicio, sub_fim): Busca a última ocorrência de uma subsequência dentro de um intervalo.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numeros = {10, 20, 30, 40, 50, 20};
// Procurar o valor 30
auto it_val = std::find(numeros.begin(), numeros.end(), 30);
if (it_val != numeros.end()) {
std::cout << "Valor 30 encontrado: " << *it_val << std::endl; // Saída: 30
}
// Procurar o primeiro elemento menor que 25
auto it_pred = std::find_if(numeros.begin(), numeros.end(), [](int x) {
return x < 25;
});
std::cout << "Primeiro < 25: " << *it_pred << std::endl; // Saída: 10
// Procurar a última ocorrência da subsequência {20}
std::vector<int> sub_seq = {20};
auto it_sub = std::find_end(numeros.begin(), numeros.end(), sub_seq.begin(), sub_seq.end());
if (it_sub != numeros.end()) {
std::cout << "Subsequencia {20} encontrada no indice: " << std::distance(numeros.begin(), it_sub) << std::endl; // Saída: 5
}
return 0;
}
2. Contagem: std::count, std::count_if
std::count(inicio, fim, valor): Retorna o número de elementos iguais avalorno intervalo.std::count_if(inicio, fim, predicado): Retorna a contagem de elementos que satisfazem opredicado.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> dados = {7, 1, 3, 7, 5, 7, 9};
int contagem_sete = std::count(dados.begin(), dados.end(), 7);
std::cout << "Quantidade de 7s: " << contagem_sete << std::endl; // Saída: 3
int contagem_impares = std::count_if(dados.begin(), dados.end(), [](int n) {
return n % 2 != 0;
});
std::cout << "Quantidade de impares: " << contagem_impares << std::endl; // Saída: 6
return 0;
}
3. Iteração: std::for_each
Aplica uma função (ou objeto de função) a cada elemento dentro de um intervalo.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> valores = {1, 2, 3, 4};
std::cout << "Valores originais: ";
for (int v : valores) { std::cout << v << " "; }
std::cout << std::endl;
std::for_each(valores.begin(), valores.end(), [](int& x) {
x += 10; // Adiciona 10 a cada elemento
});
std::cout << "Valores apos for_each: ";
for (int v : valores) { std::cout << v << " "; } // Saída: 11 12 13 14
std::cout << std::endl;
return 0;
}
4. Comparação de Intervalos: std::equal e std::mismatch
std::equal(b1, e1, b2): Verifica se dois intervalos são idênticos.std::mismatch(b1, e1, b2): Retorna um par de iteradores apontando para o primeiro par de elementos diferentes.
#include <iostream>
#include <vector>
#include <algorithm>
#include <utility> // Para std::pair
int main() {
std::vector<int> arrA = {10, 20, 30};
std::vector<int> arrB = {10, 20, 35};
std::vector<int> arrC = {10, 20, 30};
bool sao_iguais = std::equal(arrA.begin(), arrA.end(), arrC.begin());
std::cout << "ArrA == ArrC? " << std::boolalpha << sao_iguais << std::endl; // Saída: true
auto dif = std::mismatch(arrA.begin(), arrA.end(), arrB.begin());
if (dif.first != arrA.end()) {
std::cout << "Primeira diferenca: " << *dif.first << " em ArrA vs " << *dif.second << " em ArrB" << std::endl; // Saída: 30 vs 35
}
return 0;
}
5. Verificação de Propriedades: std::all_of, std::any_of, std::none_of (C++11)
Verificam se todos, pelo menos um ou nenhum dos elementos de um intervalo satisfazem um predicado.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> positivos = {10, 20, 30, 40};
bool todos_positivos = std::all_of(positivos.begin(), positivos.end(), [](int x) {
return x > 0;
});
std::cout << "Todos > 0? " << std::boolalpha << todos_positivos << std::endl; // Saída: true
bool algum_div_tres = std::any_of(positivos.begin(), positivos.end(), [](int x) {
return x % 3 == 0;
});
std::cout << "Algum divisivel por 3? " << std::boolalpha << algum_div_tres << std::endl; // Saída: true (30)
bool nenhum_negativo = std::none_of(positivos.begin(), positivos.end(), [](int x) {
return x < 0;
});
std::cout << "Nenhum negativo? " << std::boolalpha << nenhum_negativo << std::endl; // Saída: true
return 0;
}
Algoritmos de Sequência Modificadores
Estes algoritmos alteram a ordem ou os valores dos elementos no intervalo em que operam.
1. Cópia: std::copy, std::copy_if
std::copy(inicio, fim, destino): Copia elementos para um novo local.std::copy_if(inicio, fim, destino, predicado): Copia elementos que satisfazem um predicado.
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator> // Para std::back_inserter
int main() {
std::vector<int> fonte = {10, 11, 12, 13, 14};
std::vector<int> destino(5); // Deve ter espaço suficiente ou usar back_inserter
// Copia todos os elementos
std::copy(fonte.begin(), fonte.end(), destino.begin());
std::cout << "Destino (todos): ";
for (int v : destino) { std::cout << v << " "; } // Saída: 10 11 12 13 14
std::cout << std::endl;
// Copia apenas os elementos ímpares
std::vector<int> impares;
std::copy_if(fonte.begin(), fonte.end(), std::back_inserter(impares), [](int x) {
return x % 2 != 0;
});
std::cout << "Impares: ";
for (int v : impares) { std::cout << v << " "; } // Saída: 11 13
std::cout << std::endl;
return 0;
}
2. Transformação: std::transform
Aplica uma função a cada elemento de um intervalo e armazena os resultados em outro intervalo. Pode ser unário ou binário.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> // Para std::multiplies
int main() {
std::vector<int> entrada = {2, 3, 4};
std::vector<int> cubos(entrada.size());
// Calcula o cubo de cada elemento (transformação unária)
std::transform(entrada.begin(), entrada.end(), cubos.begin(), [](int x) {
return x * x * x;
});
std::cout << "Cubos: ";
for (int v : cubos) { std::cout << v << " "; } // Saída: 8 27 64
std::cout << std::endl;
// Multiplica elementos de duas coleções (transformação binária)
std::vector<int> vA = {1, 2, 3};
std::vector<int> vB = {5, 6, 7};
std::vector<int> produto(vA.size());
std::transform(vA.begin(), vA.end(), vB.begin(), produto.begin(), std::multiplies<int>());
std::cout << "Produtos: ";
for (int v : produto) { std::cout << v << " "; } // Saída: 5 12 21
std::cout << std::endl;
return 0;
}
3. Substituição: std::replace, std::replace_if, std::replace_copy
std::replace(b, e, old_val, new_val): Substitui todas as ocorrências deold_valpornew_val.std::replace_if(b, e, predicado, new_val): Substitui elementos que satisfazem opredicado.std::replace_copy(b, e, dest, old_val, new_val): Copia elementos, substituindo os valores, sem modificar o original.
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> dados_rep = {10, 60, 10, 70, 10, 80};
// Substitui todos os 10s por 99
std::replace(dados_rep.begin(), dados_rep.end(), 10, 99);
std::cout << "Apos replace (10 -> 99): ";
for (int v : dados_rep) { std::cout << v << " "; } // Saída: 99 60 99 70 99 80
std::cout << std::endl;
// Substitui elementos maiores que 50 por 0
std::replace_if(dados_rep.begin(), dados_rep.end(), [](int x) {
return x > 50;
}, 0);
std::cout << "Apos replace_if (>50 -> 0): ";
for (int v : dados_rep) { std::cout << v << " "; } // Saída: 99 0 99 0 99 0
std::cout << std::endl;
// Copia substituindo 99 por 1000 (origem inalterada)
std::vector<int> copia_substituida;
std::replace_copy(dados_rep.begin(), dados_rep.end(), std::back_inserter(copia_substituida), 99, 1000);
std::cout << "Copia substituida: ";
for (int v : copia_substituida) { std::cout << v << " "; } // Saída: 1000 0 1000 0 1000 0
std::cout << std::endl;
return 0;
}
4. Remoção: std::remove, std::remove_if e erase
std::remove(inicio, fim, valor): Move todos os elementos iguais avalorpara o final do intervalo e retorna um iterador para o novo "final lógico". Não reduz o tamanho do container.std::remove_if(inicio, fim, predicado): Similar aremove, mas com base em umpredicado.- Para remover fisicamente os elementos e redimensionar o container, é necessário combinar
removeouremove_ifcom o métodoerasedo container.
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
int main() {
std::vector<char> caracteres = {'a', 'b', 'c', 'b', 'd', 'b'};
// Remove logicamente 'b's (move para o final)
auto novo_fim = std::remove(caracteres.begin(), caracteres.end(), 'b');
std::cout << "Apos remove (logico): ";
for (char c : caracteres) { std::cout << c << " "; } // Saída: a c d b b b (tamanho ainda 6)
std::cout << std::endl;
// Remove fisicamente os elementos extra
caracteres.erase(novo_fim, caracteres.end());
std::cout << "Apos erase (fisico): ";
for (char c : caracteres) { std::cout << c << " "; } // Saída: a c d (tamanho 3)
std::cout << std::endl;
// Exemplo combinado: remover vogais
std::vector<char> letras = {'m', 'a', 'p', 'e', 'l', 'o', 'r'};
letras.erase(std::remove_if(letras.begin(), letras.end(), [](char ch) {
return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u';
}), letras.end());
std::cout << "Apos remover vogais: ";
for (char ch : letras) { std::cout << ch << " "; } // Saída: m p l r
std::cout << std::endl;
return 0;
}
5. Unicidade: std::unique
Remove elementos duplicados consecutivos de um intervalo. Retorna um iterador para o novo final lógico. Assim como remove, precisa ser combinado com erase para reduzir o tamanho físico.
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
int main() {
std::vector<std::string> palavras = {"azul", "azul", "vermelho", "verde", "verde", "verde", "amarelo"};
// Remove duplicados consecutivos
auto ultimo = std::unique(palavras.begin(), palavras.end());
palavras.erase(ultimo, palavras.end());
std::cout << "Apos unique: ";
for (const std::string& p : palavras) { std::cout << p << " "; } // Saída: azul vermelho verde amarelo
std::cout << std::endl;
return 0;
}
6. Reversão: std::reverse
Inverte a ordem dos elementos em um intervalo.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<double> precos = {10.5, 20.0, 35.7, 5.0};
std::cout << "Precos originais: ";
for (double p : precos) { std::cout << p << " "; }
std::cout << std::endl;
std::reverse(precos.begin(), precos.end());
std::cout << "Precos invertidos: ";
for (double p : precos) { std::cout << p << " "; } // Saída: 5 35.7 20 10.5
std::cout << std::endl;
return 0;
}
7. Rotação: std::rotate
Rotaciona os elementos em um intervalo de forma que o elemento apontado por meio se torne o novo primeiro elemento.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> sequencia = {10, 20, 30, 40, 50};
std::cout << "Sequencia original: ";
for (int v : sequencia) { std::cout << v << " "; }
std::cout << std::endl;
// Rotaciona a sequencia, com 30 como novo primeiro elemento
std::rotate(sequencia.begin(), sequencia.begin() + 2, sequencia.end()); // +2 significa que o elemento no índice 2 (30) será o novo começo
std::cout << "Sequencia rotacionada: ";
for (int v : sequencia) { std::cout << v << " "; } // Saída: 30 40 50 10 20
std::cout << std::endl;
return 0;
}
8. Embaralhamento: std::shuffle (C++11)
Reorganiza aleatoriamente os elementos em um intervalo. Requer um gerador de números aleatórios.
#include <iostream>
#include <vector>
#include <algorithm>
#include <random> // Para std::random_device e std::mt19937
int main() {
std::vector<int> itens = {1, 2, 3, 4, 5, 6, 7};
std::cout << "Itens originais: ";
for (int i : itens) { std::cout << i << " "; }
std::cout << std::endl;
std::random_device rd;
std::mt19937 g(rd()); // Gerador de números aleatórios
std::shuffle(itens.begin(), itens.end(), g);
std::cout << "Itens embaralhados: ";
for (int i : itens) { std::cout << i << " "; } // Saída: Sequência aleatória
std::cout << std::endl;
return 0;
}
Algoritmos de Ordenação e Relacionados
Esses algoritmos são usados para organizar elementos em uma ordem específica ou para operar em intervalos ordenados.
1. Ordenação: std::sort, std::stable_sort, std::partial_sort
std::sort(inicio, fim): Ordena um intervalo. É geralmente uma implementação de introsort (híbrido de quicksort, heapsort e insertion sort), instável, com complexidade O(N log N).std::stable_sort(inicio, fim): Ordena um intervalo, mantendo a ordem relativa de elementos iguais. Geralmente usa mergesort, estável, com complexidade O(N log N).std::partial_sort(inicio, meio, fim): Ordena osmeio - iniciomenores elementos e os coloca na parte inicial do intervalo.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> // Para std::greater
#include <string>
#include <tuple> // Para std::tuple e std::get
int main() {
std::vector<int> desordenados = {8, 2, 6, 1, 9, 3};
std::sort(desordenados.begin(), desordenados.end()); // Ordem crescente padrão
std::cout << "Sort crescente: ";
for (int v : desordenados) { std::cout << v << " "; } // Saída: 1 2 3 6 8 9
std::cout << std::endl;
std::sort(desordenados.begin(), desordenados.end(), std::greater<int>()); // Ordem decrescente
std::cout << "Sort decrescente: ";
for (int v : desordenados) { std::cout << v << " "; } // Saída: 9 8 6 3 2 1
std::cout << std::endl;
// Exemplo de stable_sort (mantém ordem relativa para elementos iguais)
std::vector<std::tuple<int, char>> pessoas = {{2, 'B'}, {1, 'A'}, {2, 'C'}, {1, 'D'}};
std::stable_sort(pessoas.begin(), pessoas.end(), [](const auto& p1, const auto& p2) {
return std::get<0>(p1) < std::get<0>(p2);
});
std::cout << "Stable Sort (por idade): ";
for (const auto& p : pessoas) { std::cout << "(" << std::get<0>(p) << "," << std::get<1>(p) << ") "; }
// Saída: (1,A) (1,D) (2,B) (2,C) - 'A' vem antes de 'D', 'B' antes de 'C'
std::cout << std::endl;
std::vector<int> todos_elementos = {10, 5, 20, 15, 30, 25};
// Coloca os 3 menores elementos no início e os ordena
std::partial_sort(todos_elementos.begin(), todos_elementos.begin() + 3, todos_elementos.end());
std::cout << "Partial Sort (primeiros 3): ";
for (int v : todos_elementos) { std::cout << v << " "; } // Saída: 5 10 15 30 25 20 (os 3 primeiros estão ordenados)
std::cout << std::endl;
return 0;
}
2. Particionamento: std::nth_element
Reorganiza um intervalo de modo que o elemento na posição n-ésima seja o mesmo que estaria nessa posição se o intervalo estivesse completamente ordenado. Todos os elementos antes dessa posição são menores ou iguais a ele, e todos os elementos depois são maiores ou iguais. Não ordena os outros elementos.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> amostra = {9, 1, 7, 3, 5, 2, 8};
// Encontrar o 3º menor elemento (índice 2)
std::nth_element(amostra.begin(), amostra.begin() + 2, amostra.end());
std::cout << "Elemento na 3a posicao (ordenado): " << amostra[2] << std::endl; // Saída: 3
std::cout << "Colecao apos nth_element: ";
for (int v : amostra) { std::cout << v << " "; } // Ex: 1 2 3 9 5 7 8 (elementos à esquerda <=3, à direita >=3)
std::cout << std::endl;
return 0;
}
3. Busca em Intervalos Ordenados: std::binary_search, std::lower_bound, std::upper_bound
Esses algoritmos exigem que o intervalo de entrada esteja previamente ordenado.
std::binary_search(inicio, fim, valor): Verifica sevalorexiste no intervalo (retornabool).std::lower_bound(inicio, fim, valor): Retorna um iterador para o primeiro elemento que não é menor quevalor.std::upper_bound(inicio, fim, valor): Retorna um iterador para o primeiro elemento que é maior quevalor.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> ordenado = {10, 20, 20, 30, 40, 50}; // Deve estar ordenado
bool existe = std::binary_search(ordenado.begin(), ordenado.end(), 20);
std::cout << "O valor 20 existe? " << std::boolalpha << existe << std::endl; // Saída: true
auto lb = std::lower_bound(ordenado.begin(), ordenado.end(), 20);
std::cout << "lower_bound para 20 no indice: " << std::distance(ordenado.begin(), lb) << std::endl; // Saída: 1 (aponta para o primeiro 20)
auto ub = std::upper_bound(ordenado.begin(), ordenado.end(), 20);
std::cout << "upper_bound para 20 no indice: " << std::distance(ordenado.begin(), ub) << std::endl; // Saída: 3 (aponta para 30)
return 0;
}
4. Combinação: std::merge
Combina dois intervalos ordenados em um terceiro intervalo, também ordenado.
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> lista1 = {1, 3, 7};
std::vector<int> lista2 = {2, 4, 6};
std::vector<int> resultado_merge(lista1.size() + lista2.size());
std::merge(lista1.begin(), lista1.end(), lista2.begin(), lista2.end(), resultado_merge.begin());
std::cout << "Listas combinadas: ";
for (int v : resultado_merge) { std::cout << v << " "; } // Saída: 1 2 3 4 6 7
std::cout << std::endl;
return 0;
}
Algoritmos de Heap
A STL oferece funções para manipular um intervalo como uma estrutura de dados de heap (pilha), tipicamente um max-heap por padrão, onde o maior elemento está sempre no início.
#include <iostream>
#include <vector>
#include <algorithm> // Contém os algoritmos de heap
int main() {
std::vector<int> elementos_heap = {9, 3, 6, 2, 8};
std::cout << "Original: ";
for (int v : elementos_heap) { std::cout << v << " "; }
std::cout << std::endl;
std::make_heap(elementos_heap.begin(), elementos_heap.end()); // Transforma em max-heap
std::cout << "Apos make_heap: ";
for (int v : elementos_heap) { std::cout << v << " "; } // Saída (exemplo): 9 8 6 2 3
std::cout << std::endl;
elementos_heap.push_back(10); // Adiciona um novo elemento
std::push_heap(elementos_heap.begin(), elementos_heap.end()); // Reorganiza para manter a propriedade de heap
std::cout << "Apos push_heap(10): ";
for (int v : elementos_heap) { std::cout << v << " "; } // Saída (exemplo): 10 9 6 2 8 3
std::cout << std::endl;
std::pop_heap(elementos_heap.begin(), elementos_heap.end()); // Move o maior elemento para o final
int maior_elemento = elementos_heap.back(); // Pega o maior
elementos_heap.pop_back(); // Remove o maior do heap (ele está no final)
std::cout << "Maior elemento: " << maior_elemento << std::endl; // Saída: 10
std::cout << "Heap apos pop_heap: ";
for (int v : elementos_heap) { std::cout << v << " "; } // Saída (exemplo): 9 8 6 2 3
std::cout << std::endl;
std::sort_heap(elementos_heap.begin(), elementos_heap.end()); // Transforma o heap em uma coleção ordenada
std::cout << "Apos sort_heap: ";
for (int v : elementos_heap) { std::cout << v << " "; } // Saída: 2 3 6 8 9
std::cout << std::endl;
return 0;
}
Algoritmos de Mínimo/Máximo
Encontram os valores mínimo e/ou máximo em um conjunto de elementos.
1. Simples: std::min, std::max
Retornam o menor/maior de dois valores ou de uma lista de inicializadores.
#include <iostream>
#include <algorithm>
#include <vector> // Para initializer_list (C++11)
int main() {
int val1 = 15, val2 = 25;
int minimo = std::min(val1, val2);
int maximo = std::max(val1, val2);
std::cout << "Minimo entre " << val1 << " e " << val2 << ": " << minimo << std::endl; // Saída: 15
std::cout << "Maximo entre " << val1 << " e " << val2 << ": " << maximo << std::endl; // Saída: 25
auto min_lista = std::min({1, 5, 2, 9, 4});
auto max_lista = std::max({1, 5, 2, 9, 4});
std::cout << "Minimo da lista: " << min_lista << std::endl; // Saída: 1
std::cout << "Maximo da lista: " << max_lista << std::endl; // Saída: 9
return 0;
}
2. Em Intervalo: std::min_element, std::max_element, std::minmax_element (C++11)
Retornam iteradores para os elementos mínimo e/ou máximo em um intervalo.
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
int main() {
std::vector<float> notas = {7.5f, 9.0f, 6.0f, 8.5f, 7.0f};
auto min_it = std::min_element(notas.begin(), notas.end());
auto max_it = std::max_element(notas.begin(), notas.end());
std::cout << "Menor nota: " << *min_it << std::endl; // Saída: 6.0
std::cout << "Maior nota: " << *max_it << std::endl; // Saída: 9.0
std::vector<std::string> palavras_minmax = {"uva", "banana", "maca", "pera"};
auto minmax_par = std::minmax_element(palavras_minmax.begin(), palavras_minmax.end());
std::cout << "Palavra lexicograficamente menor: " << *minmax_par.first << std::endl; // Saída: banana
std::cout << "Palavra lexicograficamente maior: " << *minmax_par.second << std::endl; // Saída: uva
return 0;
}
Algoritmos Numéricos (do cabeçalho <numeric>)
Estes algoritmos realizam operações matemáticas em sequências.
1. Soma Acumulada: std::accumulate
Calcula a soma de todos os elementos em um intervalo, começando com um valor inicial dado. Pode usar uma operação personalizada.
#include <iostream>
#include <vector>
#include <numeric> // Para std::accumulate
#include <functional> // Para std::multiplies
int main() {
std::vector<int> valores_numericos = {10, 20, 30, 40};
int soma_total = std::accumulate(valores_numericos.begin(), valores_numericos.end(), 0);
std::cout << "Soma total: " << soma_total << std::endl; // Saída: 100
int produto_total = std::accumulate(valores_numericos.begin(), valores_numericos.end(), 1, std::multiplies<int>());
std::cout << "Produto total: " << produto_total << std::endl; // Saída: 240000 (10*20*30*40)
return 0;
}
2. Produto Interno: std::inner_product
Calcula o produto interno de dois intervalos.
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> v_a = {1, 2, 3};
std::vector<int> v_b = {5, 6, 7};
int produto_interno = std::inner_product(v_a.begin(), v_a.end(), v_b.begin(), 0);
std::cout << "Produto interno: " << produto_interno << std::endl; // Saída: (1*5 + 2*6 + 3*7) = 5 + 12 + 21 = 38
return 0;
}
3. Preenchimento Sequencial: std::iota (C++11)
Preenche um intervalo com valores sequencialmente crescentes, começando de um valor inicial.
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> serie(5);
std::iota(serie.begin(), serie.end(), 100); // Preenche com 100, 101, 102, 103, 104
std::cout << "Serie iota: ";
for (int v : serie) { std::cout << v << " "; } // Saída: 100 101 102 103 104
std::cout << std::endl;
return 0;
}
4. Somas Parciais: std::partial_sum
Calcula as somas parciais de um intervalo, armazenando os resultados em um intervalo de destino.
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> origem_sum = {1, 1, 1, 1, 1};
std::vector<int> destino_sum(origem_sum.size());
std::partial_sum(origem_sum.begin(), origem_sum.end(), destino_sum.begin());
std::cout << "Somas parciais: ";
for (int v : destino_sum) { std::cout << v << " "; } // Saída: 1 2 3 4 5
std::cout << std::endl;
return 0;
return 0;
}
5. Diferenças Adjacentes: std::adjacent_difference
Calcula as diferenças entre elementos adjacentes em um intervalo, armazenando o rseultado em um intervalo de destino. O primeiro elemento é copiado diretamente.
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> dados_diff = {10, 12, 15, 19, 25};
std::vector<int> res_diff(dados_diff.size());
std::adjacent_difference(dados_diff.begin(), dados_diff.end(), res_diff.begin());
std::cout << "Diferencas adjacentes: ";
for (int v : res_diff) { std::cout << v << " "; } // Saída: 10 2 3 4 6
std::cout << std::endl;
return 0;
}
Outros Algoritmos Úteis
1. Geração de Elementos: std::generate, std::generate_n
std::generate(inicio, fim, gerador): Preenche um intervalo com valores gerados por uma função (gerador).std::generate_n(inicio, n, gerador): Preenche os primeirosnelementos de um intervalo.
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <string>
int main() {
std::vector<int> random_nums(5);
std::default_random_engine gerador_rand(std::random_device{}());
std::uniform_int_distribution<int> dist(1, 100);
// Preenche com números aleatórios
std::generate(random_nums.begin(), random_nums.end(), [&](){ return dist(gerador_rand); });
std::cout << "Numeros aleatorios (generate): ";
for (int n : random_nums) { std::cout << n << " "; }
std::cout << std::endl;
std::vector<std::string> prefixes(5);
int contador = 1;
// Preenche os 3 primeiros elementos com prefixos
std::generate_n(prefixes.begin(), 3, [&]() {
return "Item-" + std::to_string(contador++);
});
std::cout << "Prefixos (generate_n): ";
for (const std::string& s : prefixes) { std::cout << (s.empty() ? "(empty)" : s) << " "; } // Saída: Item-1 Item-2 Item-3 (empty) (empty)
std::cout << std::endl;
return 0;
}
2. Inclusão de Conjuntos: std::includes
Verifica se um intervalo ordenado contém todos os elementos de outro intervalo ordenado.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> superconjunto = {10, 20, 30, 40, 50};
std::vector<int> subconjunto = {20, 40};
std::vector<int> nao_subconjunto = {20, 60};
bool inclui_sub = std::includes(superconjunto.begin(), superconjunto.end(), subconjunto.begin(), subconjunto.end());
std::cout << "Superconjunto inclui {20, 40}? " << std::boolalpha << inclui_sub << std::endl; // Saída: true
bool inclui_nao_sub = std::includes(superconjunto.begin(), superconjunto.end(), nao_subconjunto.begin(), nao_subconjunto.end());
std::cout << "Superconjunto inclui {20, 60}? " << std::boolalpha << inclui_nao_sub << std::endl; // Saída: false
return 0;
}
3. Operações de Conjunto: std::set_union, std::set_intersection, std::set_difference, std::set_symmetric_difference
Esses algoritmos realizam operações de teoria de conjuntos em intervalos ordenados, produzindo um novo intervalo ordenado como resultado.
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> conj1 = {1, 2, 3, 4};
std::vector<int> conj2 = {3, 4, 5, 6};
std::vector<int> resultado;
// Uniao
std::set_union(conj1.begin(), conj1.end(), conj2.begin(), conj2.end(), std::back_inserter(resultado));
std::cout << "Uniao: ";
for (int v : resultado) { std::cout << v << " "; } // Saída: 1 2 3 4 5 6
std::cout << std::endl;
// Intersecao
resultado.clear();
std::set_intersection(conj1.begin(), conj1.end(), conj2.begin(), conj2.end(), std::back_inserter(resultado));
std::cout << "Intersecao: ";
for (int v : resultado) { std::cout << v << " "; } // Saída: 3 4
std::cout << std::endl;
// Diferenca (conj1 - conj2)
resultado.clear();
std::set_difference(conj1.begin(), conj1.end(), conj2.begin(), conj2.end(), std::back_inserter(resultado));
std::cout << "Diferenca (C1 - C2): ";
for (int v : resultado) { std::cout << v << " "; } // Saída: 1 2
std::cout << std::endl;
// Diferenca Simetrica
resultado.clear();
std::set_symmetric_difference(conj1.begin(), conj1.end(), conj2.begin(), conj2.end(), std::back_inserter(resultado));
std::cout << "Diferenca Simetrica: ";
for (int v : resultado) { std::cout << v << " "; } // Saída: 1 2 5 6
std::cout << std::endl;
return 0;
}
Perguntas Frequentes sobre Algoritmos STL
1. Qual a distinção crucial entre std::sort e std::stable_sort?
A principal diferença reside na estabilidade da ordenação:
std::sort: Geralmente implementado como introsort (uma combinação de quicksort, heapsort e insertion sort). É mais rápido para a maioria dos casos, com complexidade de tempo média de O(N log N), mas é um algoritmo instável. Isso significa que a ordem relativa de elementos que são considerados "iguais" pelo critério de comparação não é garantida após a ordenação.std::stable_sort: Geralmente implementado como mergesort. Garante que a ordem relativa de elementos iguais seja preservada. Sua complexidade de tempo também é O(N log N), mas pode ter um custo de desempenho ou uso de memória (O(N) espaço extra) ligeiramente maior em comparação comstd::sort.
2. Por que os algoritmos std::remove e std::remove_if frequentemente necessitam de container.erase()?
Os algoritmos std::remove e std::remove_if são projetados para funcionar com qualquer tipo de iterador, incluindo iteradores de entrada/saída que não permitem a modificação do tamanho do container. Eles operam movendo os elementos "não removidos" para o início do intervalo e retornam um iterador para a nova "extremidade lógica" da sequência. Os elementos que seriam removidos ainda existem fisicamente no container, apenas em posições posteriores a essa nova extremidade lógica.
Para realmente remover esses elementos e reduzir o tamanho do container (liberando memória, se aplicável), é preciso usar o método erase() do próprio container, passando o iterador retornado por remove/remove_if como o início do intervalo a ser apagado, até o final físico do container: container.erase(std::remove(...), container.end()).
3. Quais algoritmos da STL exigem que seus intervalos de entrada estejam já ordenados?
Vários algoritmos dependem da propriedade de ordenação dos seus dados para funcionar corretamente e eficientemente. Alguns exemplos notáveis incluem:
- Busca binária e relacionados:
std::binary_search,std::lower_bound,std::upper_bound,std::equal_range. - Operações de fusão:
std::merge. - Operações de conjunto:
std::set_union,std::set_intersection,std::set_difference,std::set_symmetric_difference,std::includes.
Usar esses algoritmos em intervalos não ordenados pode levar a resultados incorretos ou comportamento indefinido.