diff --git a/lib/src/graph.hpp b/lib/src/graph.hpp new file mode 100644 index 0000000..f184d4a --- /dev/null +++ b/lib/src/graph.hpp @@ -0,0 +1,211 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +/// @brief Graphs vertex +/// @tparam T +template +struct Vertex { + Vertex(const T &d) : data(d) {} + + T data; + std::set>> adjacent; +}; + +template +concept IsVertex = std::is_base_of, T>::value; + +/// @brief Basic graph +/// @tparam VertexType +/// @tparam T +template + requires IsVertex +class Graph { + public: + /** + * @brief + * Add a new vertex to the graph + * @param data + */ + virtual void AddVertex(const T &data) { + vertices_.push_back(std::make_shared(data)); + }; + + std::shared_ptr operator[](size_t index) { + return vertices_[index]; + } + + const std::shared_ptr operator[](size_t index) const { + return vertices_[index]; + } + + /** + * @brief + * Find vertexes index + * @param vertex + * @return size_t + */ + size_t Find(const T &vertex) const { + size_t index; + for (index = 0; index < vertices_.size(); ++index) + if (vertices_[index]->data == vertex) return index; + + return index; + } + + /** + * @brief + * Remove a vertex from the graph via index + * @param vertex_id + */ + virtual void RemoveVertex(size_t vertex_id) { + if (vertex_id >= Size()) { + // Vertex not found + return; + } + + // Remove edges pointing to the vertex + for (auto &v : vertices_[vertex_id]->adjacent) { + RemoveDirEdge(v, vertices_[vertex_id]); + } + + // Remove the vertex from the graph + vertices_.erase(vertices_.begin() + vertex_id); + } + + /** + * @brief + * Remove a vertex from the graph + * @param vertex + */ + virtual void RemoveVertex(std::shared_ptr vertex) { + // Find the vertex in the graph + auto it = std::find(vertices_.begin(), vertices_.end(), vertex); + if (it == vertices_.end()) { + // Vertex not found + return; + } + + RemoveVertex(it - vertices_.begin()); + } + + /** + * @brief + * Returns the number of vertices in the graph + * @return size_t + */ + size_t Size() const { return vertices_.size(); } + + /** + * @brief + * Add a directed edge between two vertices via indices + * @param source_id + * @param target_id + */ + virtual void AddDirEdge(size_t source_id, size_t target_id) { + operator[](source_id)->adjacent.insert(operator[](target_id)); + } + + /** + * @brief + * Add a directed edge between two vertices + * @param source + * @param target + */ + virtual void AddDirEdge(std::shared_ptr source, + std::shared_ptr target) { + source->adjacent.insert(target); + } + + /** + * @brief + * Remove a directed edge between two vertices via indices + * @param source_id + * @param target_id + */ + virtual void RemoveDirEdge(size_t source_id, size_t target_id) { + operator[](source_id)->adjacent.erase(*std::find( + operator[](source_id)->adjacent.begin(), + operator[](source_id)->adjacent.end(), operator[](target_id))); + } + + /** + * @brief + * Remove a directed edge between two vertices + * @param source + * @param target + */ + virtual void RemoveDirEdge(std::shared_ptr source, + std::shared_ptr target) { + source->adjacent.erase( + *std::find(source->adjacent.begin(), source->adjacent.end(), target)); + } + + /** + * @brief + * Add a non-directed edge between two vertices via indices + * @param first_id + * @param second_id + */ + virtual void AddEdge(size_t first_id, size_t second_id) { + AddDirEdge(first_id, second_id); + AddDirEdge(second_id, first_id); + } + + /** + * @brief + * Add a non-directed edge between two vertices + * @param vertex_1 + * @param vertex_2 + */ + virtual void AddEdge(std::shared_ptr vertex_1, + std::shared_ptr vertex_2) { + AddDirEdge(vertex_1, vertex_2); + AddDirEdge(vertex_2, vertex_1); + } + + /** + * @brief + * Remove a non-directed edge between two vertices via indices + * @param first_id + * @param second_id + */ + virtual void RemoveEdge(size_t first_id, size_t second_id) { + RemoveDirEdge(first_id, second_id); + RemoveDirEdge(second_id, first_id); + } + + /** + * @brief + * Remove a non-directed edge between two vertices + * @param vertex_1 + * @param vertex_2 + */ + virtual void RemoveEdge(std::shared_ptr vertex_1, + std::shared_ptr vertex_2) { + RemoveDirEdge(vertex_1, vertex_2); + RemoveDirEdge(vertex_2, vertex_1); + } + + /** + * @brief + * Print the adjacency list of the graph + */ + virtual void PrintGraph() const { + for (const auto &vertex : vertices_) { + std::cout << vertex->data << " -> "; + for (const auto &neighbor : vertex->adjacent) { + std::cout << neighbor->data << " "; + } + std::cout << std::endl; + } + } + + protected: + std::vector> vertices_; +}; diff --git a/lib/src/util.cpp b/lib/src/util.cpp deleted file mode 100644 index 81e15bd..0000000 --- a/lib/src/util.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "util.hpp" diff --git a/lib/src/utils.cpp b/lib/src/utils.cpp new file mode 100644 index 0000000..0c58fd6 --- /dev/null +++ b/lib/src/utils.cpp @@ -0,0 +1 @@ +#include "utils.hpp" \ No newline at end of file diff --git a/lib/src/util.hpp b/lib/src/utils.hpp similarity index 100% rename from lib/src/util.hpp rename to lib/src/utils.hpp diff --git a/lib/src/weighted_graph.hpp b/lib/src/weighted_graph.hpp new file mode 100644 index 0000000..680230f --- /dev/null +++ b/lib/src/weighted_graph.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include + +#include "graph.hpp" + +/// @brief Basic weighted graph +/// @tparam VertexType +/// @tparam T +template +class WeightedGraph : public Graph { + public: + WeightedGraph() : Graph() { + weights.resize(Graph::vertices_.size(), + std::vector(Graph::vertices_.size(), + std::numeric_limits::max())); + } + + /** + * @brief + * Override AddVertex to handle weights matrix resizing + * @param data + */ + void AddVertex(const T& data) override { + Graph::AddVertex(data); + weights.resize(Graph::vertices_.size(), + std::vector(Graph::vertices_.size(), + std::numeric_limits::max())); + for (auto& row : weights) + row.resize(Graph::vertices_.size(), + std::numeric_limits::max()); + } + + /** + * @brief + * Override RemoveVertex via index to handle weights matrix resizing + * @param vertex_id + */ + void RemoveVertex(size_t vertex_id) override { + // Remove the vertex from the base class + Graph::RemoveVertex(vertex_id); + + // Remove the corresponding row and column from the weights matrix + weights.erase(weights.begin() + vertex_id); // Remove the row + + for (auto& row : weights) + row.erase(row.begin() + vertex_id); // Remove the column + } + + /** + * @brief + * Add a weighted directed edge + * @param source_id + * @param target_id + * @param weight + */ + void AddDirEdge(size_t source_id, size_t target_id, int weight) { + Graph::AddDirEdge(source_id, target_id); + weights[source_id][target_id] = weight; + } + + /** + * @brief + * Add a weighted undirected edge + * @param source_id + * @param target_id + * @param weight + */ + void AddEdge(size_t source_id, size_t target_id, int weight) { + AddDirEdge(source_id, target_id, weight); + AddDirEdge(target_id, source_id, weight); + } + + /** + * @brief + * Remove a weighted directed edge + * @param source_id + * @param target_id + */ + void RemoveDirEdge(size_t source_id, size_t target_id) override { + Graph::RemoveDirEdge(source_id, target_id); + weights[source_id][target_id] = std::numeric_limits::max(); + } + + /** + * @brief + * Remove a weighted undirected edge + * @param source_id + * @param target_id + */ + void RemoveEdge(size_t source_id, size_t target_id) override { + RemoveDirEdge(source_id, target_id); + RemoveDirEdge(target_id, source_id); + } + + /** + * @brief + * Get the weight of an edge + * @param source_id + * @param target_id + * @return int + */ + int GetWeight(size_t source_id, size_t target_id) const { + return weights[source_id][target_id]; + } + + /** + * @brief + * Reweight an edge + * @param source_id + * @param target_id + * @param new_weight + */ + void Reweight(size_t source_id, size_t target_id, int new_weight) { + weights[source_id][target_id] = new_weight; + } + + /** + * @brief + * Print the weighted graph + */ + void PrintGraph() const override { + for (size_t i = 0; i < Graph::vertices_.size(); ++i) { + std::cout << Graph::vertices_[i]->data << " -> "; + for (const auto& neighbor : + Graph::vertices_[i]->adjacent) { + size_t j = Graph::Find(neighbor->data); + std::cout << "(" << neighbor->data << ", " << weights[i][j] << ") "; + } + std::cout << std::endl; + } + } + + private: + std::vector> weights; +}; \ No newline at end of file diff --git a/task_01/src/main.cpp b/task_01/src/main.cpp index 76e8197..48a53f6 100644 --- a/task_01/src/main.cpp +++ b/task_01/src/main.cpp @@ -1 +1,25 @@ -int main() { return 0; } +#include "packman.hpp" + +int main() { + DependencyGraph dg; + + std::vector packages = {"Lib_1", "Lib_2", "Lib_3", "Lib_4", + "Lib_5"}; + for (const auto& package : packages) dg.AddVertex(package); + + dg.AddDirEdge(0, 1); // Lib_1 depends on Lib_2 + dg.AddDirEdge(0, 2); // Lib_1 depends on Lib_3 + dg.AddDirEdge(2, 3); // Lib_3 depends on Lib_4 + dg.AddDirEdge(3, 1); // Lib_4 depends on Lib_2 + dg.AddDirEdge(0, 3); // Lib_1 depends on Lib_4 + + PackageManager packman(dg); + + packman.FindDownloadingOrder("Lib_1"); + packman.FindDownloadingOrder("Lib_2"); + packman.FindDownloadingOrder("Lib_3"); + packman.FindDownloadingOrder("Lib_4"); + packman.FindDownloadingOrder("Lib_5"); + + return 0; +} diff --git a/task_01/src/packman.cpp b/task_01/src/packman.cpp new file mode 100644 index 0000000..7c453b7 --- /dev/null +++ b/task_01/src/packman.cpp @@ -0,0 +1,70 @@ +#include "packman.hpp" + +bool PackageManager::IsCyclicUtil(std::shared_ptr node, + std::vector& visited, + std::vector& recStack) { + if (!visited[dependencies_.Find(node->data)]) { + visited[dependencies_.Find(node->data)] = true; + recStack[dependencies_.Find(node->data)] = true; + + for (auto& neighbor : node->adjacent) + if (!visited[dependencies_.Find(neighbor->data)] && + IsCyclicUtil(neighbor, visited, recStack)) + return true; + else if (recStack[dependencies_.Find(neighbor->data)]) + return true; + } + recStack[dependencies_.Find(node->data)] = false; + return false; +} + +bool PackageManager::IsCyclic() { + std::vector visited(dependencies_.Size(), false); + std::vector recStack(dependencies_.Size(), false); + + for (std::size_t i = 0; i < dependencies_.Size(); ++i) + if (IsCyclicUtil(dependencies_[i], visited, recStack)) return true; + + return false; +} + +void PackageManager::FindingOrderStep(std::shared_ptr target) { + is_visited_[dependencies_.Find(target->data)] = true; + + for (auto& neighbor : target->adjacent) + if (!is_visited_[dependencies_.Find(neighbor->data)]) + FindingOrderStep(neighbor); + + order_.push(target->data); +} + +std::vector PackageManager::FindDownloadingOrder( + std::string target) { + if (IsCyclic()) { + std::cout << "The graph is cyclic. Downloading order is undetermined." + << std::endl; + return {}; + } + + std::size_t target_id = dependencies_.Find(target); + if (target_id == dependencies_.Size()) { + std::cout << "No such package : " << target << std::endl; + return {}; + } + + is_visited_.resize(dependencies_.Size()); + for (size_t i = 0; i < is_visited_.size(); ++i) is_visited_[i] = false; + + FindingOrderStep(dependencies_[target_id]); + + std::vector order; + + std::cout << "Downloading Order for " << target << " is :" << std::endl; + while (order_.size() > 0) { + order.push_back(order_.front()); + std::cout << order_.front() << " "; + order_.pop(); + } + std::cout << std::endl; + return order; +} \ No newline at end of file diff --git a/task_01/src/packman.hpp b/task_01/src/packman.hpp new file mode 100644 index 0000000..3120a1e --- /dev/null +++ b/task_01/src/packman.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "graph.hpp" + +/// @brief Vertex representing single downloadable package +class Library : public Vertex { + public: + std::set> adjacent; +}; + +/// @brief Graph of dependencies between libraries +/// The "parent" libraries should be installed after thier children +class DependencyGraph : public Graph {}; + +/// @brief Packman basic algorithm +class PackageManager { + public: + PackageManager() = delete; + PackageManager(DependencyGraph& dep_graph) : dependencies_{dep_graph} {} + + /** + * @brief + * Finds the right order to install libraries in + * with respect to dependencies + * @param target Needed library + * @return std::vector + */ + std::vector FindDownloadingOrder(std::string target); + + private: + bool IsCyclicUtil(std::shared_ptr node, std::vector& visited, + std::vector& recStack); + bool IsCyclic(); + void FindingOrderStep(std::shared_ptr target); + DependencyGraph& dependencies_; + std::vector is_visited_; + std::queue order_; +}; \ No newline at end of file diff --git a/task_01/src/test.cpp b/task_01/src/test.cpp index 87cef73..09492f3 100644 --- a/task_01/src/test.cpp +++ b/task_01/src/test.cpp @@ -1,5 +1,100 @@ #include -TEST(Test, Simple) { - ASSERT_EQ(1, 1); // Stack [] +#include "packman.hpp" + +TEST(Test, Example_1) { + // Example 1: Small graph + DependencyGraph dg; + std::vector packages = {"Basic_Package", "Extention"}; + for (const auto& package : packages) dg.AddVertex(package); + + dg.AddDirEdge(1, 0); + + PackageManager packman(dg); + + std::vector> answers{{"Basic_Package"}, + {"Basic_Package", "Extention"}}; + + for (size_t i = 0; i < answers.size(); ++i) + ASSERT_EQ(answers[i], packman.FindDownloadingOrder(packages[i])); +} + +TEST(Test, Example_2) { + // Example 2: Three vertices, one edge + DependencyGraph dg; + + std::vector packages = {"First", "Second", "Independent"}; + + for (const auto& package : packages) dg.AddVertex(package); + + dg.AddDirEdge(1, 0); + + PackageManager packman(dg); + + std::vector> answers{ + {"First"}, {"First", "Second"}, {"Independent"}}; + + for (size_t i = 0; i < answers.size(); ++i) + ASSERT_EQ(answers[i], packman.FindDownloadingOrder(packages[i])); +} + +TEST(Test, Example_3) { + // Example 3: Five vertices, more complex dependencies + DependencyGraph dg; + + std::vector packages = {"BaseLib", "TestLib", "DataLib", + "AlgorithmLib", "ToolLib"}; + + for (const auto& package : packages) dg.AddVertex(package); + + dg.AddDirEdge(1, 0); + dg.AddDirEdge(2, 0); + dg.AddDirEdge(3, 2); + dg.AddDirEdge(4, 3); + + PackageManager packman(dg); + + std::vector> answers{ + {"BaseLib"}, + {"BaseLib", "TestLib"}, + {"BaseLib", "DataLib"}, + {"BaseLib", "DataLib", "AlgorithmLib"}, + {"BaseLib", "DataLib", "AlgorithmLib", "ToolLib"}}; + + for (size_t i = 0; i < answers.size(); ++i) + ASSERT_EQ(answers[i], packman.FindDownloadingOrder(packages[i])); +} + +TEST(Test, Example_4) { + // Example 4: More complex graph, multiple paths + DependencyGraph dg; + + std::vector packages = {"Start", "A1", "A2", "B1", + "B2", "C1", "C2", "End"}; + + for (const auto& package : packages) dg.AddVertex(package); + + dg.AddDirEdge(1, 0); + dg.AddDirEdge(2, 0); + dg.AddDirEdge(3, 1); + dg.AddDirEdge(4, 2); + dg.AddDirEdge(5, 3); + dg.AddDirEdge(6, 4); + dg.AddDirEdge(7, 5); + dg.AddDirEdge(7, 6); + + PackageManager packman(dg); + + std::vector> answers{ + {"Start"}, + {"Start", "A1"}, + {"Start", "A2"}, + {"Start", "A1", "B1"}, + {"Start", "A2", "B2"}, + {"Start", "A1", "B1", "C1"}, + {"Start", "A2", "B2", "C2"}, + {"Start", "A1", "B1", "C1", "A2", "B2", "C2", "End"}}; + + for (size_t i = 0; i < answers.size(); ++i) + ASSERT_EQ(answers[i], packman.FindDownloadingOrder(packages[i])); } \ No newline at end of file diff --git a/task_02/src/main.cpp b/task_02/src/main.cpp index 0e4393b..2894da4 100644 --- a/task_02/src/main.cpp +++ b/task_02/src/main.cpp @@ -1,3 +1,32 @@ #include -int main() { return 0; } +#include "network.hpp" + +int main() { + // Create a graph to represent the network + Network network; + + network.AddVertex(0); + network.AddVertex(1); + network.AddVertex(2); + network.AddVertex(3); + + network.AddEdge(0, 1); + network.AddEdge(1, 2); + network.AddEdge(2, 0); + network.AddEdge(2, 3); + + auto [bridges, cut_vertices] = network.FindBridgesAndCutVertices(); + + // Print bridges + std::cout << "Bridges:" << std::endl; + for (const auto &bridge : bridges) + std::cout << bridge.first << " -- " << bridge.second << std::endl; + + // Print cut vertices + std::cout << "Cut vertices:" << std::endl; + for (const auto &cut_vertex : cut_vertices) + std::cout << cut_vertex << std::endl; + + return 0; +} \ No newline at end of file diff --git a/task_02/src/network.cpp b/task_02/src/network.cpp new file mode 100644 index 0000000..8dfb7e4 --- /dev/null +++ b/task_02/src/network.cpp @@ -0,0 +1,56 @@ +#include "network.hpp" + +void Network::TarjanVisit(size_t vertex_id, std::vector &disc, + std::vector &low, std::vector &parent, + std::vector> &bridges, + std::vector &cut_vertices, int &time) { + // Visiting another vertex + low[vertex_id] = disc[vertex_id] = time++; + + // Counter of child nodes DFS traversal tree (for cut vertices) + int children = 0; + + // Iterate over all adjacent vertices + for (auto &neighbor : vertices_[vertex_id]->adjacent) { + size_t neighbor_id = Find(neighbor->data); + + if (disc[neighbor_id] == -1) { // Vertex is not visited + children++; + parent[neighbor_id] = vertex_id; + + // DFS traversal + TarjanVisit(neighbor_id, disc, low, parent, bridges, cut_vertices, time); + + // Update low value of current vertex + low[vertex_id] = std::min(low[vertex_id], low[neighbor_id]); + + // Update bridges + if (low[neighbor_id] > disc[vertex_id]) + bridges.emplace_back(vertex_id, neighbor_id); + + // Update cut vertices + if ((low[neighbor_id] >= disc[vertex_id]) && (parent[vertex_id] != -1)) + cut_vertices.push_back(vertex_id); + } else if (parent[vertex_id] != neighbor_id) // Vertex is visited + // Update low value of current vertex + low[vertex_id] = std::min(low[vertex_id], disc[neighbor_id]); + } + // Update cut vertices + if ((parent[vertex_id] == -1) && (children > 1)) + cut_vertices.push_back(vertex_id); +} + +std::pair>, std::vector> +Network::FindBridgesAndCutVertices() { + std::vector disc(Size(), -1); + std::vector low(Size(), -1); + std::vector parent(Size(), -1); + std::vector> bridges; + std::vector cut_vertices; + + int time = 0; + for (size_t vertex_id = 0; vertex_id < Size(); ++vertex_id) + if (disc[vertex_id] == -1) + TarjanVisit(vertex_id, disc, low, parent, bridges, cut_vertices, time); + return std::make_pair(bridges, cut_vertices); +} \ No newline at end of file diff --git a/task_02/src/network.hpp b/task_02/src/network.hpp new file mode 100644 index 0000000..e538d8b --- /dev/null +++ b/task_02/src/network.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "graph.hpp" + +/// @brief Vertex representing a single router +class Router : public Vertex { + public: + std::set> adjacent; +}; + +/// @brief Graph of routers in cities net +class Network : public Graph { + public: + /// @brief Finds bridges and cut vertices in graph using Tarjan's algorithm + std::pair>, std::vector> + FindBridgesAndCutVertices(); + + private: + void TarjanVisit(size_t vertex_id, std::vector &disc, + std::vector &low, std::vector &parent, + std::vector> &bridges, + std::vector &cut_vertices, int &time); +}; \ No newline at end of file diff --git a/task_02/src/stack.cpp b/task_02/src/stack.cpp deleted file mode 100644 index 8ca8990..0000000 --- a/task_02/src/stack.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "stack.hpp" - -#include - -void Stack::Push(int value) { data_.push(value); } - -int Stack::Pop() { - auto result = data_.top(); - data_.pop(); - return result; -} - -void MinStack::Push(int value) { data_.push_back(value); } - -int MinStack::Pop() { - auto result = data_.back(); - data_.pop_back(); - return result; -} - -int MinStack::GetMin() { return *std::min_element(data_.begin(), data_.end()); } \ No newline at end of file diff --git a/task_02/src/stack.hpp b/task_02/src/stack.hpp deleted file mode 100644 index 138ec40..0000000 --- a/task_02/src/stack.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -class Stack { - public: - void Push(int value); - int Pop(); - - private: - std::stack data_; -}; - -class MinStack { - public: - void Push(int value); - int Pop(); - int GetMin(); - - private: - std::vector data_; -}; diff --git a/task_02/src/test.cpp b/task_02/src/test.cpp index 54e7ce9..d97be12 100644 --- a/task_02/src/test.cpp +++ b/task_02/src/test.cpp @@ -1,42 +1,129 @@ - #include -#include - -#include "stack.hpp" - -TEST(StackTest, Simple) { - Stack stack; - stack.Push(1); // Stack [1] - ASSERT_EQ(stack.Pop(), 1); // Stack [] - stack.Push(1); // Stack [1] - stack.Push(2); // Stack [1, 2] - ASSERT_EQ(stack.Pop(), 2); // Stack [1] - ASSERT_EQ(stack.Pop(), 1); // Stack [] - stack.Push(1); // Stack [1] - stack.Push(2); // Stack [1, 2] - ASSERT_EQ(stack.Pop(), 2); // Stack [1] - stack.Push(3); // Stack [1, 3] - ASSERT_EQ(stack.Pop(), 3); // Stack [1] - ASSERT_EQ(stack.Pop(), 1); // Stack [] -} - -TEST(MinStackTest, Simple) { - MinStack stack; - stack.Push(1); // Stack [1] - ASSERT_EQ(stack.GetMin(), 1); - ASSERT_EQ(stack.Pop(), 1); // Stack [] - stack.Push(1); // Stack [1] - stack.Push(2); // Stack [1, 2] - ASSERT_EQ(stack.GetMin(), 1); - ASSERT_EQ(stack.Pop(), 2); // Stack [1] - ASSERT_EQ(stack.Pop(), 1); // Stack [] - stack.Push(1); // Stack [1] - stack.Push(2); // Stack [1, 2] - ASSERT_EQ(stack.GetMin(), 1); - ASSERT_EQ(stack.Pop(), 2); // Stack [1] - stack.Push(3); // Stack [1, 3] - ASSERT_EQ(stack.GetMin(), 1); - ASSERT_EQ(stack.Pop(), 3); // Stack [1] - ASSERT_EQ(stack.Pop(), 1); // Stack [] +#include +#include + +#include "network.hpp" + +// Helper function to check if a bridge exists +bool BridgeExists(const std::vector>& bridges, int u, + int v) { + return std::find_if(bridges.begin(), bridges.end(), + [u, v](const auto& bridge) { + return (bridge.first == u && bridge.second == v) || + (bridge.first == v && bridge.second == u); + }) != bridges.end(); +} + +// Helper function to check if returned bridges are correct +void CheckBridges(const std::vector>& bridges, + const std::vector>& expected_bridges) { + EXPECT_EQ(bridges.size(), expected_bridges.size()); + for (const auto& bridge : expected_bridges) + EXPECT_TRUE(BridgeExists(bridges, bridge.first, bridge.second)); +} + +// Helper function to check if two sets of integers are equal +bool SetsEqual(const std::vector& vec1, const std::vector& vec2) { + std::set set1(vec1.begin(), vec1.end()); + std::set set2(vec2.begin(), vec2.end()); + return set1 == set2; +} + +// Helper function to check if returned cut vertices are correct +void CheckCutVertices(const std::vector& cut_vertices, + const std::vector& expected_cut_vertices) { + EXPECT_TRUE(SetsEqual(cut_vertices, expected_cut_vertices)); +} + +TEST(NetworkTest, SimpleTriangleWithBridge) { + Network network; + network.AddVertex(0); + network.AddVertex(1); + network.AddVertex(2); + network.AddVertex(3); + + network.AddEdge(0, 1); + network.AddEdge(1, 2); + network.AddEdge(2, 0); + network.AddEdge(2, 3); // This is a bridge + + auto [bridges, cut_vertices] = network.FindBridgesAndCutVertices(); + + std::vector> excepted_bridges = {{2, 3}}; + std::vector excepted_cut_vertices = {2}; + + CheckBridges(bridges, excepted_bridges); + CheckCutVertices(cut_vertices, excepted_cut_vertices); +} + +TEST(NetworkTest, NoBridgesOrCutVertices) { + Network network; + network.AddVertex(0); + network.AddVertex(1); + network.AddVertex(2); + + network.AddEdge(0, 1); + network.AddEdge(1, 2); + network.AddEdge(2, 0); + + auto [bridges, cut_vertices] = network.FindBridgesAndCutVertices(); + + std::vector> excepted_bridges = {}; + std::vector excepted_cut_vertices = {}; + + CheckBridges(bridges, excepted_bridges); + CheckCutVertices(cut_vertices, excepted_cut_vertices); +} + +TEST(NetworkTest, LineGraph) { + Network network; + network.AddVertex(0); + network.AddVertex(1); + network.AddVertex(2); + + network.AddEdge(0, 1); + network.AddEdge(1, 2); + + auto [bridges, cut_vertices] = network.FindBridgesAndCutVertices(); + + std::vector> excepted_bridges = {{0, 1}, {1, 2}}; + std::vector excepted_cut_vertices = {1}; + + CheckBridges(bridges, excepted_bridges); + CheckCutVertices(cut_vertices, excepted_cut_vertices); +} + +TEST(NetworkTest, EmptyGraph) { + Network network; + auto [bridges, cut_vertices] = network.FindBridgesAndCutVertices(); + + std::vector> excepted_bridges = {}; + std::vector excepted_cut_vertices = {}; + + CheckBridges(bridges, excepted_bridges); + CheckCutVertices(cut_vertices, excepted_cut_vertices); +} + +TEST(NetworkTest, CycleWithMultipleBridges) { + Network network; + network.AddVertex(0); + network.AddVertex(1); + network.AddVertex(2); + network.AddVertex(3); + network.AddVertex(4); + + network.AddEdge(0, 1); + network.AddEdge(1, 2); + network.AddEdge(2, 0); + network.AddEdge(1, 3); // Bridge to vertex 3 + network.AddEdge(3, 4); // Bridge to vertex 4 + + auto [bridges, cut_vertices] = network.FindBridgesAndCutVertices(); + + std::vector> excepted_bridges = {{1, 3}, {3, 4}}; + std::vector excepted_cut_vertices = {1, 3}; + + CheckBridges(bridges, excepted_bridges); + CheckCutVertices(cut_vertices, excepted_cut_vertices); } \ No newline at end of file diff --git a/task_03/CMakeLists.txt b/task_03/CMakeLists.txt index 7c1cb30..65417b5 100644 --- a/task_03/CMakeLists.txt +++ b/task_03/CMakeLists.txt @@ -25,7 +25,8 @@ find_package(GTest REQUIRED) include_directories(${GTEST_INCLUDE_DIRS}) find_library(Utils ../) -target_link_libraries(${PROJECT_NAME} PUBLIC Utils) +find_library(Dijkstra ../) +target_link_libraries(${PROJECT_NAME} PUBLIC Utils Dijkstra) # Link runTests with what we want to test and the GTest and pthread library add_executable(${PROJECT_NAME}_tests ${test_source_list}) @@ -33,6 +34,7 @@ target_link_libraries( ${PROJECT_NAME}_tests GTest::gtest_main Utils + Dijkstra ) include(GoogleTest) diff --git a/task_03/src/johnson.hpp b/task_03/src/johnson.hpp new file mode 100644 index 0000000..721d725 --- /dev/null +++ b/task_03/src/johnson.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include + +#include "dijkstra.hpp" +#include "weighted_graph.hpp" + +constexpr int INF = std::numeric_limits::max(); + +template +std::vector> Johnson(WeightedGraph& graph) { + // Step 1: Add a new vertex + T new_vertex_data; + graph.AddVertex(new_vertex_data); + + // Step 2: Add edges from the new vertex to all other vertices with weight 0 + for (size_t u = 0; u < graph.Size() - 1; ++u) + graph.AddDirEdge(graph.Size() - 1, u, 0); + + // Step 3: Run Bellman-Ford algorithm from the new vertex + std::vector h(graph.Size(), INF); + h[graph.Size() - 1] = 0; + for (int i = 0; i < graph.Size() - 1; ++i) // |V| - 1 relaxation iterations + for (int u = 0; u < graph.Size(); ++u) + for (const auto& neighbour : graph[u]->adjacent) { + size_t v = graph.Find(neighbour->data); + if (h[u] != INF && h[u] + graph.GetWeight(u, v) < h[v]) + h[v] = h[u] + graph.GetWeight(u, v); + } + + // Step 4: Check for negative weight cycles + for (int u = 0; u < graph.Size(); ++u) + for (const auto& neighbour : graph[u]->adjacent) { + size_t v = graph.Find(neighbour->data); + if (h[u] != INF && h[u] + graph.GetWeight(u, v) < h[v]) { + std::cout << "Graph contains a negative weight cycle" << std::endl; + graph.RemoveVertex(graph.Size() - 1); + return {}; + } + } + + // Step 5: Remove the new vertex + graph.RemoveVertex(graph.Size() - 1); + + // Step 6: Reweight the edges + for (int u = 0; u < graph.Size(); ++u) + for (const auto& neighbour : graph[u]->adjacent) { + size_t v = graph.Find(neighbour->data); + if (graph.GetWeight(u, v) != INF) + graph.Reweight(u, v, graph.GetWeight(u, v) + h[u] - h[v]); + } + + // Step 7: Run Dijkstra's algorithm for each vertex + std::vector> distances(graph.Size(), + std::vector(graph.Size(), INF)); + + for (size_t u = 0; u < graph.Size(); ++u) + distances[u] = Dijkstra(graph, u).first; + + // Step 8: Restore the original weights + for (size_t u = 0; u < graph.Size(); ++u) + for (size_t v = 0; v < graph.Size(); ++v) + if (distances[u][v] != INF) distances[u][v] += h[v] - h[u]; + + return distances; +} \ No newline at end of file diff --git a/task_03/src/main.cpp b/task_03/src/main.cpp index 0e4393b..09ffeca 100644 --- a/task_03/src/main.cpp +++ b/task_03/src/main.cpp @@ -1,3 +1,27 @@ #include -int main() { return 0; } +#include "johnson.hpp" + +int main() { + WeightedGraph, int> graph; + for (int i = 0; i < 4; ++i) graph.AddVertex(i); + graph.AddDirEdge(0, 1, -5); + graph.AddDirEdge(0, 2, 2); + graph.AddDirEdge(0, 3, 3); + graph.AddDirEdge(1, 2, 4); + graph.AddDirEdge(2, 3, 1); + + std::vector> result = Johnson(graph); + + for (size_t i = 0; i < result.size(); ++i) { + std::cout << "{ "; + for (size_t j = 0; j < result[i].size(); ++j) { + if (result[i][j] != INF) + std::cout << result[i][j] << " "; + else + std::cout << "INF "; + } + std::cout << "}" << std::endl; + } + return 0; +} diff --git a/task_03/src/test.cpp b/task_03/src/test.cpp index ef5a86a..f436a7a 100644 --- a/task_03/src/test.cpp +++ b/task_03/src/test.cpp @@ -1,8 +1,67 @@ - #include -#include "topology_sort.hpp" +#include "johnson.hpp" + +TEST(JohnsonAlgorithmTest, SimpleGraph) { + WeightedGraph, int> graph; + for (int i = 0; i < 4; ++i) graph.AddVertex(i); + graph.AddDirEdge(0, 1, 1); + graph.AddDirEdge(1, 2, 2); + graph.AddDirEdge(2, 3, 1); + graph.AddDirEdge(0, 2, 4); + + std::vector> expected = { + {0, 1, 3, 4}, {INF, 0, 2, 3}, {INF, INF, 0, 1}, {INF, INF, INF, 0}}; + + std::vector> result = Johnson(graph); + EXPECT_EQ(result, expected); +} + +TEST(JohnsonAlgorithmTest, GraphWithNegativeWeights) { + WeightedGraph, int> graph; + for (int i = 0; i < 4; ++i) graph.AddVertex(i); + graph.AddDirEdge(0, 1, -2); + graph.AddDirEdge(1, 2, -3); + graph.AddDirEdge(2, 3, 1); + graph.AddDirEdge(0, 2, 4); + + std::vector> expected = { + {0, -2, -5, -4}, {INF, 0, -3, -2}, {INF, INF, 0, 1}, {INF, INF, INF, 0}}; + + std::vector> result = Johnson(graph); + EXPECT_EQ(result, expected); +} + +TEST(JohnsonAlgorithmTest, NegativeCycle) { + WeightedGraph, int> graph; + for (int i = 0; i < 4; ++i) graph.AddVertex(i); + graph.AddDirEdge(0, 1, 1); + graph.AddDirEdge(1, 2, 2); + graph.AddDirEdge(2, 0, -4); + graph.AddDirEdge(1, 3, 5); + + std::vector> result = Johnson(graph); + EXPECT_TRUE(result.empty()); +} -TEST(TopologySort, Simple) { - ASSERT_EQ(1, 1); // Stack [] +TEST(JohnsonAlgorithmTest, DisconnectedGraph) { + WeightedGraph, int> graph; + for (int i = 0; i < 4; ++i) graph.AddVertex(i); + graph.AddDirEdge(0, 1, 1); + graph.AddDirEdge(1, 2, 2); + // Vertex 3 and 4 are disconnected + + std::vector> expected = { + {0, 1, 3, INF}, {INF, 0, 2, INF}, {INF, INF, 0, INF}, {INF, INF, INF, 0}}; + + std::vector> result = Johnson(graph); + EXPECT_EQ(result, expected); } + +TEST(JohnsonAlgorithmTest, EmptyGraph) { + WeightedGraph, int> graph; + // No edges or vertices + + std::vector> result = Johnson(graph); + EXPECT_TRUE(result.empty()); +} \ No newline at end of file diff --git a/task_03/src/topology_sort.cpp b/task_03/src/topology_sort.cpp deleted file mode 100644 index e53f670..0000000 --- a/task_03/src/topology_sort.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "topology_sort.hpp" diff --git a/task_03/src/topology_sort.hpp b/task_03/src/topology_sort.hpp deleted file mode 100644 index 6f70f09..0000000 --- a/task_03/src/topology_sort.hpp +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/task_04/CMakeLists.txt b/task_04/CMakeLists.txt index 7c1cb30..a90cc12 100644 --- a/task_04/CMakeLists.txt +++ b/task_04/CMakeLists.txt @@ -9,7 +9,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp") file(GLOB_RECURSE main_source_list "src/main.cpp") -file(GLOB_RECURSE test_source_list "src/*.cpp") +file(GLOB_RECURSE test_source_list "src/*.cpp" "src/*.hpp") file(GLOB_RECURSE test_list "src/*test.cpp") list(REMOVE_ITEM test_source_list ${main_source_list}) @@ -27,6 +27,14 @@ include_directories(${GTEST_INCLUDE_DIRS}) find_library(Utils ../) target_link_libraries(${PROJECT_NAME} PUBLIC Utils) +file(GLOB_RECURSE lib_source_list "src/*.hpp") + +add_library(Dijkstra ${lib_source_list}) + +set_target_properties(Dijkstra PROPERTIES LINKER_LANGUAGE CXX) + +target_include_directories(Dijkstra PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) + # 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( diff --git a/task_04/src/dijkstra.hpp b/task_04/src/dijkstra.hpp new file mode 100644 index 0000000..9cd2178 --- /dev/null +++ b/task_04/src/dijkstra.hpp @@ -0,0 +1,84 @@ +#pragma once +#include + +#include "weighted_graph.hpp" + +template +std::pair, std::vector> Dijkstra( + const WeightedGraph& graph, size_t start) { + const size_t size = graph.Size(); + + // Distance array to store shortest distances from start + std::vector distances(size, std::numeric_limits::max()); + + // Previous vertex array to reconstruct the path + std::vector previous(size, -1); + + // Visited set to keep track of processed vertices + std::vector visited(size, false); + + // Priority queue to get vertex with minimum distance + // pair: (distance, vertex_index) + std::priority_queue, + std::vector>, std::greater<>> + pq; + + // Initialize distance to start vertex as 0 + distances[start] = 0; + pq.push({0, start}); + + while (!pq.empty()) { + size_t current = pq.top().second; + pq.pop(); + + // Skip if already visited + if (visited[current]) continue; + + visited[current] = true; + + // For each adjacent vertex of current vertex + for (const auto& neighbor : graph[current]->adjacent) { + size_t next = graph.Find(neighbor->data); + int weight = graph.GetWeight(current, next); + + // If we found a shorter path to neighbor + if (!visited[next] && + distances[current] != std::numeric_limits::max() && + distances[current] + weight < distances[next]) { + distances[next] = distances[current] + weight; + previous[next] = current; + pq.push({distances[next], next}); + } + } + } + + return {distances, previous}; +} + +/** + * @brief + * Reconstruct path from start to end vertex + * @tparam T + * @param previous + * @param start + * @param end + * @return std::vector path + */ +template +std::vector GetPath(const std::vector& previous, size_t start, + size_t end) { + std::vector path; + + // If there's no path to end + if (previous[end] == -1 && end != start) return path; + + // Reconstruct path by walking backwards from end to start + for (size_t current = end; current != -1; current = previous[current]) { + path.push_back(current); + if (current == start) break; + } + + // Reverse path to get start->end order + std::reverse(path.begin(), path.end()); + return path; +} \ No newline at end of file diff --git a/task_04/src/test.cpp b/task_04/src/test.cpp index 5e11617..c89c443 100644 --- a/task_04/src/test.cpp +++ b/task_04/src/test.cpp @@ -1,6 +1,97 @@ - #include -TEST(TopologySort, Simple) { - ASSERT_EQ(1, 1); // Stack [] +#include "dijkstra.hpp" +#include "weighted_graph.hpp" + +void CheckDistances(std::vector& dist, std::vector& exp_dist) { + EXPECT_EQ(dist.size(), exp_dist.size()); + for (size_t i = 0; i < dist.size(); ++i) EXPECT_EQ(dist[i], exp_dist[i]); } + +void CheckPaths(std::vector& prev, + std::vector>& exp_paths, size_t source) { + for (size_t i = 0; i < prev.size(); ++i) { + std::vector path = GetPath(prev, source, i); + EXPECT_EQ(path, exp_paths[i]); + } +} + +TEST(DijkstraTest, SimpleUndirectedGraph) { + WeightedGraph, int> graph; + + // Add vertices + for (int i = 0; i < 5; i++) graph.AddVertex(i); + + // Add edges + graph.AddEdge(0, 1, 4); + graph.AddEdge(0, 2, 2); + graph.AddEdge(1, 2, 1); + graph.AddEdge(1, 3, 5); + graph.AddEdge(2, 3, 8); + graph.AddEdge(2, 4, 10); + graph.AddEdge(3, 4, 2); + + size_t source = 0; + + auto [distances, previous] = Dijkstra(graph, source); + + std::vector expected_distances = {0, 3, 2, 8, 10}; + + CheckDistances(distances, expected_distances); + + std::vector> expected_paths = { + {0}, {0, 2, 1}, {0, 2}, {0, 2, 1, 3}, {0, 2, 1, 3, 4}}; + + CheckPaths(previous, expected_paths, source); +} + +TEST(DijkstraTest, SimpleDirectedGraph) { + WeightedGraph, int> graph; + + // Add vertices + for (int i = 0; i < 5; i++) graph.AddVertex(i); + + // Add edges + graph.AddDirEdge(0, 1, 4); + graph.AddDirEdge(0, 2, 2); + graph.AddDirEdge(1, 2, 1); + graph.AddDirEdge(1, 3, 5); + graph.AddDirEdge(2, 3, 8); + graph.AddDirEdge(2, 4, 10); + graph.AddDirEdge(3, 4, 2); + + size_t source = 0; + + auto [distances, previous] = Dijkstra(graph, source); + + std::vector expected_distances = {0, 4, 2, 9, 11}; + + CheckDistances(distances, expected_distances); + + std::vector> expected_paths = { + {0}, {0, 1}, {0, 2}, {0, 1, 3}, {0, 1, 3, 4}}; + + CheckPaths(previous, expected_paths, source); +} + +TEST(DijkstraTest, DisconnectedGraph) { + WeightedGraph, int> graph; + + // Add vertices + for (int i = 0; i < 3; i++) graph.AddVertex(i); + + // Add single edge + graph.AddDirEdge(0, 1, 5); + + size_t source = 0; + + auto [distances, previous] = Dijkstra(graph, source); + + std::vector expected_distances = {0, 5, std::numeric_limits::max()}; + + CheckDistances(distances, expected_distances); + + std::vector> expected_paths = {{0}, {0, 1}, {}}; + + CheckPaths(previous, expected_paths, source); +} \ No newline at end of file