diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d1ce1c6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +project(heaps CXX) +set(CMAKE_CXX_STANDARD 17) + +include(GNUInstallDirs) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + +# we use this to get code coverage +if(CMAKE_CXX_COMPILER_ID MATCHES GNU) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") +endif() + +add_subdirectory(src/FibonacciHeap) + +include(cmake/googletest.cmake) +fetch_googletest( + ${PROJECT_SOURCE_DIR}/cmake + ${PROJECT_BINARY_DIR}/googletest +) + +enable_testing() +add_subdirectory(test) + diff --git a/README.md b/README.md index 1292fe6..227710c 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# heapsLab \ No newline at end of file +# gtest-example-linkedlist +Example of usage of Google Test library. Made as a part of course Algorithms and Data Structures at DIHT MIPT. diff --git a/cmake/googletest-download.cmake b/cmake/googletest-download.cmake new file mode 100644 index 0000000..f580295 --- /dev/null +++ b/cmake/googletest-download.cmake @@ -0,0 +1,20 @@ +# code copied from https://crascit.com/2015/07/25/cmake-gtest/ +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) + +project(googletest-download NONE) + +include(ExternalProject) + +ExternalProject_Add( + googletest + SOURCE_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-src" + BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build" + GIT_REPOSITORY + https://github.com/google/googletest.git + GIT_TAG + release-1.8.0 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/cmake/googletest.cmake b/cmake/googletest.cmake new file mode 100644 index 0000000..de0c3bb --- /dev/null +++ b/cmake/googletest.cmake @@ -0,0 +1,32 @@ +# the following code to fetch googletest +# is inspired by and adapted after https://crascit.com/2015/07/25/cmake-gtest/ +# download and unpack googletest at configure time + +macro(fetch_googletest _download_module_path _download_root) + set(GOOGLETEST_DOWNLOAD_ROOT ${_download_root}) + configure_file( + ${_download_module_path}/googletest-download.cmake + ${_download_root}/CMakeLists.txt + @ONLY + ) + unset(GOOGLETEST_DOWNLOAD_ROOT) + + execute_process( + COMMAND + "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY + ${_download_root} + ) + execute_process( + COMMAND + "${CMAKE_COMMAND}" --build . + WORKING_DIRECTORY + ${_download_root} + ) + + # adds the targers: gtest, gtest_main, gmock, gmock_main + add_subdirectory( + ${_download_root}/googletest-src + ${_download_root}/googletest-build + ) +endmacro() diff --git a/src/FibonacciHeap/CMakeLists.txt b/src/FibonacciHeap/CMakeLists.txt new file mode 100644 index 0000000..54e9cd9 --- /dev/null +++ b/src/FibonacciHeap/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(fibonacciHeap) + +target_sources( + fibonacciHeap + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/FibonacciHeap.hpp +) + +target_include_directories( + fibonacciHeap + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +set_target_properties(fibonacciHeap PROPERTIES LINKER_LANGUAGE CXX) \ No newline at end of file diff --git a/src/FibonacciHeap/FibonacciHeap.hpp b/src/FibonacciHeap/FibonacciHeap.hpp new file mode 100644 index 0000000..b8a69ee --- /dev/null +++ b/src/FibonacciHeap/FibonacciHeap.hpp @@ -0,0 +1,350 @@ +// +// Created by runfme on 13.11.18. +// + +#include +#include +#include + +template +class FibonacciHeap { + private: + class TreeNode; + class Pointer; + + public: + FibonacciHeap() { + size_ = 0; + min_root = nullptr; + } + + bool empty() { + return size_ == 0; + } + + unsigned long size() { + return size_; + } + + std::shared_ptr insert(T value) { + auto new_root = TreeNode::create_node(value); + concatenate_node_lists(min_root, new_root); + ++size_; + + if (new_root->value < min_root->value) { + min_root = new_root; + } + + return new_root->get_pointer(); + } + + T min() { + if (empty()) { + throw std::runtime_error("Heap is empty"); + } + + return min_root->value; + } + + T extract_min() { + if (empty()) { + throw std::runtime_error("Heap is empty"); + } + + auto extracted_node = min_root; + extract_node(min_root); + --size_; + + if (min_root != nullptr) { + consolidate(); + } + find_new_min(); + + T return_value = extracted_node->value; + extracted_node->get_pointer()->valid = false; + + return return_value; + } + + void remove(std::shared_ptr pointer) { + if (!pointer->is_valid()) { + throw std::runtime_error("Invalidated pointer to an element"); + } + auto node = pointer->node; + auto parent = node->parent; + + extract_node(node); + --size_; + cascading_cut(parent); + find_new_min(); + + pointer->valid = false; + } + + void decrease_key(std::shared_ptr pointer, T new_value) { + if (!pointer->is_valid()) { + throw std::runtime_error("Invalidated pointer to an element"); + } + if (new_value > pointer->value()) { + throw std::invalid_argument("Value can only be decreased"); + } + + auto node = pointer->node; + auto parent = node->parent; + + cut_out_node(node); + node->value = new_value; + concatenate_node_lists(min_root, node); + + cascading_cut(parent); + find_new_min(); + } + + void merge(FibonacciHeap &heap) { + if (heap.min_root != nullptr) { + concatenate_node_lists(min_root, heap.min_root); + size_ += heap.size_; + find_new_min(); + + heap.min_root = nullptr; + heap.size_ = 0; + } + } + + ~FibonacciHeap() { + recursive_delete(min_root); + } + + private: + unsigned long size_; + std::shared_ptr min_root; + + void static concatenate_node_lists(std::shared_ptr &first_list, std::shared_ptr second_list) { + if (second_list == nullptr) { + throw std::invalid_argument("Can not append empty list"); + } + + if (first_list == nullptr) { + first_list = second_list; + } else { + first_list->left_sib->right_sib = second_list; + second_list->left_sib->right_sib = first_list; + + std::swap(first_list->left_sib, second_list->left_sib); + } + } + + void cut_out_node(std::shared_ptr node) { + auto parent = node->parent; + if (node->parent != nullptr) { + node->parent = nullptr; + } + + if (node->left_sib == node) { + if (parent == nullptr) { + min_root = nullptr; + } else { + parent->child = nullptr; + } + } else { + if (parent != nullptr) { + parent->child = node->right_sib; + } else if (node == min_root) { + min_root = node->right_sib; + } + + node->left_sib->right_sib = node->right_sib; + node->right_sib->left_sib = node->left_sib; + + node->left_sib = node->right_sib = node; + } + } + + unsigned long max_tree_degree() { + unsigned long highest_bit = 0; + while ((size_ >> highest_bit) > 0) { + ++highest_bit; + } + + return highest_bit; + } + + void consolidate() { + if (min_root == nullptr) { + throw std::runtime_error("Can not consolidate empty heap"); + } + + unsigned long highest_bit = max_tree_degree(); + std::shared_ptr roots[highest_bit]; + for (unsigned long i = 0; i < highest_bit; ++i) { + roots[i] = nullptr; + } + + while (min_root != nullptr) { + auto cur_root = min_root; + cut_out_node(min_root); + + while (roots[cur_root->degree] != nullptr) { + auto second_root = roots[cur_root->degree]; + roots[cur_root->degree] = nullptr; + + cur_root = cur_root->hang(second_root); + } + roots[cur_root->degree] = cur_root; + } + + for (unsigned long j = 0; j < highest_bit; ++j) { + if (roots[j] != nullptr) { + concatenate_node_lists(min_root, roots[j]); + if (roots[j]->value < min_root->value) { + min_root = roots[j]; + } + } + } + } + + void add_children_as_roots(std::shared_ptr node) { + if (node->child != nullptr) { + auto cur_child = node->child; + do { + cur_child->parent = nullptr; + cur_child->mark = false; + cur_child = cur_child->right_sib; + } while (cur_child != node->child); + + concatenate_node_lists(min_root, node->child); + node->child = nullptr; + } + } + + void find_new_min() { + if (min_root != nullptr) { + auto cur_root = min_root; + auto new_min = cur_root; + + do { + if (cur_root->value < new_min->value) { + new_min = cur_root; + } + cur_root = cur_root->right_sib; + } while (cur_root != min_root); + + min_root = new_min; + } + } + + void extract_node(std::shared_ptr node) { + cut_out_node(node); + add_children_as_roots(node); + } + + void recursive_delete(std::shared_ptr node) { + if (node == nullptr) { + return; + } + recursive_delete(node->child); + node->left_sib->right_sib = nullptr; + recursive_delete(node->right_sib); + } + + void cascading_cut(std::shared_ptr node) { + if (node == nullptr) { + return; + } + if (node->parent == nullptr) { + node->degree -= 1; + } + + if (node->mark) { + auto parent = node->parent; + + cut_out_node(node); + node->mark = false; + concatenate_node_lists(min_root, node); + + cascading_cut(parent); + } else { + node->mark = true; + node->degree -= 1; + } + } + + class TreeNode { + public: + friend class FibonacciHeap; + T value; + bool mark; + std::shared_ptr left_sib; + std::shared_ptr right_sib; + std::shared_ptr parent; + std::shared_ptr child; + unsigned long degree; + + static std::shared_ptr create_node(T value) { + auto new_node = std::shared_ptr(new TreeNode(value)); + new_node->pointer = std::shared_ptr(new Pointer(new_node)); + new_node->left_sib = new_node->right_sib = new_node; + + return new_node; + } + + std::shared_ptr hang(std::shared_ptr second_tree) { + if (degree != second_tree->degree) { + throw std::runtime_error("Trees should have the same degree to hang one to another"); + } + + std::shared_ptr root_tree = get_pointer()->node; + if (second_tree->value < root_tree->value) { + std::swap(root_tree, second_tree); + } + + concatenate_node_lists(root_tree->child, second_tree); + second_tree->parent = root_tree; + + return root_tree; + } + + std::shared_ptr get_pointer() { + return pointer; + } + + private: + std::shared_ptr pointer; + + explicit TreeNode(T value) { + this->value = value; + degree = 0; + mark = false; + pointer = nullptr; + + parent = nullptr; + child = nullptr; + } + }; + + class Pointer { + friend class FibonacciHeap; + public: + explicit Pointer(std::shared_ptr node) { + this->node = node; + this->valid = true; + } + + bool is_valid() { + return valid; + } + + T value() { + if (!is_valid()) { + throw std::runtime_error("Invalidated pointer to an element"); + } + return node->value; + } + private: + std::shared_ptr node; + bool valid; + }; + +}; + + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..e1fbbdb --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.9) + +add_executable( + unit_tests + fibonacci_heap_test.cpp + main.cpp) + +target_link_libraries( + unit_tests + gtest_main + fibonacciHeap +) + +add_test( + NAME + unit + COMMAND + ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/unit_tests +) diff --git a/test/fibonacci_heap_test.cpp b/test/fibonacci_heap_test.cpp new file mode 100644 index 0000000..76f4f5f --- /dev/null +++ b/test/fibonacci_heap_test.cpp @@ -0,0 +1,158 @@ +// +// Created by runfme on 16.11.18. +// + +#define BOOST_TEST_MAIN +#define BOOST_TEST_MODULE testFibonacciHeap + +#include "../src/FibonacciHeap/FibonacciHeap.hpp" +#include + +TEST(FibonacciHeapBasics, Constructor) { + FibonacciHeap heap = FibonacciHeap(); + EXPECT_EQ(heap.empty(), true); +} + +TEST(FibonacciHeapBasics, Empty) { + FibonacciHeap heap = FibonacciHeap(); + EXPECT_EQ(heap.empty(), true); + + heap.insert(1); + EXPECT_EQ(heap.empty(), false); +} + +TEST(FibonacciHeapBasics, Insert) { + FibonacciHeap heap = FibonacciHeap(); + auto pointer = heap.insert(1); + EXPECT_EQ(pointer->value(), 1); +} + +TEST(FibonacciHeapBasics, GetMin) { + FibonacciHeap heap = FibonacciHeap(); + auto pointer = heap.insert(1); + heap.insert(2); + heap.insert(5); + heap.insert(0); + heap.insert(3); + EXPECT_EQ(heap.min(), 0); +} + +TEST(FibonacciHeapBasics, ExtractMin) { + FibonacciHeap heap = FibonacciHeap(); + heap.insert(2); + heap.insert(3); + heap.insert(5); + heap.insert(10); + heap.insert(3); + heap.insert(11); + EXPECT_EQ(heap.extract_min(), 2); + EXPECT_EQ(heap.extract_min(), 3); + EXPECT_EQ(heap.extract_min(), 3); + heap.insert(1); + EXPECT_EQ(heap.extract_min(), 1); + EXPECT_EQ(heap.extract_min(), 5); + EXPECT_EQ(heap.extract_min(), 10); + EXPECT_EQ(heap.extract_min(), 11); + + EXPECT_EQ(heap.size(), 0); +} + +TEST(FibonacciHeapBasics, Size) { + FibonacciHeap heap = FibonacciHeap(); + EXPECT_EQ(heap.size(), 0); + heap.insert(1); + EXPECT_EQ(heap.size(), 1); + heap.insert(2); + heap.insert(6); + heap.insert(1); + EXPECT_EQ(heap.size(), 4); + heap.extract_min(); + heap.extract_min(); + EXPECT_EQ(heap.size(), 2); +} + +TEST(FibonacciHeapBasics, Delete) { + FibonacciHeap heap = FibonacciHeap(); + auto pointer_to_1 = heap.insert(1); + heap.remove(pointer_to_1); + EXPECT_EQ(pointer_to_1->is_valid(), false); + EXPECT_EQ(heap.size(), 0); + + heap.insert(3); + auto pointer_to_0 = heap.insert(0); + heap.insert(1); + heap.remove(pointer_to_0); + EXPECT_EQ(pointer_to_0->is_valid(), false); + EXPECT_EQ(heap.min(), 1); +} + +TEST(FibonacciHeapAdvanced, Change) { + FibonacciHeap heap = FibonacciHeap(); + heap.insert(1); + heap.insert(3); + auto pointer_to_2 = heap.insert(2); + heap.insert(1); + + heap.decrease_key(pointer_to_2, 0); + EXPECT_EQ(pointer_to_2->value(), 0); + EXPECT_EQ(heap.min(), 0); + + heap.decrease_key(pointer_to_2, -1); + EXPECT_EQ(pointer_to_2->value(), -1); + EXPECT_EQ(heap.min(), -1); +} + +TEST(FibonacciHeapAdvanced, MergeEmptyToNonEmpty) { + FibonacciHeap heap1 = FibonacciHeap(); + FibonacciHeap heap2 = FibonacciHeap(); + heap1.insert(1); + heap1.insert(3); + + heap1.merge(heap2); + EXPECT_EQ(heap1.min(), 1); + EXPECT_EQ(heap1.size(), 2); + EXPECT_EQ(heap2.size(), 0); +} + +TEST(FibonacciHeapAdvanced, MergeNonEmptyToEmpty) { + FibonacciHeap heap1 = FibonacciHeap(); + FibonacciHeap heap2 = FibonacciHeap(); + heap1.insert(1); + heap1.insert(3); + + heap2.merge(heap1); + EXPECT_EQ(heap2.min(), 1); + EXPECT_EQ(heap2.size(), 2); + EXPECT_EQ(heap1.size(), 0); +} + +TEST(FibonacciHeapAdvanced, MergeNonEmptyToNonEmpty) { + FibonacciHeap heap1 = FibonacciHeap(); + FibonacciHeap heap2 = FibonacciHeap(); + heap1.insert(1); + heap1.insert(3); + + heap2.insert(0); + heap2.insert(4); + + heap1.merge(heap2); + EXPECT_EQ(heap1.min(), 0); + EXPECT_EQ(heap1.size(), 4); + EXPECT_EQ(heap2.size(), 0); +} + +TEST(FibonacciHeapExceptions, RequestsToEmptyHeap) { + FibonacciHeap heap = FibonacciHeap(); + + EXPECT_THROW(heap.min(), std::runtime_error); + EXPECT_THROW(heap.extract_min(), std::runtime_error); +} + +TEST(FibonacciHeapExceptions, RequestsToInvalidatedPointer) { + FibonacciHeap heap = FibonacciHeap(); + auto pointer_to_1 = heap.insert(1); + heap.remove(pointer_to_1); + + EXPECT_THROW(heap.remove(pointer_to_1), std::runtime_error); + EXPECT_THROW(heap.decrease_key(pointer_to_1, 10), std::runtime_error); +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..5b08882 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,11 @@ +// +// Created by sashka on 19.10.18. +// + +#include "gtest/gtest.h" + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}