diff --git a/additional_tasks/DSU/CMakeLists.txt b/additional_tasks/DSU/CMakeLists.txt new file mode 100644 index 0000000..cb87577 --- /dev/null +++ b/additional_tasks/DSU/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.10) + +get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME}) +project(${PROJECT_NAME} C CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp") +file(GLOB_RECURSE lib_source_list "../../lib/src/*.cpp" "../../lib/src/*.hpp") +file(GLOB_RECURSE main_source_list "src/main.cpp") +file(GLOB_RECURSE test_source_list "src/*.cpp") +file(GLOB_RECURSE test_list "src/*test.cpp") + +list(REMOVE_ITEM test_source_list ${main_source_list}) +list(REMOVE_ITEM source_list ${test_list}) + +include_directories(${PROJECT_NAME} PUBLIC src) + +add_executable(${PROJECT_NAME} ${source_list} ${lib_source_list}) + +# Locate GTest +enable_testing() +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIRS}) + +find_library(Utils ../) +target_link_libraries(${PROJECT_NAME} PUBLIC Utils) + +# Link runTests with what we want to test and the GTest and pthread library +add_executable(${PROJECT_NAME}_tests ${test_source_list}) +target_link_libraries( + ${PROJECT_NAME}_tests + GTest::gtest_main + Utils +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}_tests) diff --git a/additional_tasks/template_task/README.md b/additional_tasks/DSU/README.md similarity index 100% rename from additional_tasks/template_task/README.md rename to additional_tasks/DSU/README.md diff --git a/additional_tasks/DSU/src/DSU.cpp b/additional_tasks/DSU/src/DSU.cpp new file mode 100644 index 0000000..dbb095f --- /dev/null +++ b/additional_tasks/DSU/src/DSU.cpp @@ -0,0 +1,65 @@ +#include "DSU.hpp" + +using namespace std; + +DSU::DSU(int n) : parent(n), rank(n, 0) { + for (int i = 0; i < n; i++) parent[i] = i; +} + +int DSU::find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); // Path compression + } + return parent[x]; +} + +void DSU::unite(int x, int y) { + int rootX = find(x); + int rootY = find(y); + if (rootX != rootY) { + if (rank[rootX] > rank[rootY]) { + parent[rootY] = rootX; + } else if (rank[rootX] < rank[rootY]) { + parent[rootX] = rootY; + } else { + parent[rootY] = rootX; + rank[rootX]++; + } + } +} + +bool Edge::operator<(const Edge& other) const { return weight < other.weight; } + +int kruskalMST(int n, vector& edges) { + sort(edges.begin(), edges.end()); + DSU dsu(n); + int mstWeight = 0, edgesUsed = 0; + + for (const Edge& edge : edges) { + if (dsu.find(edge.u) != dsu.find(edge.v)) { + dsu.unite(edge.u, edge.v); + mstWeight += edge.weight; + edgesUsed++; + if (edgesUsed == n - 1) break; + } + } + + return (edgesUsed == n - 1) ? mstWeight : -1; // -1 if MST is not possible +} + +int main_part(int& n, int& m, vector>& vec) { + vector edges(m); + for (int i = 0; i < m; i++) { + edges[i].u = vec[i][0] - 1; // Уменьшаем на 1 для 0-индексации + edges[i].v = vec[i][1] - 1; // Уменьшаем на 1 для 0-индексации + edges[i].weight = vec[i][2]; + } + + int result = kruskalMST(n, edges); + if (result != -1) { + return result; + } else { + throw NotMST("MST does not exist"); + } + return 0; +} diff --git a/additional_tasks/DSU/src/DSU.hpp b/additional_tasks/DSU/src/DSU.hpp new file mode 100644 index 0000000..286e83b --- /dev/null +++ b/additional_tasks/DSU/src/DSU.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +using namespace std; + +class NotMST : public logic_error { + using logic_error::logic_error; +}; + +class DSU { + public: + DSU(int n); + int find(int x); + void unite(int x, int y); + + private: + vector parent; + vector rank; +}; + +struct Edge { + int u, v, weight; + bool operator<(const Edge& other) const; +}; + +int kruskalMST(int n, vector& edges); + +int main_part(int& n, int& m, vector>& vec); \ No newline at end of file diff --git a/additional_tasks/DSU/src/main.cpp b/additional_tasks/DSU/src/main.cpp new file mode 100644 index 0000000..0e4393b --- /dev/null +++ b/additional_tasks/DSU/src/main.cpp @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/additional_tasks/DSU/src/test.cpp b/additional_tasks/DSU/src/test.cpp new file mode 100644 index 0000000..b6bfd73 --- /dev/null +++ b/additional_tasks/DSU/src/test.cpp @@ -0,0 +1,61 @@ +#include + +#include "DSU.hpp" + +using namespace std; + +TEST(Template, Simple) { + int vertices = 3; + int edge = 3; + vector> edges = {{1, 2, 4}, {2, 3, 5}, {1, 3, 3}}; + + int const result = main_part(vertices, edge, edges); + int expected = 7; + ASSERT_EQ(result, expected); +} + +TEST(Error, Simple) { + int vertices = 3; + int edge = 1; + vector> edges = {{1, 2, 4}}; + + EXPECT_THROW(main_part(vertices, edge, edges), NotMST); +} + +TEST(Template, NoEdges) { + int vertices = 5; + int edge = 0; + vector> edges = {}; + + EXPECT_THROW(main_part(vertices, edge, edges), NotMST); +} + +TEST(Template, MultipleEdgesSameWeight) { + int vertices = 4; + int edge = 5; + vector> edges = { + {1, 2, 1}, {2, 3, 1}, {3, 4, 1}, {1, 4, 1}, {2, 4, 1}}; + + int const result = main_part(vertices, edge, edges); + int const expected = 3; // MST can include edges with weight 1 + ASSERT_EQ(result, expected); +} + +TEST(Template, DisconnectedGraph) { + int vertices = 4; + int edge = 2; + vector> edges = {{1, 2, 1}, {3, 4, 1}}; + + EXPECT_THROW(main_part(vertices, edge, edges), NotMST); +} + +TEST(Template, LargerGraph) { + int vertices = 6; + int edge = 8; + vector> edges = {{1, 2, 4}, {1, 3, 1}, {2, 3, 2}, {2, 4, 5}, + {3, 4, 8}, {4, 5, 3}, {5, 6, 7}, {4, 6, 6}}; + + int const result = main_part(vertices, edge, edges); + int const expected = 17; // Minimum spanning tree weight + ASSERT_EQ(result, expected); +} diff --git a/additional_tasks/template_task/src/main.cpp b/additional_tasks/template_task/src/main.cpp index 76e8197..9ec81e0 100644 --- a/additional_tasks/template_task/src/main.cpp +++ b/additional_tasks/template_task/src/main.cpp @@ -1 +1,3 @@ -int main() { return 0; } +#include + +int main() { return 0; } \ No newline at end of file diff --git a/doc/DSU.md b/doc/DSU.md new file mode 100644 index 0000000..19fb019 --- /dev/null +++ b/doc/DSU.md @@ -0,0 +1,32 @@ +# Система Непересекающихся Множеств (Disjoint Set Union, DSU) + +## Система Непересекающихся Множеств (или DSU) — это структура данных, позволяющая работать с множеством непересекающихся подмножеств и эффективно выполнять две ключевые операции: + +1. Объединение (union): соединяет два множества в одно. +2. Нахождение представителя (find): возвращает представителя множества, к которому принадлежит элемент. + +Эта структура данных часто применяется в задачах на графы, таких как построение минимального остовного дерева или поиск компонент связности. + +## Основные идеи + +Для повышения эффективности операций используются два подхода: + +- Сжатие пути (Path Compression): при нахождении представителя для каждого элемента мы устанавливаем прямой путь к представителю, что значительно ускоряет последующие операции find. +- Случайный приоритет (Union by Rank/Size): при объединении двух множеств меньшее множество добавляется к большему, что уменьшает высоту дерева и снижает сложность операций. + +## Асимптотика работы + +С применением сжатия пути и объединения по рангу операции find и union выполняются за амортизированное время O(α(n)), где α — обратная функция Аккермана, которая для практически всех значимых значений n меньше 5. + +## Построение минимального остовного дерева с использованием DSU + +Алгоритм Крускала использует DSU для построения минимального остовного дерева (МСТ) в графе. Основная идея алгоритма заключается в последовательном добавлении ребер с наименьшим весом, избегая образования циклов. DSU помогает проверить, находятся ли два узла в одном компоненте, чтобы избежать циклов. + +## Шаги алгоритма Крускала + +1. Отсортировать все ребра графа по возрастанию веса. +2. Создать DSU для отслеживания компонентов связности. +3. Инициализировать переменные для хранения общего веса минимального остовного дерева. +4. Для каждого ребра в отсортированном списке: + - Если его концы принадлежат разным компонентам, добавить это ребро в МСТ и объединить компоненты. +5. Алгоритм завершен, когда в МСТ добавлено ровно V - 1 ребер (где V — количество вершин). \ No newline at end of file