HOWTO · C++
Como usar algoritmos de heap da STL em C++
Este artigo apresenta como utilizar algoritmos de heap STL em C++.
Nesta página
Use os algoritmos de heap da STL do C++ para transformar um intervalo de std::vector em um max-heap, ordenar esse intervalo ou mover seu maior valor para o final. std::pop_heap move o valor; se quiser removê-lo do vetor, apague-o separadamente.
Use std::make_heap para transformar um intervalo em um heap
Um heap é um intervalo organizado de modo a obedecer a uma regra entre pais e filhos. Com a comparação padrão, std::make_heap cria um max-heap: cada pai é maior ou igual aos filhos, por isso o maior valor fica no início. Em um heap binário armazenado em um vetor, os filhos do elemento no índice i ficam nos índices 2 * i + 1 e 2 * i + 2, quando essas posições estão dentro do intervalo. Um min-heap usa outra comparação, como std::greater<int>.
std::make_heap reorganiza os elementos para satisfazer a propriedade de heap; não ordena todo o intervalo. Várias disposições podem satisfazer essa mesma propriedade. Por isso, o código deve depender do valor no início e das comparações entre pais e filhos, e não de uma sequência específica de valores após a chamada.
O exemplo independente a seguir cria um heap e verifica a relação entre pais e filhos. Ele foi executado com sucesso, e a saída observada é mostrada abaixo. Os rótulos do bloco de saída permanecem em inglês.
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{11, 82, 39, 72, 51, 32, 91};
std::make_heap(values.begin(), values.end());
bool is_max_heap = true;
for (std::size_t child = 1; child < values.size(); ++child) {
const std::size_t parent = (child - 1) / 2;
if (values[parent] < values[child]) {
is_max_heap = false;
break;
}
}
std::cout << "max-heap: " << std::boolalpha << is_max_heap << '\n';
std::cout << "top: " << values.front() << '\n';
}
max-heap: true
top: 91
O elemento no início é o valor do topo do heap. A ordem dos demais elementos do vetor é limitada apenas pela propriedade de heap; portanto, ela não deve ser interpretada como uma lista em ordem crescente ou decrescente.
Use std::sort_heap para ordenar um intervalo de heap
Chame std::sort_heap em um intervalo que já satisfaça a propriedade de heap. Com a comparação padrão, a função reorganiza o intervalo em ordem crescente. Depois da ordenação, o intervalo deixa de satisfazer a propriedade de heap. Antes de usar uma operação de heap em todo o intervalo novamente, chame std::make_heap.
Por exemplo, depois de std::make_heap(values.begin(), values.end()), chame std::sort_heap(values.begin(), values.end()) para ordenar o mesmo intervalo. A comparação padrão produz uma ordem crescente.
A sobrecarga que recebe um comparador aceita uma função ou um objeto de função que define a ordenação. Use a mesma comparação ao construir e ordenar um heap; por exemplo, um heap criado com std::greater<int> deve ser ordenado com esse comparador. A sobrecarga padrão é adequada quando o objetivo é obter uma saída em ordem crescente.
Use std::pop_heap para mover o próximo elemento do heap
std::pop_heap move o valor do topo para a última posição do intervalo e restaura a propriedade de heap no trecho anterior. A função não altera o tamanho do vetor. Para remover esse valor do contêiner, chame pop_back() depois de std::pop_heap.
Para adicionar um elemento, acrescente-o ao vetor e chame std::push_heap. O intervalo anterior ao elemento acrescentado já deve ser um heap; então, std::push_heap restaura a propriedade de heap em todo o intervalo ampliado. Use o mesmo comparador que define o heap em ambas as operações.
Este exemplo independente verifica o valor do topo movido, apaga-o separadamente e, em seguida, adiciona um valor mantendo a propriedade de heap. Ele foi executado com sucesso, e a saída observada é mostrada abaixo. Os rótulos do bloco de saída permanecem em inglês.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{11, 82, 39, 72, 51, 32, 91};
std::make_heap(values.begin(), values.end());
std::pop_heap(values.begin(), values.end());
std::cout << "moved top: " << values.back() << '\n';
values.pop_back();
values.push_back(95);
std::push_heap(values.begin(), values.end());
std::cout << "new top: " << values.front() << '\n';
}
moved top: 91
new top: 95
Depois de std::pop_heap, o prefixo que termina antes do último elemento é o heap que pode ser reutilizado. Quando pop_back() remove o valor final, o próprio vetor volta a conter exatamente esse intervalo de heap. Acrescentar um elemento sem preservar antes essa condição não atende ao pré-requisito de std::push_heap.