diff --git a/01-tree_successor/cpp/Makefile b/01-tree_successor/cpp/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..30f0b25a40595cc4423ba7bd8ace6b324e74f677 --- /dev/null +++ b/01-tree_successor/cpp/Makefile @@ -0,0 +1,12 @@ +test: tree_successor_test + ./$< + +CXXFLAGS=-std=c++23 -O2 -Wall -Wextra -g -Wno-sign-compare + +tree_successor_test: tree_successor.h tree_successor_test.cpp test_main.cpp + $(CXX) $(CXXFLAGS) $^ -o $@ + +clean: + rm -f tree_successor_test + +.PHONY: clean test diff --git a/01-tree_successor/cpp/test_main.cpp b/01-tree_successor/cpp/test_main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f4aff0785f636b7fd0ea1a15aa69dafe06f290f --- /dev/null +++ b/01-tree_successor/cpp/test_main.cpp @@ -0,0 +1,43 @@ +#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; +} diff --git a/01-tree_successor/cpp/tree_successor.h b/01-tree_successor/cpp/tree_successor.h new file mode 100644 index 0000000000000000000000000000000000000000..106b5fc0365ea65887ab4fff71dc9d7dec568014 --- /dev/null +++ b/01-tree_successor/cpp/tree_successor.h @@ -0,0 +1,76 @@ +// A node of the tree +class Node { + public: + int key; + Node* left; + Node* right; + Node* parent; + + // Constructor + Node(int key, Node* parent=nullptr) { + this->key = key; + this->parent = parent; + this->left = nullptr; + this->right = nullptr; + } +}; + +// Binary tree +class Tree { + public: + // Pointer to root of the tree; nullptr if the tree is empty. + Node* root = nullptr; + + // Insert a key into the tree. + // If the key is already present, nothing happens. + // If the node is given, start searching a new position from that node. + Node* insert(int key, Node *node = nullptr) { + if (!root) { + root = new Node(key); + return root; + } + + if (!node) + node = root; + while (node->key != key) { + if (key < node->key) { + if (!node->left) + node->left = new Node(key, node); + node = node->left; + } else { + if (!node->right) + node->right = new Node(key, node); + node = node->right; + } + } + return node; + } + + // Return successor of the given node. + // + // The successor of a node is the node with the next higher key. + // Return nullptr if there is no such node. + // If the argument is nullptr, return the node with the smallest key. + Node* successor(Node* node) { + // TODO: Implement + } + + // Destructor to free all allocated memory. + ~Tree() { + Node* node = root; + while (node) { + Node* next; + if (node->left) { + next = node->left; + node->left = nullptr; + } else if (node->right) { + next = node->right; + node->right = nullptr; + } else { + next = node->parent; + delete node; + } + node = next; + } + } +}; diff --git a/01-tree_successor/cpp/tree_successor_test.cpp b/01-tree_successor/cpp/tree_successor_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..552762a5202ca4e15ca7d2dede05ec15447ef4df --- /dev/null +++ b/01-tree_successor/cpp/tree_successor_test.cpp @@ -0,0 +1,105 @@ +#include <algorithm> +#include <functional> +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +#include "tree_successor.h" + +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); + +void test(const vector<int>& sequence, Tree &tree) { + Node* node = tree.successor(nullptr); + for (const auto& element : sequence) { + EXPECT(node, "Expected successor " + to_string(element) + ", got nullptr"); + EXPECT(node->key == element, + "Expected successor " + to_string(element) + ", got " + to_string(node->key)); + node = tree.successor(node); + } + EXPECT(!node, "Expected no successor, got " + to_string(node->key)); +} + +vector<int> get_linear_sequence() { + vector<int> numbers; + for (int i = 0; i < 10000000; i++) + numbers.push_back((int)(7.13*i)); + return numbers; +} + +void test_path(bool right) { + vector<int> numbers = get_linear_sequence(); + Tree tree; + Node *node = nullptr; + if (right) + for (int key : numbers) + node = tree.insert(key, node); + else + for (int index = numbers.size() - 1; index >= 0; --index) + node = tree.insert(numbers[index], node); + + test(numbers, tree); +} + +void test_two_paths() { + vector<int> numbers = get_linear_sequence(); + Tree tree; + Node *node = nullptr; + for(size_t i = numbers.size()/2; i < numbers.size(); i++) + node = tree.insert(numbers[i], node); + node = nullptr; + for(int i = numbers.size()/2 - 1; i >= 0; i--) + node = tree.insert(numbers[i], node); + + test(numbers, tree); +} + +void test_sequence(vector<int> &&sequence) { + Tree tree; + for (const auto& element : sequence) + tree.insert(element); + + sort(sequence.begin(), sequence.end()); + test(sequence, tree); +} + +void test_random() { + vector<int> sequence = {997}; + for (int i = 2; i < 199999; i++) + sequence.push_back((sequence.back() * int64_t(997)) % 199999); + test_sequence(move(sequence)); +} + +void test_trivial() { + test_sequence({5}); + test_sequence({7,9}); + test_sequence({7,3}); + test_sequence({5,3,7}); +} + +void test_comb() { + vector<int> numbers = get_linear_sequence(); + Tree tree; + Node *node = nullptr; + for(size_t i = numbers.size()/2; i < numbers.size(); i++) + node = tree.insert(numbers[i], node); + node = nullptr; + for(int i = numbers.size()/2 - 1; i >= 0; i-=2) { + node = tree.insert(numbers[i-1], node); + tree.insert(numbers[i], node); + } + test(numbers, tree); +} + +vector<pair<string, function<void()>>> tests = { + { "trivial", test_trivial }, + { "right_path", []{ test_path(true); } }, + { "left_path", []{ test_path(false); } }, + { "random_tree", test_random }, + { "two_paths", test_two_paths }, + { "comb", test_comb } +}; diff --git a/01-tree_successor/python/tree_successor.py b/01-tree_successor/python/tree_successor.py new file mode 100644 index 0000000000000000000000000000000000000000..417ec9c04b3bd4d80bd2bc3a63a1000f0a91373a --- /dev/null +++ b/01-tree_successor/python/tree_successor.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +class Node: + """Node in a binary tree `Tree`""" + + def __init__(self, key, left=None, right=None, parent=None): + self.key = key + self.left = left + self.right = right + self.parent = parent + +class Tree: + """A simple binary search tree""" + + def __init__(self, root=None): + self.root = root + + def insert(self, key, node=None): + """Insert key into the tree. + + If the key is already present, do nothing. + If the node is given, start searching a new position from that node. + """ + if self.root is None: + self.root = Node(key) + return self.root + + if not node: + node = self.root + + while node.key != key: + if key < node.key: + if node.left is None: + node.left = Node(key, parent=node) + node = node.left + else: + if node.right is None: + node.right = Node(key, parent=node) + node = node.right + + return node + + def successor(self, node=None): + """Return successor of the given node. + + The successor of a node is the node with the next greater key. + Return None if there is no such node. + If the argument is None, return the node with the smallest key. + """ + # TODO: Implement + raise NotImplementedError diff --git a/01-tree_successor/python/tree_successor_test.py b/01-tree_successor/python/tree_successor_test.py new file mode 100644 index 0000000000000000000000000000000000000000..3415b8ff688ca141584343c5639bd1b905313614 --- /dev/null +++ b/01-tree_successor/python/tree_successor_test.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import sys + +from tree_successor import Tree + +def test_tree(tree, sequence): + node = tree.successor(None) + for element in sequence: + assert node is not None, "Expected successor {}, got None".format(element) + assert node.key == element, "Expected successor {}, got {}".format(element, node.key) + node = tree.successor(node) + assert node is None, "Expected no successor, got {}".format(node.key) + +def test_sequence(sequence): + tree = Tree() + for i in sequence: + tree.insert(i) + sequence.sort() + test_tree(tree, sequence) + +def test_trivial_tree(): + test_sequence([5]) + test_sequence([7,9]) + test_sequence([7,3]) + test_sequence([5,3,7]) + +def test_random_tree(): + test_sequence([pow(997, i, 199999) for i in range(1, 199999)]) + +def test_path(right): + sequence = [int(7.13*i) for i in range(1000000)] + tree = Tree() + node = None + sequence_insert = sequence if right else reversed(sequence) + for key in sequence_insert: + node = tree.insert(key, node) + test_tree(tree, sequence) + +def test_two_paths(): + sequence_left = [int(7.13*i) for i in range(1000000)] + sequence_right = [int(7.13*i) for i in range(1000000, 2000000)] + tree = Tree() + node = None + for key in sequence_right: + node = tree.insert(key, node) + node = None + for key in reversed(sequence_left): + node = tree.insert(key, node) + test_tree(tree, sequence_left + sequence_right) + +def test_comb(): + sequence = [int(7.13*i) for i in range(1000000)] + tree = Tree() + node = None + for i in range(len(sequence)//2, len(sequence)): + node = tree.insert(sequence[i], node) + node = None + for i in range(len(sequence)//2-1, 0, -2): + node = tree.insert(sequence[i-1], node) + tree.insert(sequence[i], node) + test_tree(tree, sequence) + +tests = [ + ("trivial", test_trivial_tree), + ("random_tree", test_random_tree), + ("right_path", lambda: test_path(True)), + ("left_path", lambda: test_path(False)), + ("two_paths", test_two_paths), + ("comb", test_comb), +] + +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)) diff --git a/01-tree_successor/task.md b/01-tree_successor/task.md new file mode 100644 index 0000000000000000000000000000000000000000..ec268c46e235ad886e1f3171e7188b97f0acbe3c --- /dev/null +++ b/01-tree_successor/task.md @@ -0,0 +1,17 @@ +# `tree_successor` + +Given an implementation of a simple binary search tree including parent +pointers, implement a `successor` method. The methods is given a node +and it should return another node of the tree with the next higher key +(i.e., the smallest of keys which are greater than the given one). + +- If there is no such node, it should return a null pointer. +- The given node can also be a null pointer, in which case the method should + return the smallest node in the tree. + +You can expect that `successor` method is never called on an empty tree. + +You should submit the file `tree_successor.*` (but not the +`tree_successor_test.*`). + +Source code templates can be found in [the git repository](https://gitlab.kam.mff.cuni.cz/datovky/assignments/-/tree/master).