1
0
Fork 0

C++ datastructures for O(n*m*log(n))

This commit is contained in:
Joris van Rantwijk 2023-06-16 19:55:43 +02:00
parent e103a493fc
commit efb238ff8e
3 changed files with 1784 additions and 0 deletions

View File

@ -21,6 +21,11 @@ test_mwmatching: DBGFLAGS = -fsanitize=address -fsanitize=undefined
test_mwmatching: test_mwmatching.cpp mwmatching.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LIB_BOOST_TEST)
test_datastruct: OPTFLAGS = -O1
test_datastruct: DBGFLAGS = -fsanitize=address -fsanitize=undefined
test_datastruct: test_datastruct.cpp datastruct.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $< $(LIB_BOOST_TEST)
.PHONY: clean
clean:
$(RM) run_matching run_matching_dbg test_mwmatching

1119
cpp/datastruct.h Normal file

File diff suppressed because it is too large Load Diff

660
cpp/test_datastruct.cpp Normal file
View File

@ -0,0 +1,660 @@
/*
* Unit tests for data structures.
*
* Depends on the Boost.Test unit test framework.
* Tested with Boost v1.74, available from https://www.boost.org/
*/
#include <algorithm>
#include <climits>
#include <map>
#include <memory>
#include <random>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#define BOOST_TEST_MODULE datastruct
#include <boost/test/unit_test.hpp>
#include "datastruct.h"
/* ********** Test DisjointSetNode ********** */
BOOST_AUTO_TEST_SUITE(test_disjoint_set)
BOOST_AUTO_TEST_CASE(test_single)
{
using Node = DisjointSetNode<int>;
Node a(1);
BOOST_TEST(a.find() == 1);
a.set_label(2);
BOOST_TEST(a.find() == 2);
}
BOOST_AUTO_TEST_CASE(test_simple)
{
using Node = DisjointSetNode<int>;
Node a(1);
Node b(2);
Node c(3);
Node* m = a.merge(&b);
m->set_label(10);
BOOST_TEST(a.find() == 10);
BOOST_TEST(b.find() == 10);
BOOST_TEST(c.find() == 3);
m = m->merge(&c);
m->set_label(11);
BOOST_TEST(a.find() == 11);
BOOST_TEST(c.find() == 11);
a.detach(1);
b.detach(2);
c.detach(3);
BOOST_TEST(a.find() == 1);
BOOST_TEST(b.find() == 2);
BOOST_TEST(c.find() == 3);
}
BOOST_AUTO_TEST_CASE(test_multilevel)
{
using Node = DisjointSetNode<int>;
std::unique_ptr<Node> nodes[27];
for (int i = 0; i < 27; ++i) {
nodes[i].reset(new Node(i));
}
std::vector<Node*> level1;
for (int i = 0; i < 9; ++i) {
Node* m = nodes[3*i].get();
for (int k = 1; k < 3; ++k) {
m = m->merge(nodes[3*i+k].get());
}
m->set_label(100 + i);
level1.push_back(m);
}
std::vector<Node*> level2;
for (int i = 0; i < 3; ++i) {
Node* m = level1[3*i];
for (int k = 1; k < 3; ++k) {
m = m->merge(level1[3*i+k]);
}
m->set_label(200 + i);
level2.push_back(m);
}
Node* m = level2[0];
for (int k = 1; k < 3; ++k) {
m = m->merge(level2[k]);
}
m->set_label(300);
for (int i = 0; i < 27; ++i) {
BOOST_TEST(nodes[i]->find() == 300);
}
for (int i = 0; i < 3; ++i) {
level2[i]->detach(200 + i);
}
for (int i = 0; i < 27; ++i) {
BOOST_TEST(nodes[i]->find() == 200 + i / 9);
}
for (int i = 0; i < 6; ++i) {
level1[i]->detach(100 + i);
}
for (int i = 0; i < 18; ++i) {
BOOST_TEST(nodes[i]->find() == 100 + i / 3);
}
for (int i = 18; i < 27; ++i) {
BOOST_TEST(nodes[i]->find() == 202);
}
for (int i = 6; i < 9; ++i) {
level1[i]->detach(100 + i);
}
for (int i = 0; i < 27; ++i) {
BOOST_TEST(nodes[i]->find() == 100 + i / 3);
}
for (int i = 6; i < 27; ++i) {
nodes[i]->detach(i);
}
for (int i = 0; i < 6; ++i) {
BOOST_TEST(nodes[i]->find() == 100 + i / 3);
}
for (int i = 6; i < 27; ++i) {
BOOST_TEST(nodes[i]->find() == i);
}
for (int i = 0; i < 6; i++) {
nodes[i]->detach(i);
}
for (int i = 0; i < 27; ++i) {
BOOST_TEST(nodes[i]->find() == i);
}
}
BOOST_AUTO_TEST_CASE(test_random)
{
using Node = DisjointSetNode<int>;
std::mt19937 rng(12345);
const int num_nodes = 1000;
std::unique_ptr<Node> nodes[num_nodes];
for (int i = 0; i < num_nodes; ++i) {
nodes[i].reset(new Node(i));
}
std::unordered_map<int, Node*> blossoms;
std::unordered_map<int, std::vector<int>> sub_blossoms;
std::vector<int> top_blossoms;
for (int i = 0; i < num_nodes; ++i) {
blossoms[i] = nodes[i].get();
top_blossoms.push_back(i);
}
int next_blossom = num_nodes;
auto make_blossom = [&]() {
int b = next_blossom;
++next_blossom;
int nsub = 1 + 2 * std::uniform_int_distribution<>(1, 4)(rng);
std::vector<int> subs(nsub);
for (int i = 0; i < nsub; ++i) {
int p = std::uniform_int_distribution<>(0, top_blossoms.size() - 1)(rng);
subs[i] = top_blossoms[p];
top_blossoms.erase(top_blossoms.begin() + p);
}
Node *m = blossoms[subs[0]];
for (int i = 1; i < nsub; ++i) {
m = m->merge(blossoms[subs[i]]);
}
m->set_label(b);
blossoms[b] = m;
sub_blossoms[b] = std::move(subs);
top_blossoms.push_back(b);
};
auto expand_blossom = [&](int b) {
top_blossoms.erase(
std::find(top_blossoms.begin(), top_blossoms.end(), b));
for (int t : sub_blossoms[b]) {
blossoms[t]->detach(t);
top_blossoms.push_back(t);
}
blossoms.erase(b);
sub_blossoms.erase(b);
};
auto check_membership = [&](int b, int label) {
std::vector<int> q;
q.push_back(b);
while (! q.empty()) {
b = q.back();
q.pop_back();
if (b < num_nodes) {
BOOST_TEST(nodes[b]->find() == label);
} else {
for (int t : sub_blossoms[b]) {
q.push_back(t);
}
}
}
};
for (int k = 0; k < 100; ++k) {
make_blossom();
}
for (int b : top_blossoms) {
check_membership(b, b);
}
std::vector<int> top_groups;
for (int b : top_blossoms) {
if (b >= num_nodes) {
top_groups.push_back(b);
}
}
std::shuffle(top_groups.begin(), top_groups.end(), rng);
for (int k = 0; k < 50; ++k) {
expand_blossom(top_groups[k]);
}
top_groups.clear();
for (int b : top_blossoms) {
check_membership(b, b);
}
for (int k = 0; k < 50; ++k) {
make_blossom();
}
for (int b : top_blossoms) {
check_membership(b, b);
}
for (int b : top_blossoms) {
if (b >= num_nodes) {
top_groups.push_back(b);
}
}
std::shuffle(top_groups.begin(), top_groups.end(), rng);
for (int b : top_groups) {
expand_blossom(b);
}
for (int b : top_blossoms) {
check_membership(b, b);
}
}
BOOST_AUTO_TEST_SUITE_END()
/* ********** Test SplittablePriorityQueue ********** */
template <typename PrioType, typename DataType>
static void check_min_elem(const std::pair<PrioType, DataType>& pair,
PrioType prio,
const DataType& data)
{
BOOST_TEST(pair.first == prio);
BOOST_TEST(pair.second == data);
}
BOOST_AUTO_TEST_SUITE(test_splittable_queue)
BOOST_AUTO_TEST_CASE(test_single)
{
using Queue = SplittablePriorityQueue<int, int>;
Queue q;
BOOST_TEST(q.empty() == true);
Queue::Node n;
q.insert(&n, 3, 4, 101);
BOOST_TEST(q.empty() == false);
check_min_elem(q.find_min(), 4, 101);
q.update(&n, 5, 102);
check_min_elem(q.find_min(), 4, 101);
q.update(&n, 3, 103);
check_min_elem(q.find_min(), 3, 103);
q.clear();
BOOST_TEST(q.empty() == true);
}
BOOST_AUTO_TEST_CASE(test_simple)
{
using Queue = SplittablePriorityQueue<int, std::string>;
Queue q;
Queue::Node n1, n2, n3, n4, n5, nx;
q.insert(&n1, 1, 5, "a");
q.insert(&n2, 2, 6, "b");
q.insert(&n3, 3, 7, "c");
q.insert(&n4, 4, 4, "d");
q.insert(&n5, 5, 3, "e");
check_min_elem(q.find_min(), 3, std::string("e"));
q.update(&n1, 4, "f");
check_min_elem(q.find_min(), 3, std::string("e"));
q.update(&n3, 2, "h");
check_min_elem(q.find_min(), 2, std::string("h"));
Queue q2 = q.split(3);
check_min_elem(q.find_min(), 4, std::string("f"));
check_min_elem(q2.find_min(), 2, std::string("h"));
q.insert(&nx, 3, 1, "x");
check_min_elem(q.find_min(), 1, std::string("x"));
check_min_elem(q2.find_min(), 2, std::string("h"));
q.clear();
q2.clear();
}
BOOST_AUTO_TEST_CASE(test_split_empty)
{
using Queue = SplittablePriorityQueue<int, std::string>;
Queue q;
Queue q2 = q.split(10);
BOOST_TEST(q.empty() == true);
BOOST_TEST(q2.empty() == true);
}
BOOST_AUTO_TEST_CASE(test_split_oneway)
{
using Queue = SplittablePriorityQueue<int, std::string>;
Queue q;
Queue::Node n4, n5, n6;
q.insert(&n4, 4, 3, "a");
q.insert(&n5, 5, 4, "b");
q.insert(&n6, 6, 2, "c");
Queue q2 = q.split(7);
BOOST_TEST(q.empty() == false);
BOOST_TEST(q2.empty() == true);
check_min_elem(q.find_min(), 2, std::string("c"));
q.clear();
q.insert(&n4, 4, 3, "a");
q.insert(&n5, 5, 4, "b");
q.insert(&n6, 6, 2, "c");
q2 = q.split(4);
BOOST_TEST(q.empty() == true);
BOOST_TEST(q2.empty() == false);
check_min_elem(q2.find_min(), 2, std::string("c"));
q2.clear();
}
BOOST_AUTO_TEST_CASE(test_larger)
{
using Queue = SplittablePriorityQueue<int, std::string>;
Queue q;
Queue::Node nodes[15];
q.insert(&nodes[7], 7, 5, "h");
q.insert(&nodes[6], 6, 4, "g");
q.insert(&nodes[8], 8, 2, "i");
q.insert(&nodes[5], 5, 4, "f");
q.insert(&nodes[9], 9, 6, "j");
q.insert(&nodes[4], 4, 8, "e");
q.insert(&nodes[10], 10, 4, "k");
q.insert(&nodes[3], 3, 5, "d");
q.insert(&nodes[11], 11, 6, "l");
q.insert(&nodes[2], 2, 7, "c");
q.insert(&nodes[12], 12, 8, "m");
q.insert(&nodes[1], 1, 3, "b");
q.insert(&nodes[13], 13, 1, "n");
q.insert(&nodes[0], 0, 9, "a");
q.insert(&nodes[14], 14, 7, "o");
check_min_elem(q.find_min(), 1, std::string("n"));
Queue q2 = q.split(10);
check_min_elem(q.find_min(), 2, std::string("i"));
check_min_elem(q2.find_min(), 1, std::string("n"));
Queue q1 = q.split(5);
check_min_elem(q.find_min(), 3, std::string("b"));
check_min_elem(q1.find_min(), 2, std::string("i"));
check_min_elem(q2.find_min(), 1, std::string("n"));
q.clear();
q1.clear();
q2.clear();
}
BOOST_AUTO_TEST_CASE(test_random)
{
using Queue = SplittablePriorityQueue<int, int>;
std::mt19937 rng(23456);
std::uniform_int_distribution<> index_distribution(0, 1000000);
std::uniform_int_distribution<> prio_distribution(0, 1000000);
Queue q;
std::map<int, std::tuple<std::unique_ptr<Queue::Node>, int, int>> elems;
int next_data = 1;
for (int i = 0; i < 200; ++i) {
// Insert stuff into the queue.
for (int k = 0; k < 1000; ++k) {
int idx = index_distribution(rng);
int prio = prio_distribution(rng);
int data = next_data;
++next_data;
auto it = elems.find(idx);
if (it != elems.end()) {
auto& v = it->second;
Queue::Node* nptr = std::get<0>(v).get();
int pprio = std::get<1>(v);
q.update(nptr, prio, data);
if (prio < pprio) {
std::get<1>(v) = prio;
std::get<2>(v) = data;
}
} else {
std::unique_ptr<Queue::Node> nptr(new Queue::Node);
q.insert(nptr.get(), idx, prio, data);
elems[idx] = std::make_tuple(std::move(nptr), prio, data);
}
}
// Check min element.
int min_prio = INT_MAX;
int min_data = 0;
for (const auto& v : elems) {
int prio = std::get<1>(v.second);
int data = std::get<2>(v.second);
if (prio < min_prio) {
min_prio = prio;
min_data = data;
}
}
check_min_elem(q.find_min(), min_prio, min_data);
// Split the queue.
int threshold = index_distribution(rng);
Queue q2 = q.split(threshold);
// Keep one queue and discard the other.
if (rng() % 2 == 0) {
q.clear();
q = std::move(q2);
elems.erase(elems.begin(), elems.lower_bound(threshold));
} else {
q2.clear();
elems.erase(elems.lower_bound(threshold), elems.end());
}
// Check min element.
min_prio = INT_MAX;
min_data = 0;
for (const auto& v : elems) {
int prio = std::get<1>(v.second);
int data = std::get<2>(v.second);
if (prio < min_prio) {
min_prio = prio;
min_data = data;
}
}
if (min_prio < INT_MAX) {
check_min_elem(q.find_min(), min_prio, min_data);
}
}
q.clear();
}
BOOST_AUTO_TEST_SUITE_END()
/* ********** Test PriorityQueue ********** */
BOOST_AUTO_TEST_SUITE(test_priority_queue)
BOOST_AUTO_TEST_CASE(test_empty)
{
using Queue = PriorityQueue<int, std::string>;
Queue q;
BOOST_TEST(q.empty() == true);
}
BOOST_AUTO_TEST_CASE(test_single)
{
using Queue = PriorityQueue<int, std::string>;
Queue q;
Queue::Node n1;
q.insert(&n1, 5, "a");
BOOST_TEST(q.empty() == false);
check_min_elem(q.find_min(), 5, std::string("a"));
q.update(&n1, 3, "a");
check_min_elem(q.find_min(), 3, std::string("a"));
q.remove(&n1);
BOOST_TEST(q.empty() == true);
}
BOOST_AUTO_TEST_CASE(test_simple)
{
using Queue = PriorityQueue<int, char>;
Queue q;
Queue::Node nodes[10];
q.insert(&nodes[0], 9, 'a');
check_min_elem(q.find_min(), 9, 'a');
q.insert(&nodes[1], 4, 'b');
check_min_elem(q.find_min(), 4, 'b');
q.insert(&nodes[2], 7, 'c');
check_min_elem(q.find_min(), 4, 'b');
q.insert(&nodes[3], 5, 'd');
check_min_elem(q.find_min(), 4, 'b');
q.insert(&nodes[4], 8, 'e');
check_min_elem(q.find_min(), 4, 'b');
q.insert(&nodes[5], 6, 'f');
check_min_elem(q.find_min(), 4, 'b');
q.insert(&nodes[6], 4, 'g');
q.insert(&nodes[7], 5, 'h');
q.insert(&nodes[8], 2, 'i');
check_min_elem(q.find_min(), 2, 'i');
q.insert(&nodes[9], 6, 'j');
check_min_elem(q.find_min(), 2, 'i');
q.update(&nodes[2], 1, 'c');
check_min_elem(q.find_min(), 1, 'c');
q.update(&nodes[4], 3, 'e');
check_min_elem(q.find_min(), 1, 'c');
q.remove(&nodes[2]);
check_min_elem(q.find_min(), 2, 'i');
q.remove(&nodes[8]);
check_min_elem(q.find_min(), 3, 'e');
q.remove(&nodes[4]);
q.remove(&nodes[1]);
check_min_elem(q.find_min(), 4, 'g');
q.remove(&nodes[3]);
q.remove(&nodes[9]);
check_min_elem(q.find_min(), 4, 'g');
q.remove(&nodes[6]);
check_min_elem(q.find_min(), 5, 'h');
BOOST_TEST(q.empty() == false);
q.clear();
BOOST_TEST(q.empty() == true);
}
BOOST_AUTO_TEST_CASE(test_random)
{
using Queue = PriorityQueue<int, int>;
Queue q;
const int num_elem = 1000;
std::vector<std::tuple<std::unique_ptr<Queue::Node>, int, int>> elems;
int next_data = 0;
std::mt19937 rng(34567);
auto check = [&q,&elems]() {
int min_prio, min_data;
std::tie(min_prio, min_data) = q.find_min();
int best_prio = INT_MAX;
bool found = false;
for (const auto& v : elems) {
int this_prio = std::get<1>(v);
int this_data = std::get<2>(v);
best_prio = std::min(best_prio, this_prio);
if ((this_prio == min_prio) && (this_data == min_data)) {
found = true;
}
}
BOOST_TEST(found == true);
BOOST_TEST(min_prio == best_prio);
};
for (int i = 0; i < num_elem; ++i) {
++next_data;
int prio = std::uniform_int_distribution<>(0, 1000000)(rng);
std::unique_ptr<Queue::Node> nptr(new Queue::Node);
q.insert(nptr.get(), prio, next_data);
elems.push_back(std::make_tuple(std::move(nptr), prio, next_data));
check();
}
for (int i = 0; i < 10000; ++i) {
int p = std::uniform_int_distribution<>(0, num_elem - 1)(rng);
Queue::Node* node = std::get<0>(elems[p]).get();
int prio = std::get<1>(elems[p]);
int data = std::get<2>(elems[p]);
prio = std::uniform_int_distribution<>(0, prio)(rng);
q.update(node, prio, data);
std::get<1>(elems[p]) = prio;
check();
p = std::uniform_int_distribution<>(0, num_elem - 1)(rng);
node = std::get<0>(elems[p]).get();
q.remove(node);
elems.erase(elems.begin() + p);
check();
++next_data;
prio = std::uniform_int_distribution<>(0, 1000000)(rng);
std::unique_ptr<Queue::Node> nptr(new Queue::Node);
q.insert(nptr.get(), prio, next_data);
elems.push_back(std::make_tuple(std::move(nptr), prio, next_data));
check();
}
for (int i = 0; i < num_elem; ++i) {
int p = std::uniform_int_distribution<>(0, num_elem - 1 - i)(rng);
Queue::Node* node = std::get<0>(elems[p]).get();
q.remove(node);
elems.erase(elems.begin() + p);
if (! elems.empty()) {
check();
}
}
BOOST_TEST(q.empty() == true);
}
BOOST_AUTO_TEST_SUITE_END()