Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions additional_tasks/DSU/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
65 changes: 65 additions & 0 deletions additional_tasks/DSU/src/DSU.cpp
Original file line number Diff line number Diff line change
@@ -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<Edge>& 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<vector<int>>& vec) {
vector<Edge> 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;
}
32 changes: 32 additions & 0 deletions additional_tasks/DSU/src/DSU.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>

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<int> parent;
vector<int> rank;
};

struct Edge {
int u, v, weight;
bool operator<(const Edge& other) const;
};

int kruskalMST(int n, vector<Edge>& edges);

int main_part(int& n, int& m, vector<vector<int>>& vec);
3 changes: 3 additions & 0 deletions additional_tasks/DSU/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <iostream>

int main() { return 0; }
61 changes: 61 additions & 0 deletions additional_tasks/DSU/src/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <gtest/gtest.h>

#include "DSU.hpp"

using namespace std;

TEST(Template, Simple) {
int vertices = 3;
int edge = 3;
vector<vector<int>> edges = {{1, 2, 4}, {2, 3, 5}, {1, 3, 3}};

int const result = main_part(vertices, edge, edges);
int expected = 7;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'expected' of type 'int' can be declared 'const' [misc-const-correctness]

Suggested change
int expected = 7;
int const expected = 7;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'expected' of type 'int' can be declared 'const' [misc-const-correctness]

Suggested change
int expected = 7;
int const expected = 7;

ASSERT_EQ(result, expected);
}

TEST(Error, Simple) {
int vertices = 3;
int edge = 1;
vector<vector<int>> edges = {{1, 2, 4}};

EXPECT_THROW(main_part(vertices, edge, edges), NotMST);
}

TEST(Template, NoEdges) {
int vertices = 5;
int edge = 0;
vector<vector<int>> edges = {};

EXPECT_THROW(main_part(vertices, edge, edges), NotMST);
}

TEST(Template, MultipleEdgesSameWeight) {
int vertices = 4;
int edge = 5;
vector<vector<int>> 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<vector<int>> 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<vector<int>> 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);
}
4 changes: 3 additions & 1 deletion additional_tasks/template_task/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
int main() { return 0; }
#include <iostream>

int main() { return 0; }
32 changes: 32 additions & 0 deletions doc/DSU.md
Original file line number Diff line number Diff line change
@@ -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 — количество вершин).