I began this project to experiment with data structures and algorithms
in C++
, as a result I ended up writing a book which is still ongoig
work. You can find it in doc/book.pdf or Fundamental Algorithms
Another topic that I touched is memory allocation in C++
. Most
specifically how to improve allocation performance for node based
containers. The proposal can be found at Splitting node and array allocations in allocators
Slides are also available in doc/p0310r0.pdf or here
Implementation of some algorithms found in the book.
Assume a node in the form
struct bst_node {
int info;
bst_node* left;
bst_node* right;
};
void bst_insert(bst_node& head, int key)
{
if (!head.left) {
head.left = new bst_node {key, nullptr, nullptr};
return;
}
auto* p = head.left;
while (p) {
if (key < p->info) {
if (!p->left) {
p->left = new bst_node {key, nullptr, nullptr};
return;
}
p = p->left;
} else if (p->info < key) {
if (!p->right) {
p->right = new bst_node {key, nullptr, nullptr};
return;
}
p = p->right;
} else {
return;
}
}
}
void preorder_recursive(bst_node* p)
{
if (!p)
return;
visit(p);
preorder_recursive(p->left);
preorder_recursive(p->right);
}
void preorder_traversal(const bst_node* p)
{
std::stack<bst_node const*> s;
while (p) {
visit(p);
if (p->right)
s.push(p->right);
p = p->left;
if (!p && !s.empty()) {
p = s.top();
s.pop();
}
}
}
void inorder_recursive(bst_node* p)
{
if (!p)
return;
inorder_recursive(p->left);
visit(p);
inorder_recursive(p->right);
}
void inorder_traversal(const bst_node* p)
{
std::stack<const bst_node*> s;
for (;;) {
while (p) {
s.push(p);
p = p->left;
}
if (s.empty())
return;
p = s.top();
s.pop();
visit(p);
p = p->right;
}
}
void postorder_recursive(bst_node* p)
{
if (!p)
return;
postorder_recursive(p->left);
postorder_recursive(p->right);
visit(p);
}
void postorder_traversal(const bst_node* p)
{
std::stack<const bst_node*> s;
const bst_node* q = nullptr;
for (;;) {
while (p) {
s.push(p);
p = p->left;
}
for (;;) {
if (s.empty())
return;
p = s.top();
s.pop();
if (!p->right || p->right == q) {
visit(p);
q = p;
continue;
}
s.push(p);
p = p->right;
break;
}
}
}
Copies a binary search tree to another location
void copy(bst_node* from, bst_node* to)
{
preorder_successor p(from);
preorder_successor q(to);
for (;;) {
if (p.p->left)
q.p->left = new bst_node {{}, nullptr, nullptr};
p.next();
q.next();
if (p.p == from)
return;
if (p.p->right)
q.p->right = new bst_node {{}, nullptr, nullptr};
q.p->info = p.p->info;
}
}
Some searching algorithms.
template<class Iter, class T>
bool binary_search_recursive(Iter begin, Iter end, const T& K)
{
if (begin == end)
return false;
auto mid = (end - begin) / 2;
if (K < begin[mid])
return binary_search_recursive(begin, begin + mid, K);
else if (begin[mid] < K)
return binary_search_recursive(begin + mid + 1, end, K);
else
return true;
}
template<class Iter, class T>
bool binary_search(Iter begin, Iter end, const T& K)
{
if (begin == end)
return false;
auto low = 0;
auto high = end - begin - 1;
while (low <= high) {
auto mid = (low + high) / 2;
if (K < begin[mid])
high = mid - 1;
else if (begin[mid] < K)
low = mid + 1;
else
return true;
}
return false;
}
In the STL the binary search requires only forward iterator so the implementation above has to be changed.
template<class Iter, class T>
bool binary_search_stl(Iter begin, Iter end, const T& K)
{
auto l = std::distance(begin, end);
while (l > 0) {
auto half = l / 2;
auto mid = begin;
std::advance(mid, half);
if (K < *mid) {
l = half;
} else if (*mid < K) {
begin = mid;
++begin;
l = l - half - 1;
} else {
return true;
}
}
return false;
}
This algorithm is sometimes asked in interview questions
template <class Iter, class T>
bool binary_search_rotated(Iter begin, Iter end, const T& K)
{
auto is_sorted = [&](auto a, auto b)
{ return !(begin[b - 1] < begin[a]); };
auto in_range = [&](auto a, auto b)
{ return !(K < begin[a] || begin[b - 1] < K); };
auto low = 0;
auto high = end - begin;
while (low < high) {
auto mid = (low + high) / 2;
if (is_sorted(mid, high)) {
if (in_range(mid, high))
return rt::binary_search(begin + mid, begin + high, K);
high = mid;
} else {
if (in_range(low, mid))
return rt::binary_search(begin + low, begin + mid, K);
low = mid;
}
}
return false;
}
Some combinatorics algorithms.
Calculates the next tuple based on the current one. The first element in the array is used for convenience and does not belong in the tuple. Minimum and maximum values for each element in the tuple are passed in paramenter min and max.
template <class Iter>
auto next_tuple(Iter begin, Iter end, Iter min, Iter max)
{
auto j = end - begin - 1;
while (begin[j] == max[j]) {
begin[j] = min[j];
--j;
}
++begin[j];
return j != 0;
}
Example usage
void all_tuples()
{
std::vector<int> min {0, 1, 1, 1};
std::vector<int> max {min[0] + 1, 3, 2, 3};
auto arr = min;
do {
visit(arr);
} while (next_tuple( std::begin(arr), std::end(arr)
, std::begin(min), std::begin(max)));
}
An implementation that is suitable for the STL can be seen below
template <class Iter>
auto next_tuple(Iter begin, Iter end, Iter min, Iter max)
{
auto j = end - begin - 1;
while (begin[j] == max[j]) {
begin[j] = min[j];
--j;
}
++begin[j];
return j != 0;
}
auto next_combination(std::vector<int>& v)
{
int t = v.size() - 2;
auto i = 0;
while ((v[i] + 1) == v[i + 1]) {
v[i] = i;
++i;
}
if (i == t) return false;
++v[i];
return true;
}
Example usage
void all_combinations()
{
int n = 5;
std::vector<int> v {0, 1, 2, n, 0};
do {
visit_combination(v);
} while (next_combination(v));
}
Some algorithms that deal with permutations
template <class Iter>
bool next_permutation(Iter begin, Iter end)
{
auto b = end;
auto a = std::prev(b);
while (!(*--a < *--b));
if (a == begin) {
std::reverse(++a, end);
return false;
}
b = end;
while (!(*a < *--b));
std::iter_swap(a, b);
std::reverse(++a, end);
return true;
}
Example usage
void all_permutations()
{
std::vector<int> v{0, 1, 2, 3, 4};
do {
visit_permutation(v);
} while (rt::next_permutation(std::begin(v), std::end(v)));
}
template <class Iter>
void permute(Iter begin, Iter end, Iter perm)
{
auto n = end - begin;
for (auto i = 0; i < n; ++i) {
if (perm[i] != i) {
auto t = begin[i];
auto j = i;
do {
auto k = perm[j];
begin[j] = begin[k];
perm[j] = j;
j = k;
} while (perm[j] != i);
begin[j] = t;
perm[j] = j;
}
}
}
template <class Iter>
void unpermute(Iter begin, Iter end, Iter table)
{
auto n = end - begin;
for (auto i = 0; i < n; ++i) {
while (table[i] != i) {
std::swap(begin[i], begin[table[i]]);
std::swap(table[i], table[table[i]]);
}
}
}
const auto index = [=](auto i)
{ return c - 1 - (i % c) + c * (i / c); };
template <class Iter, class Index>
void unpermute_on_the_fly(Iter begin, Iter end, Index index)
{
const auto n = end - begin;
for (auto i = 0; i < n; ++i) {
auto k = index(i);
while (k > i)
k = index(k);
if (k == i) {
k = index(i);
while (k != i) {
std::swap(begin[i], begin[k]);
k = index(k);
}
}
}
}
constexpr auto bit = 1 << 30;
auto set_bit = [=](auto& o) {o |= bit;};
auto unset_bit = [=](auto& o) {o &= ~bit;};
auto is_set = [=](auto& o) {return o & bit;};
template <class Iter>
void unpermute_on_the_fly_bit(Iter begin, Iter end)
{
const auto n = end - begin;
for (auto i = 0; i < n; ++i) {
auto k = index(i);
if (!is_set(begin[i])) {
while (k != i) {
std::swap(begin[i], begin[k]);
set_bit(begin[k]);
k = index(k);
}
}
unset_bit(begin[i]);
}
}
Generates a random permutation with elements in the range [1, n].
template <class Rand>
auto random_permutation(int n, Rand& rand)
{
std::vector<int> ret(n);
for (auto j = 0; j < n; ++j) {
auto k = static_cast<int>((j + 1) * rand());
ret[j] = ret[k];
ret[k] = j + 1;
}
return ret;
}
Shuffles the input array. Expects a parameter rand that produces random numbers uniformily distributed in the interval [0, 1).
template <class Rand>
void shuffle(std::vector<int>& v, Rand& rand)
{
auto j = v.size();
do {
auto k = static_cast<int>(j * rand());
std::swap(v[k], v[--j]);
} while (j != 0);
}
The algorithm to calculate the next partition can be found in the source code, here I will show how to calculate them in a loop
void all_partitions_loop(int n)
{
std::vector<int> a(n + 1, 1);
a[0] = 0;
int m = 1;
for (;;) {
a[m] = n;
int q = m - (n == 1 ? 1 : 0);
for (;;) {
visit_partition(a, m);
if (a[q] != 2) break;
a[q--] = 1;
++m;
}
if (q == 0) return;
int x = a[q] - 1;
a[q] = x;
n = m - q + 1;
m = q + 1;
while (x < n) {
a[m++] = x;
n -= x;
}
}
}
Some elementary sorting algorithms
template <class Iter>
void bubble_sort(Iter begin, Iter end)
{
if (begin == end) return;
auto B = end - begin - 1;
while (B != 0) {
auto t = 0;
for (auto j = 0; j < B; ++j) {
if (begin[j] > begin[j + 1]) {
std::swap(begin[j], begin[j + 1]);
t = j + 1;
}
}
B = t;
}
}
template <class Iter>
auto calc_address_table(Iter begin, Iter end)
{
const auto n = end - begin;
std::vector<int> count(n, 0);
for (auto i = 1; i < n; ++i)
for (auto j = 0; j < i; ++j)
if (begin[i] < begin[j])
++count[j];
else
++count[i];
return count;
}
template <class Iter>
void comparison_counting_sort(Iter begin, Iter end)
{
auto n = end - begin;
auto count = calc_address_table(begin, end);
std::vector<int> tmp(n, 0);
for (auto i = 0; i < n; ++i)
tmp[count[i]] = begin[i];
std::copy(std::begin(tmp), std::end(tmp), begin);
}
template <class Iter>
void inplace_comparison_counting_sort(Iter begin, Iter end)
{
auto table = calc_address_table(begin, end);
unpermute(begin, end, std::begin(table));
}
template <class Iter>
void dist_counting_sort(Iter begin, Iter end, int min, int max)
{
auto n = end - begin;
auto m = max - min + 1;
std::vector<int> count(m, 0);
for (auto i = 0; i < n; ++i)
++count[begin[i] - min];
for (auto i = 1; i < m; ++i)
count[i] += count[i - 1];
std::vector<int> out(n, 0);
for (auto i = 0; i < n; ++i)
out[--count[begin[i] - min]] = begin[i];
std::copy(std::begin(out), std::end(out), begin);
}
template <class Iter>
void straight_insertion(Iter begin, Iter end)
{
if (begin == end) return;
auto N = end - begin;
for (auto j = 1; j < N; ++j) {
auto i = j - 1;
auto k = begin[j];
while (k < begin[i]) {
begin[i + 1] = begin[i];
if (--i < 0) break;
}
begin[i + 1] = k;
}
}
template <class Iter>
void binary_insertion(Iter begin, Iter end)
{
for (auto pos = begin; pos != end; ++pos)
std::rotate( std::upper_bound(begin, pos, *pos)
, pos
, std::next(pos));
}
This data structure came up in an interview
template<class K, class V>
struct interval_map {
std::map<K,V> map;
interval_map(V v = {}) : map({{std::numeric_limits<K>::min(), v}}) {}
void assign(const K& a, const K& b, const V& v)
{
auto comp = map.key_comp();
if (!comp(a, b))
return;
auto lbb = map.lower_bound(b);
auto vb = lbb == std::end(map) ? map.rbegin()->second
: comp(b, lbb->first) ? std::prev(lbb)->second
: lbb->second;
if (v == vb)
return;
auto lba = map.lower_bound(a);
auto va = lba == std::end(map) ? map.rbegin()->second
: lba == std::begin(map) ? lba->second
: std::prev(lba)->second;
if (v == va && lba != std::begin(map))
return;
map.erase( std::next(map.insert_or_assign(lba, a, v))
, map.insert_or_assign(lbb, b, vb));
}
const V& operator[](K const& key) const
{ return (--map.upper_bound(key))->second; }
};