Implementations
C++
#include <cstddef>
#include <vector>
template <typename T, typename Cmp = std::less<T>> class Heap {
private:
std::vector<T> array_;
Cmp comp_;
// INFO: you don't need a size
// INFO: it's complete b tree in the sense that you FILL the
// row for given height by virtue of the indexing being used
// WARN:
// the comp is strict weak ordered so a < b
// doing a !comp(a, b) where invariant is that a < b
// and thinking that it reads as if invariant does not hold
// is wrong as it'll swap equals
// read it instead as, if the invariant is in the other direction
// then you swap
inline std::size_t parent(std::size_t i) const { return (i - 1) >> 1; }
inline std::size_t left(std::size_t i) const { return (i << 1) + 1; }
inline std::size_t right(std::size_t i) const { return (i << 1) + 2; }
void sift_up(std::size_t i) {
// from i go all the way up while invariant does not hold
// NOTE: comp being true is the invariant
// say for min heapt comp gives true for a < b
while (i > 0 && comp_(array_[i], array_[parent(i)])) {
std::swap(array_[parent(i)], array_[i]);
i = parent(i);
}
}
void sift_down(std::size_t i) {
// there's cur i and it's two children
// invariant is that i must be smaller than both children
// basically smallest of the 3, so we pick if the smaller child
// if cur is not alreayd the smallest ( exit if that is the case )
// and then swap to make the smallest of the 3 the cur
while (i < array_.size()) {
auto smallest = i;
auto l = left(i);
auto r = right(i);
// read as => invariant should be comp_(smallest, l)
// so if NOT of invariant, if the smallest is not the smallest
for (auto child_pos : {l, r}) {
if (child_pos < array_.size() &&
comp_(array_[child_pos], array_[smallest])) {
smallest = child_pos;
}
}
if (smallest == i)
break;
std::swap(array_[smallest], array_[i]);
i = smallest;
}
}
public:
// no need of ctors or detors, custom ones will do
// only the swaps mainain the invariant and since we push to some
// position and then move the way up only the onces encountered
// on the chain can possibly be wrong, for the rest the invariant
// holds
Heap() = default;
Heap(const std::vector<T> &array) : array_(array) {
// how to know the boundary? the first leaf will have no child sp
// 2i+1 >=n
// i >= (n-1)/2, by int division this is same as >= n/2
for (std::size_t i = array_.size() / 2; i-- > 0;)
sift_down(i);
// NOTE:
// we do in reverse form n/2 to 1 (inc) as
// - n/2 beyond are all leaves so nothing to sift down to
// - 0 is to top and assumes that sub-trees are heaps hence reverse
// to go bottom up
// How O(n)?
// n/2 = 0 moves
// n/4 => 1 moves ( worst case )
// n/8 => 2
// so n * (1/4 + 2/8 + 3/16) => this is a convergent series ( nums i
// ari, denom is geo ) so n * constant
}
std::size_t size() const { return array_.size(); }
const T &top() const {
// must be const to preserve invariant
return array_.front();
}
void push(const T &value) {
array_.push_back(value);
sift_up(array_.size() - 1);
}
void pop() {
// 0 idx to be removed, so move it to back and just pop back
std::swap(array_.front(), array_.back());
array_.pop_back();
sift_down(0); // this'll handle internally if empty
}
};
int main() {
Heap<int> heap;
heap.push(10);
heap.pop();
}Java
import java.util.ArrayList;
import java.util.Collections;
final class Heap<T extends Comparable<? super T>> {
private ArrayList<T> array = new ArrayList<T>();
private int left(int i) {
return 2 * i + 1;
}
private int right(int i) {
return 2 * i + 2;
}
private int par(int i) {
return (i - 1) / 2;
}
private void siftUp(int i) {
// WARN: rem to do "if the invariant holds in the other direction"
// doesn't really matter for java since I have the != info as well
// but better to use ONE style
while (i > 0 && array.get(i).compareTo(array.get(par(i))) < 0) {
Collections.swap(array, i, par(i));
i = par(i);
}
}
private void siftDown(int i) {
while (i < array.size()) {
int smallest = i;
int l = left(i);
int r = right(i);
// NOTE: avoid this, needless stack allocation
// for(int childPos : [left(i), right(i)]){
if (l < array.size() && array.get(l).compareTo(array.get(smallest)) < 0) {
smallest = l;
}
// WARN: rem to compare to "smallest" and not i
if (r < array.size() && array.get(r).compareTo(array.get(smallest)) < 0) {
smallest = r;
}
if (smallest == i)
break;
Collections.swap(array, i, smallest);
i = smallest;
}
}
public int size() {
return array.size();
}
// need offer = insert, peek = top and poll = remove and ret
public void offer(T val) {
array.add(val);
siftUp(array.size() - 1);
}
public T peek() {
return array.get(0);
}
public T poll() {
Collections.swap(array, 0, array.size() - 1);
var ret = array.removeLast();
siftDown(0);
return ret;
}
}Notes
cpp
what is std::less?
template <typename T>
struct less {
bool operator()(const T& a, const T& b) const {
return a < b;
}
};a struct that defines this operator() ( or class );
the std::sort needs a strict weak ordering means a < b and a < a is ALWAYS false ( the last part is what the strict is )
the func returns where it’s true if a is ”<” b by strick weak ordering