diff --git a/lib/src/graph.cpp b/lib/src/graph.cpp new file mode 100644 index 0000000..b58dd7f --- /dev/null +++ b/lib/src/graph.cpp @@ -0,0 +1,197 @@ +#include "graph.hpp" + +#include +#include + +#define UNREACHABLE (-1) + +using namespace algo; + +Graph::Graph(int v, int e) { + if (v <= 0) + throw std::invalid_argument( + "Number of vertices must be greater than zero."); + + vertexes_num = v; + edges_num = e; + adjList.resize(v); +} + +Graph::Graph(int v, int e, bool directed) { + if (v <= 0) + throw std::invalid_argument( + "Number of vertices must be greater than zero."); + + vertexes_num = v; + edges_num = e; + is_directed = directed; + adjList.resize(v); +} + +void Graph::AddEdge(int u, int v, int weight) { + if (u < 1 || u > vertexes_num || v < 1 || v > vertexes_num) { + throw std::out_of_range("Vertex out of bounds in addEdge."); + } + + adjList[u - 1].emplace_back(v - 1, weight); + if (!is_directed) adjList[v - 1].emplace_back(u - 1, weight); + + edges_num++; +} + +int Graph::GetVertexesNum() { + if (vertexes_num <= 0) + throw std::runtime_error("Graph is not properly initialized."); + return vertexes_num; +} + +int Graph::GetEdgesNum() { return edges_num; } + +const AdjacencyList Graph::GetAdjList() { return adjList; } + +void Graph::PrintGraph() const { + if (vertexes_num == 0) { + std::cerr << "Error: Graph is empty." << std::endl; + return; + } + + for (int i = 0; i < vertexes_num; i++) { + if (adjList[i].empty()) continue; + + std::cout << "Node " << i + 1 << ": "; + + for (const auto& [neighbor, weight] : adjList[i]) { + std::cout << "(" << neighbor + 1 << ", " << weight << ") "; + } + std::cout << '\n'; + } +} + +std::vector> Graph::GetNeighbours(int v) { + if (v < 0 || v >= vertexes_num) { + throw std::out_of_range("Vertex index out of bounds in getNeighbours."); + } + return adjList[v]; +} + +void Graph::TopSort(int v, std::vector& visited, std::vector& way) { + if (visited[v]) return; + visited[v] = true; + way.push_back(v); + + for (auto [u, w] : GetNeighbours(v)) { + if (!visited[u]) { + TopSort(u, visited, way); + } + } +} + +std::vector Graph::TopologicalSort(int start) { + if (vertexes_num == 0) { + std::cerr + << "Error: Graph is empty, topological sort cannot be performed.\n"; + return {}; + } + + std::vector way; + std::vector visited(GetVertexesNum(), false); + + TopSort(start, visited, way); + + if (way.size() < vertexes_num) + throw std::invalid_argument("Graph is disconnected."); + + return way; +} + +std::vector> Graph::GetBridges() { + std::vector tin(vertexes_num, -1); + std::vector low(vertexes_num, -1); + std::vector visited(vertexes_num, false); + std::vector> bridges; + int timer = 0; + + for (int i = 0; i < vertexes_num; i++) { + if (!visited[i]) { + DfsBridges(i, -1, tin, low, visited, timer, bridges); + } + } + + return bridges; +} + +void Graph::DfsBridges(int v, int parent, std::vector& tin, + std::vector& low, std::vector& visited, + int& timer, std::vector>& bridges) { + visited[v] = true; + tin[v] = low[v] = timer++; + + for (auto [u, _] : adjList[v]) { + if (u == parent) continue; + + if (!visited[u]) { + DfsBridges(u, v, tin, low, visited, timer, bridges); + low[v] = std::min(low[v], low[u]); + + if (low[u] > tin[v]) { + bridges.emplace_back(v + 1, u + 1); // Добавляем мост (нумерация с 1) + } + } else { + low[v] = std::min(low[v], tin[u]); + } + } +} + +std::vector Graph::GetArticulationPoints() { + std::vector tin(vertexes_num, -1); + std::vector low(vertexes_num, -1); + std::vector visited(vertexes_num, false); + std::vector isArticulationPoint(vertexes_num, false); + int timer = 0; + + for (int i = 0; i < vertexes_num; i++) { + if (!visited[i]) { + DfsArticulation(i, -1, tin, low, visited, timer, isArticulationPoint); + } + } + + std::vector articulationPoints; + for (int i = 0; i < vertexes_num; i++) { + if (isArticulationPoint[i]) { + articulationPoints.push_back(i + 1); // Нумерация с 1 + } + } + + return articulationPoints; +} + +void Graph::DfsArticulation(int v, int parent, std::vector& tin, + std::vector& low, std::vector& visited, + int& timer, + std::vector& isArticulationPoint) { + visited[v] = true; + tin[v] = low[v] = timer++; + int children = 0; + + for (auto [u, _] : adjList[v]) { + if (u == parent) continue; + + if (!visited[u]) { + children++; + DfsArticulation(u, v, tin, low, visited, timer, isArticulationPoint); + low[v] = std::min(low[v], low[u]); + + // Условие для точки сочленения + if (parent != -1 && low[u] >= tin[v]) { + isArticulationPoint[v] = true; + } + } else { + low[v] = std::min(low[v], tin[u]); + } + } + + // Отдельное условие для корня + if (parent == -1 && children > 1) { + isArticulationPoint[v] = true; + } +} diff --git a/lib/src/graph.hpp b/lib/src/graph.hpp new file mode 100644 index 0000000..16891b7 --- /dev/null +++ b/lib/src/graph.hpp @@ -0,0 +1,51 @@ +#pragma once + +#ifndef GRAPH_HPP +#define GRAPH_HPP + +#include + +using AdjacencyList = std::vector>>; + +namespace algo { +class Graph { + public: + Graph(int v, int e); + + Graph(int v, int e, bool directed); + + void AddEdge(int, int, int); + + int GetVertexesNum(); + int GetEdgesNum(); + const AdjacencyList GetAdjList(); + + std::vector> GetNeighbours(int); + + bool HasEdge(int, int) const; + + void PrintGraph() const; + + std::vector TopologicalSort(int); + + std::vector> GetBridges(); + std::vector GetArticulationPoints(); + + private: + int vertexes_num = 0; + int edges_num = 0; + bool is_directed = false; + + AdjacencyList adjList; + + void TopSort(int, std::vector&, std::vector&); + + void DfsBridges(int, int, std::vector&, std::vector&, + std::vector&, int&, std::vector>&); + + void DfsArticulation(int, int, std::vector&, std::vector&, + std::vector&, int&, std::vector&); +}; +}; // namespace algo + +#endif // GRAPH_HPP diff --git a/lib/src/util.cpp b/lib/src/util.cpp index 81e15bd..ceac1d5 100644 --- a/lib/src/util.cpp +++ b/lib/src/util.cpp @@ -1 +1,44 @@ #include "util.hpp" + +#include +#include +#include +#include + +#include "graph.hpp" + +using namespace std; + +vector FindWays(algo::Graph graph, int start) { + vector vis(graph.GetVertexesNum()); + vector dst(graph.GetVertexesNum(), INT_MAX); + + dst[start - 1] = 0; + + Dijkstra(start - 1, -1, vis, dst, graph.GetAdjList()); + + return dst; +} + +void Dijkstra(int v, int from, vector& vis, vector& dst, + const AdjacencyList adj) { + using Pair = pair; + + priority_queue, greater> q; + + q.push({0, v}); + while (!q.empty()) { + auto [cur_d, v] = q.top(); + q.pop(); + + if (cur_d > dst[v]) continue; + + for (auto [u, w] : adj[v]) { + if (dst[u] > dst[v] + w) { + dst[u] = dst[v] + w; + + q.push({dst[u], u}); + } + } + } +} diff --git a/lib/src/util.hpp b/lib/src/util.hpp index e69de29..f0d1b03 100644 --- a/lib/src/util.hpp +++ b/lib/src/util.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "graph.hpp" + +std::vector FindWays(algo::Graph, int); + +void Dijkstra(int, int, std::vector&, std::vector&, + const AdjacencyList); \ No newline at end of file diff --git a/task_01/src/main.cpp b/task_01/src/main.cpp index 76e8197..e2a9af2 100644 --- a/task_01/src/main.cpp +++ b/task_01/src/main.cpp @@ -1 +1,25 @@ -int main() { return 0; } +#include + +#include "graph.hpp" + +int main() { + int vertexes_num, edges_num; + std::cin >> vertexes_num >> edges_num; + + algo::Graph graph(vertexes_num, edges_num); + + for (int i = 0; i < edges_num; i++) { + int a, b, w; + + std::cin >> a >> b >> w; + + graph.AddEdge(a, b, w); + } + + std::cout << "Topsort:" << std::endl; + for (auto i : graph.TopologicalSort(0)) { + std::cout << i + 1 << " "; + } + + std::cout << std::endl; +} diff --git a/task_01/src/test.cpp b/task_01/src/test.cpp index 87cef73..9e6109b 100644 --- a/task_01/src/test.cpp +++ b/task_01/src/test.cpp @@ -1,5 +1,85 @@ #include -TEST(Test, Simple) { - ASSERT_EQ(1, 1); // Stack [] -} \ No newline at end of file +#include "graph.hpp" + +using namespace algo; + +TEST(GraphTest, TopSort) { + Graph graph(6, 5); + + graph.AddEdge(1, 2, 1); + graph.AddEdge(1, 3, 2); + graph.AddEdge(2, 4, 3); + graph.AddEdge(3, 5, 4); + graph.AddEdge(4, 6, 5); + + std::vector expected = {0, 1, 3, 5, 2, 4}; // Индексы вершин + + auto calc = graph.TopologicalSort(0); + + ASSERT_EQ(expected, calc); +} + +TEST(GraphTest, TopSortEmpty) { + EXPECT_THROW( + { + try { + Graph graph(0, 0); + graph.TopologicalSort(0); + } catch (const std::invalid_argument& e) { + ASSERT_STREQ(e.what(), + "Number of vertices must be greater than zero."); + throw; + } + }, + std::invalid_argument); +} + +TEST(GraphTest, TopologicalSort_Disconnected) { + Graph graph(5, 2); + + graph.AddEdge(1, 2, 1); + graph.AddEdge(3, 4, 2); + + EXPECT_THROW( + { + try { + graph.TopologicalSort(0); + } catch (const std::invalid_argument& e) { + ASSERT_STREQ(e.what(), "Graph is disconnected."); + throw; + } + }, + std::invalid_argument); +} + +TEST(GraphTest, TopologicalSort_LinearChain) { + Graph graph(5, 4); + + graph.AddEdge(1, 2, 1); + graph.AddEdge(2, 3, 2); + graph.AddEdge(3, 4, 3); + graph.AddEdge(4, 5, 4); + + std::vector expected_order = {0, 1, 2, 3, + 4}; // Ожидаемый линейный порядок + + auto actual_order = graph.TopologicalSort(0); + + ASSERT_EQ(expected_order, actual_order); +} + +TEST(GraphTest, TopologicalSort_EmptyEdgeList) { + Graph graph(3, 0); // Граф без рёбер + + EXPECT_THROW( + { + try { + graph.TopologicalSort(0); + } catch (const std::invalid_argument& e) { + ASSERT_STREQ(e.what(), "Graph is disconnected."); + throw; + } + }, + std::invalid_argument); +} diff --git a/task_02/src/main.cpp b/task_02/src/main.cpp index 0e4393b..56a031c 100644 --- a/task_02/src/main.cpp +++ b/task_02/src/main.cpp @@ -1,3 +1,29 @@ #include -int main() { return 0; } +#include "graph.hpp" + +using namespace algo; + +int main() { + Graph graph(5, 5, false); + graph.AddEdge(1, 2, 0); + graph.AddEdge(1, 3, 0); + graph.AddEdge(3, 4, 0); + graph.AddEdge(4, 5, 0); + graph.AddEdge(4, 2, 0); + + auto bridges = graph.GetBridges(); + auto articulationPoints = graph.GetArticulationPoints(); + + std::cout << "Bridges:\n"; + for (auto [u, v] : bridges) { + std::cout << u << " - " << v << '\n'; + } + + std::cout << "Articulation Points:\n"; + for (int v : articulationPoints) { + std::cout << v << '\n'; + } + + return 0; +} \ No newline at end of file diff --git a/task_02/src/test.cpp b/task_02/src/test.cpp index 54e7ce9..5372d8d 100644 --- a/task_02/src/test.cpp +++ b/task_02/src/test.cpp @@ -1,10 +1,11 @@ #include -#include - +#include "graph.hpp" #include "stack.hpp" +using namespace algo; + TEST(StackTest, Simple) { Stack stack; stack.Push(1); // Stack [1] @@ -39,4 +40,73 @@ TEST(MinStackTest, Simple) { ASSERT_EQ(stack.GetMin(), 1); ASSERT_EQ(stack.Pop(), 3); // Stack [1] ASSERT_EQ(stack.Pop(), 1); // Stack [] -} \ No newline at end of file +} + +// МОСТЫ + +// Тест для поиска мостов +TEST(GraphTest, getBridges) { + Graph graph(5, 0, false); + graph.AddEdge(1, 2, 0); + graph.AddEdge(1, 3, 0); + graph.AddEdge(3, 4, 0); + graph.AddEdge(4, 5, 0); + + auto bridges = graph.GetBridges(); + + // Ожидаем следующие мосты + std::vector> expectedBridges = { + {1, 2}, {1, 3}, {3, 4}, {4, 5}}; + + EXPECT_EQ(bridges.size(), expectedBridges.size()); + for (const auto& bridge : expectedBridges) { + EXPECT_NE(std::find(bridges.begin(), bridges.end(), bridge), bridges.end()); + } +} + +// Тест для поиска точек сочленения +TEST(GraphTest, getArticulationPoints) { + Graph graph(5, 0, false); + graph.AddEdge(1, 2, 0); + graph.AddEdge(1, 3, 0); + graph.AddEdge(3, 4, 0); + graph.AddEdge(4, 5, 0); + + auto articulationPoints = graph.GetArticulationPoints(); + + // Ожидаем следующие точки сочленения + std::vector expectedPoints = {1, 3, 4}; + + EXPECT_EQ(articulationPoints.size(), expectedPoints.size()); + for (int point : expectedPoints) { + EXPECT_NE( + std::find(articulationPoints.begin(), articulationPoints.end(), point), + articulationPoints.end()); + } +} + +// Тест для графа без мостов +TEST(GraphTest, NoBridges) { + Graph graph(4, 0, false); + graph.AddEdge(1, 2, 0); + graph.AddEdge(2, 3, 0); + graph.AddEdge(3, 4, 0); + graph.AddEdge(4, 1, 0); + + auto bridges = graph.GetBridges(); + + EXPECT_TRUE(bridges.empty()); +} + +// Тест для графа без точек сочленения +TEST(GraphTest, NoArticulationPoints) { + Graph graph(4, 0, false); + graph.AddEdge(1, 2, 0); + graph.AddEdge(2, 3, 0); + graph.AddEdge(3, 4, 0); + graph.AddEdge(4, 1, 0); + + auto articulationPoints = graph.GetArticulationPoints(); + + EXPECT_TRUE(articulationPoints.empty()); +} diff --git a/task_04/src/main.cpp b/task_04/src/main.cpp index 0e4393b..442b4dd 100644 --- a/task_04/src/main.cpp +++ b/task_04/src/main.cpp @@ -1,3 +1,33 @@ +#include #include -int main() { return 0; } +#include "graph.hpp" +#include "util.hpp" + +using namespace algo; +using namespace std; + +int main() { + int vertexes_num, edges_num; + cin >> vertexes_num >> edges_num; + + algo::Graph graph(vertexes_num, edges_num, true); + + for (int i = 0; i < edges_num; i++) { + int a, b, w; + + cin >> a >> b >> w; + + graph.AddEdge(a, b, w); + } + + int start; + cin >> start; + + start--; + auto ways = FindWays(graph, start); + + for (int i = 0; i < ways.size(); i++) { + cout << i + 1 << ": " << ways[i] << endl; + } +} diff --git a/task_04/src/test.cpp b/task_04/src/test.cpp index 5e11617..b85347b 100644 --- a/task_04/src/test.cpp +++ b/task_04/src/test.cpp @@ -1,6 +1,91 @@ #include +#include "graph.hpp" +#include "util.hpp" + +using namespace algo; + TEST(TopologySort, Simple) { ASSERT_EQ(1, 1); // Stack [] } + +void CheckDistances(const std::vector& actual, + const std::vector& expected) { + EXPECT_EQ(actual.size(), expected.size()); + for (size_t i = 0; i < expected.size(); ++i) { + EXPECT_EQ(actual[i], expected[i]); + } +} + +// Тест: стандартный граф с положительными весами +TEST(DijkstraTest, StandardGraph) { + Graph graph(6, 0, false); + graph.AddEdge(1, 2, 7); + graph.AddEdge(1, 3, 9); + graph.AddEdge(1, 6, 14); + graph.AddEdge(2, 3, 10); + graph.AddEdge(2, 4, 15); + graph.AddEdge(3, 4, 11); + graph.AddEdge(3, 6, 2); + graph.AddEdge(4, 5, 6); + graph.AddEdge(5, 6, 9); + + auto distances = FindWays(graph, 1); + + // Ожидаемые минимальные расстояния от вершины 1 + std::vector expected = {0, 7, 9, 20, 20, 11}; + CheckDistances(distances, expected); +} + +// Тест: граф с недостижимыми вершинами +TEST(DijkstraTest, GraphWithUnreachableNodes) { + Graph graph(4, 0, false); + graph.AddEdge(1, 2, 5); + graph.AddEdge(2, 3, 10); + // Вершина 4 изолирована + + auto distances = FindWays(graph, 1); + + // Ожидаемые минимальные расстояния от вершины 1 + std::vector expected = {0, 5, 15, std::numeric_limits::max()}; + CheckDistances(distances, expected); +} + +// Тест: граф с петлями +TEST(DijkstraTest, GraphWithSelfLoops) { + Graph graph(3, 0, false); + graph.AddEdge(1, 1, 10); // Петля на вершине 1 + graph.AddEdge(1, 2, 5); + graph.AddEdge(2, 3, 10); + + auto distances = FindWays(graph, 1); + + // Ожидаемые минимальные расстояния от вершины 1 + std::vector expected = {0, 5, 15}; + CheckDistances(distances, expected); +} + +// Тест: граф с одной вершиной +TEST(DijkstraTest, SingleNodeGraph) { + Graph graph(1, 0, false); + + auto distances = FindWays(graph, 1); + + // Ожидаемые минимальные расстояния от вершины 1 + std::vector expected = {0}; + CheckDistances(distances, expected); +} + +// Тест: граф без рёбер +TEST(DijkstraTest, GraphWithoutEdges) { + Graph graph(4, 0, false); + + auto distances = FindWays(graph, 1); + + // Ожидаемые минимальные расстояния от вершины 1 + std::vector expected = {0, std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()}; + CheckDistances(distances, expected); +} \ No newline at end of file