Skip to content
Snippets Groups Projects
Commit 9d33e043 authored by David Mareček's avatar David Mareček
Browse files

ab tree

parent e0eea6d9
Branches
No related tags found
No related merge requests found
test: ab_tree_test
./$<
CXXFLAGS=-std=c++11 -O2 -Wall -Wextra -g -Wno-sign-compare
ab_tree_test: ab_tree_test.cpp ab_tree.h test_main.cpp
$(CXX) $(CXXFLAGS) $^ -o $@
clean:
rm -f ab_tree_test
.PHONY: clean test
#include <limits>
#include <vector>
#include <tuple>
#include <iostream>
using namespace std;
// If the condition is not true, report an error and halt.
#define EXPECT(condition, message) do { if (!(condition)) expect_failed(message); } while (0)
void expect_failed(const string& message);
/*** One node ***/
class ab_node {
public:
// Keys stored in this node and the corresponding children
// The vectors are large enough to accomodate one extra entry
// in overflowing nodes.
vector<ab_node *> children;
vector<int> keys;
ab_node *parent;
// If this node contains the given key, return true and set i to key's position.
// Otherwise return false and set i to the first key greater than the given one.
bool find_branch(int key, int &i)
{
i = 0;
while (i < keys.size() && keys[i] <= key) {
if (keys[i] == key)
return true;
i++;
}
return false;
}
// Insert a new key at posision i and add a new child between keys i and i+1.
void insert_branch(int i, int key, ab_node *child)
{
keys.insert(keys.begin() + i, key);
children.insert(children.begin() + i + 1, child);
}
// An auxiliary function for displaying a sub-tree under this node.
void show(int indent);
};
/*** Tree ***/
class ab_tree {
public:
int a; // Minimum allowed number of children
int b; // Maximum allowed number of children
ab_node *root; // Root node (even a tree with no keys has a root)
int num_nodes; // We keep track of how many nodes the tree has
// Create a new node and return a pointer to it.
ab_node *new_node(ab_node* parent)
{
ab_node *n = new ab_node;
n->keys.reserve(b);
n->children.reserve(b+1);
n->parent = parent;
num_nodes++;
return n;
}
// Delete a given node, assuming that its children have been already unlinked.
void delete_node(ab_node *n)
{
num_nodes--;
delete n;
}
// Constructor: initialize an empty tree with just the root.
ab_tree(int a, int b)
{
EXPECT(a >= 2 && b >= 2*a - 1, "Invalid values of a,b");
this->a = a;
this->b = b;
num_nodes = 0;
// The root has no keys and one null child pointer.
root = new_node(nullptr);
root->children.push_back(nullptr);
}
// An auxiliary function for deleting a subtree recursively.
void delete_tree(ab_node *n)
{
for (int i=0; i < n->children.size(); i++)
if (n->children[i])
delete_tree(n->children[i]);
delete_node(n);
}
// Destructor: delete all nodes.
~ab_tree()
{
delete_tree(root);
EXPECT(num_nodes == 0, "Memory leak detected: some nodes were not deleted");
}
// Find a key: returns true if it is present in the tree.
bool find(int key)
{
ab_node *n = root;
while (n) {
int i;
if (n->find_branch(key, i))
return true;
n = n->children[i];
}
return false;
}
// Display the tree on standard output in human-readable form.
void show();
// Check that the data structure satisfies all invariants.
void audit();
// Split the node into two nodes: move some children of n into
// a newly created node such that n contains exactly size children in the end.
// Return the new node and the key separating n and the new node.
virtual pair<ab_node*, int> split_node(ab_node* n, int size)
{
// FIXME: Implement
}
// Insert: add key to the tree (unless it was already present).
virtual void insert(int key)
{
// FIXME: Implement
}
};
#include <functional>
#include <cstdlib>
#include <vector>
#include "ab_tree.h"
// Debugging output: showing trees prettily on standard output.
void ab_tree::show()
{
root->show(0);
for (int i=0; i<70; i++)
cout << '=';
cout << endl;
}
void ab_node::show(int indent)
{
for (int i = children.size() - 1; i >= 0 ; i--) {
if (i < keys.size()) {
for (int j = 0; j < indent; j++)
cout << " ";
cout << keys[i] << endl;
}
if (children[i])
children[i]->show(indent+1);
}
}
// Invariant checks
void audit_subtree(ab_tree *tree, ab_node *n, ab_node* parent, int key_min, int key_max, int depth, int &leaf_depth)
{
if (!n) {
// Check that all leaves are on the same level.
if (leaf_depth < 0)
leaf_depth = depth;
else
EXPECT(depth == leaf_depth, "Leaves are not on the same level");
return;
}
// Check consistency of parent pointers
EXPECT(n->parent == parent, "Inconsistent parent pointers");
// The number of children must be in the allowed range.
if (depth > 0)
EXPECT(n->children.size() >= tree->a, "Too few children");
EXPECT(n->children.size() <= tree->b, "Too many children");
// We must have one more children than keys.
EXPECT(n->children.size() == n->keys.size() + 1, "Number of keys does not match number of children");
// Allow degenerate trees with 0 keys in the root.
if (n->children.size() == 1)
return;
// Check order of keys: they must be increasing and bounded by the keys on the higher levels.
for (int i = 0; i < n->keys.size(); i++) {
EXPECT(n->keys[i] >= key_min && n->keys[i] <= key_max, "Wrong key order");
EXPECT(i == 0 || n->keys[i-1] < n->keys[i], "Wrong key order");
}
// Call on children recursively.
for (int i = 0; i < n->children.size(); i++) {
int tmin, tmax;
if (i == 0)
tmin = key_min;
else
tmin = n->keys[i-1] + 1;
if (i < n->keys.size())
tmax = n->keys[i] - 1;
else
tmax = key_max;
audit_subtree(tree, n->children[i], n, tmin, tmax, depth+1, leaf_depth);
}
}
void ab_tree::audit()
{
EXPECT(root, "Tree has no root");
int leaf_depth = -1;
audit_subtree(this, root, nullptr, numeric_limits<int>::min(), numeric_limits<int>::max(), 0, leaf_depth);
}
// A basic test: insert a couple of keys and show how the tree evolves.
void test_basic()
{
cout << "## Basic test" << endl;
ab_tree t(2, 3);
vector<int> keys = { 3, 1, 4, 5, 9, 2, 6, 8, 7, 0 };
for (int k : keys) {
t.insert(k);
t.show();
t.audit();
EXPECT(t.find(k), "Inserted key disappeared");
}
for (int k : keys)
EXPECT(t.find(k), "Some keys are missing at the end");
}
// The main test: inserting a lot of keys and checking that they are really there.
// We will insert num_items keys from the set {1,...,range-1}, where range is a prime.
void test_main(int a, int b, int range, int num_items)
{
// Create a new tree.
cout << "## Test: a=" << a << " b=" << b << " range=" << range << " num_items=" << num_items << endl;
ab_tree t(a, b);
int key = 1;
int step = (int)(range * 1.618);
int audit_time = 1;
// Insert keys.
for (int i=1; i <= num_items; i++) {
t.insert(key);
// Audit the tree occasionally.
if (i == audit_time || i == num_items) {
// cout << "== Audit at " << i << endl;
// t.show();
t.audit();
audit_time = (int)(audit_time * 1.33) + 1;
}
key = (key + step) % range;
}
// Check that the tree contains exactly the items it should contain.
key = 1;
for (int i=1; i < range; i++) {
bool found = t.find(key);
// cout << "Step #" << i << ": find(" << key << ") = " << found << endl;
EXPECT(found == (i <= num_items), "Tree contains wrong keys");
key = (key + step) % range;
}
}
/*** A list of all tests ***/
vector<pair<string, function<void()>>> tests = {
{ "basic", [] { test_basic(); } },
{ "small-2,3", [] { test_main(2, 3, 997, 700); } },
{ "small-2,4", [] { test_main(2, 4, 997, 700); } },
{ "big-2,3", [] { test_main(2, 3, 999983, 700000); } },
{ "big-2,4", [] { test_main(2, 4, 999983, 700000); } },
{ "big-10,20", [] { test_main(10, 20, 999983, 700000); } },
{ "big-100,200", [] { test_main(100, 200, 999983, 700000); } },
};
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
using namespace std;
extern vector<pair<string, function<void()>>> tests;
void expect_failed(const string& message) {
cerr << "Test error: " << message << endl;
exit(1);
}
int main(int argc, char* argv[]) {
vector<string> required_tests;
if (argc > 1) {
required_tests.assign(argv + 1, argv + argc);
} else {
for (const auto& test : tests)
required_tests.push_back(test.first);
}
for (const auto& required_test : required_tests) {
bool found = false;
for (const auto& test : tests)
if (required_test == test.first) {
cerr << "Running test " << required_test << endl;
test.second();
found = true;
break;
}
if (!found) {
cerr << "Unknown test " << required_test << endl;
return 1;
}
}
return 0;
}
#!/usr/bin/env python3
class ABNode:
"""Single node in an ABTree.
Each node contains keys and children
(with one more children than there are keys).
We also store a pointer to node's parent (None for root).
"""
def __init__(self, keys = None, children = None, parent = None):
self.keys = keys if keys is not None else []
self.children = children if children is not None else []
self.parent = parent
def find_branch(self, key):
""" Try finding given key in this node.
If this node contains the given key, returns (True, key_position).
If not, returns (False, first_position_with_key_greater_than_the_given).
"""
i = 0
while (i < len(self.keys) and self.keys[i] < key):
i += 1
return (i < len(self.keys) and self.keys[i] == key, i)
def insert_branch(self, i, key, child):
""" Insert a new key and a given child between keys i and i+1."""
self.keys.insert(i, key)
self.children.insert(i + 1, child)
class ABTree:
"""A class representing the whole ABTree."""
def __init__(self, a, b):
assert a >= 2 and b >= 2 * a - 1, "Invalid values of a, b: {}, {}".format(a, b)
self.a = a
self.b = b
self.root = ABNode(children=[None])
def find(self, key):
"""Find a key in the tree.
Returns True if the key is present, False otherwise.
"""
node = self.root
while node:
found, i = node.find_branch(key)
if found: return True
node = node.children[i]
return False
def split_node(self, node, size):
"""Helper function for insert
Split node into two nodes such that original node contains first _size_ children.
Return new node and the key separating nodes.
"""
# TODO: Implement and use in insert method
raise NotImplementedError
def insert(self, key):
"""Add a given key to the tree, unless already present."""
# TODO: Implement
raise NotImplementedError
#!/usr/bin/env python3
import math
import sys
from ab_tree import ABNode, ABTree
def show(tree):
"""Show a tree."""
def show_node(node, indent):
for i in reversed(range(len(node.children))):
if i < len(node.keys):
print(" " * indent, node.keys[i], sep="")
if node.children[i]:
show_node(node.children[i], indent + 1)
show_node(tree.root, 0)
print("=" * 70)
def audit(tree):
"""Invariant check for the given tree."""
def audit_node(node, parent, key_min, key_max, depth, leaf_depth):
if not node:
# Check that all leaves are on the same level.
if leaf_depth is None:
leaf_depth = depth
assert depth == leaf_depth, "Leaves are not on the same level"
else:
# Check consistency of parent pointers
assert node.parent == parent, "Inconsistent parent pointers"
# The number of children must be in the allowed range.
assert depth == 0 or len(node.children) >= tree.a, "Too few children"
assert len(node.children) <= tree.b, "Too many children"
# We must have one more children than keys
assert len(node.children) == len(node.keys) + 1, "Number of keys does not match number of children"
# Check that keys are increasing and in (key_min, key_max) range.
for i in range(len(node.keys)):
assert node.keys[i] > key_min and node.keys[i] < key_max, "Wrong key order"
assert i == 0 or node.keys[i - 1] < node.keys[i], "Wrong key order"
# Check children recursively
for i in range(len(node.children)):
child_min = node.keys[i - 1] if i > 0 else key_min
child_max = node.keys[i] if i < len(node.keys) else key_max
leaf_depth = audit_node(node.children[i], node, child_min, child_max, depth + 1, leaf_depth)
return leaf_depth
assert tree.root, "Tree has no root"
audit_node(tree.root, None, -math.inf, math.inf, 0, None)
def test_basic():
"""Insert a couple of keys and show how the tree evolves."""
print("## Basic test")
tree = ABTree(2, 3)
keys = [3, 1, 4, 5, 9, 2, 6, 8, 7, 0]
for key in keys:
tree.insert(key)
show(tree)
audit(tree)
assert tree.find(key), "Inserted key disappeared"
for key in keys:
assert tree.find(key), "Some keys are missing at the end"
def test_main(a, b, limit, num_items):
print("## Test: a={} b={} range={} num_items={}".format(a, b, limit, num_items))
tree = ABTree(a, b)
# Insert keys
step = int(limit * 1.618)
key, audit_time = 1, 1
for i in range(num_items):
tree.insert(key)
key = (key + step) % limit
# Audit the tree occasionally
if i == audit_time or i + 1 == num_items:
audit(tree)
audit_time = int(audit_time * 1.33) + 1
# Check the content of the tree
key = 1
for i in range(limit):
assert tree.find(key) == (i < num_items), "Tree contains wrong keys"
key = (key + step) % limit
tests = [
("basic", test_basic),
("small-2,3", lambda: test_main(2, 3, 997, 700)),
("small-2,4", lambda: test_main(2, 4, 997, 700)),
("big-2,3", lambda: test_main(2, 3, 99991, 70000)),
("big-2,4", lambda: test_main(2, 4, 99991, 70000)),
("big-10,20", lambda: test_main(10, 20, 99991, 70000)),
("big-100,200", lambda: test_main(100, 200, 99991, 70000)),
]
if __name__ == "__main__":
for required_test in sys.argv[1:] or [name for name, _ in tests]:
for name, test in tests:
if name == required_test:
print("Running test {}".format(name), file=sys.stderr)
test()
break
else:
raise ValueError("Unknown test {}".format(name))
You are given a representation of _(a, b)-tree_ with a `find` operation,
and a representation of an _(a, b)-tree node_.
Your goal is to implement an `insert` operation, which inserts the given
key in the tree (or does nothing if the key is already present). Preferably,
you should also implement `split_node` method and use it properly in
your `insert` implementation.
The implementation uses the variant of (a,b)-trees from lecture notes by [Martin Mares,
Chapter 3](http://mj.ucw.cz/vyuka/dsnotes/03-abtree.pdf) where the actual values are
stored also in the internal nodes of the tree and not only in leaves.
You should submit the `ab_tree.*` file (but not `ab_tree_test.*` files).
Source code templates can be found in [git](https://gitlab.kam.mff.cuni.cz/datovky/assignments/-/tree/master).
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment