diff --git a/include/CXXGraph/Graph/Algorithm/Bridges_impl.hpp b/include/CXXGraph/Graph/Algorithm/Bridges_impl.hpp new file mode 100644 index 000000000..334e14c25 --- /dev/null +++ b/include/CXXGraph/Graph/Algorithm/Bridges_impl.hpp @@ -0,0 +1,142 @@ +/***********************************************************/ +/*** ______ ____ ______ _ ***/ +/*** / ___\ \/ /\ \/ / ___|_ __ __ _ _ __ | |__ ***/ +/*** | | \ / \ / | _| '__/ _` | '_ \| '_ \ ***/ +/*** | |___ / \ / \ |_| | | | (_| | |_) | | | | ***/ +/*** \____/_/\_\/_/\_\____|_| \__,_| .__/|_| |_| ***/ +/*** |_| ***/ +/***********************************************************/ +/*** Header-Only C++ Library for Graph ***/ +/*** Representation and Algorithms ***/ +/***********************************************************/ +/*** Author: ZigRazor ***/ +/*** E-Mail: zigrazor@gmail.com ***/ +/***********************************************************/ +/*** Collaboration: John Ronchetto ***/ +/***********************************************************/ +/*** License: MPL v2.0 ***/ +/***********************************************************/ + +// NOT OPTIMIZED SOLUTION +// UNDIRECTED GRAPH ASSUMPTION + +#ifndef __CXXGRAPH_BRIDGES_IMPL_H__ +#define __CXXGRAPH_BRIDGES_IMPL_H__ + +#pragma once + +#include +#include +#include + +#include "CXXGraph/Graph/Graph_decl.h" + + + +namespace CXXGraph { + +// Returns list of edges that are bridges in the graph +template +const std::vector, Node>> Graph::bridges( + const Node &start) const { + + std::vector, Node>> bridge; + + auto &nodeSet = Graph::getNodeSet(); + + // Check that start node actually exists + auto startNodeIt = std::find_if( + nodeSet.begin(), nodeSet.end(), + [&start](auto &node) {return node->getUserId() == start.getUserId();}); + // If start node not in graph, then return wmpty result + if (startNodeIt == nodeSet.end()) + return bridge; + + // Helper that runs dfs on node and skips a specific edge (skipU-SkipV) + std::function>, + shared>, + shared>, + shared>, + std::vector> &)> + dfs; + + dfs = [&dfs](const std::shared_ptr> adj, + shared> node, + shared> skipU, + shared> skipV, + std::vector> &visited) -> void { + + // Mark current node as visited + visited.push_back(*node); + + // if node has no out edges, then we're done + if (adj->find(node) == adj->end()) { + return; + } + + // Explore all nighbors of current node + for (const auto &x : adj->at(node)) { + auto next = x.first; + + // Check whether the current edge (node-next) is + // the one we want to remove. Check both directions. + + bool edgeRemoved = + (node->getUserId() == skipU->getUserId() && + next->getUserId() == skipV->getUserId()) || + (node->getUserId() == skipV->getUserId() && + next->getUserId() == skipU->getUserId()); + + // Pretend that edge doesn't exist + if (edgeRemoved) { + continue; + } + + // Standard dfs check using Node value + if (std::find(visited.begin(), visited.end(), *next) == + visited.end()) { + dfs(adj, next, skipU, skipV, visited); + } + } + }; + + // Iterate over every node in the graph + for (auto &uPtr : nodeSet) { + // cachedAdjListOut maps each node in the graph to its outgoing neighbors + auto adjIt = cachedAdjListOut->find(uPtr); + + if (adjIt == cachedAdjListOut->end()) { + continue; + } + + // Look at every neighbor + for (const auto &edge : adjIt->second) { + auto vPtr = edge.first; + + // Run dfs form u and skipping edge u-v + std::vector> visited; + dfs(cachedAdjListOut, uPtr, uPtr, vPtr, visited); + + // Check if v was reached + // if NOT, then u-v is a bridge + bool reached = + std::find(visited.begin(), visited.end(), *vPtr) != + visited.end(); + + auto uId = uPtr->getUserId(); + auto vId = vPtr->getUserId(); + + if (!reached) { + if (uId < vId) { + bridge.emplace_back(*uPtr, *vPtr); + } + } + } + } + + return bridge; +} + +} // namespace CXXGraph + +#endif // __CXXGRAPH_BRIDGES_IMPL_H__ \ No newline at end of file diff --git a/include/CXXGraph/Graph/Graph.h b/include/CXXGraph/Graph/Graph.h index e51f826a4..362455935 100644 --- a/include/CXXGraph/Graph/Graph.h +++ b/include/CXXGraph/Graph/Graph.h @@ -47,6 +47,7 @@ #include "CXXGraph/Graph/Algorithm/TopologicalSort_impl.hpp" #include "CXXGraph/Graph/Algorithm/TransitiveReduction_impl.hpp" #include "CXXGraph/Graph/Algorithm/WelshPowellColoring_impl.hpp" +#include "CXXGraph/Graph/Algorithm/Bridges_impl.hpp" // IO Operation #include "CXXGraph/Graph/IO/IOUtility_impl.hpp" diff --git a/include/CXXGraph/Graph/Graph_decl.h b/include/CXXGraph/Graph/Graph_decl.h index 3c55f8c62..18e01de24 100644 --- a/include/CXXGraph/Graph/Graph_decl.h +++ b/include/CXXGraph/Graph/Graph_decl.h @@ -22,6 +22,8 @@ #pragma once +#include + #include #include #include @@ -114,6 +116,8 @@ class Graph { const std::string &fileName, bool readNodeFeatures = false, bool readEdgeWeights = true); + const std::vector, Node>> bridges( + const Node &start) const; private: T_EdgeSet edgeSet = {}; diff --git a/include/CXXGraph/Utility/SecureRandom.hpp b/include/CXXGraph/Utility/SecureRandom.hpp index 1dec0562a..fd47423a8 100644 --- a/include/CXXGraph/Utility/SecureRandom.hpp +++ b/include/CXXGraph/Utility/SecureRandom.hpp @@ -11,7 +11,9 @@ #ifndef STATUS_SUCCESS #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #endif +#ifdef _MSC_VER #pragma comment(lib, "bcrypt.lib") +#endif #else #include #endif diff --git a/test/BridgesTest.cpp b/test/BridgesTest.cpp new file mode 100644 index 000000000..d577d8219 --- /dev/null +++ b/test/BridgesTest.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +#include "CXXGraph/CXXGraph.hpp" +#include "gtest/gtest.h" + + +// Smart pointers alias +template +using unique = std::unique_ptr; +template +using shared = std::shared_ptr; + +using std::make_shared; +using std::make_unique; + +// helper: does the bridges vector contain an edge between a and b? +static bool has_bridge( + const std::vector, CXXGraph::Node>> &bridges, + const CXXGraph::Node &a, + const CXXGraph::Node &b) { + + return std::any_of( + bridges.begin(), bridges.end(), + [&a, &b](const auto &e) { + return (e.first == a && e.second == b) || + (e.first == b && e.second == a); + }); +} + +/* +Test 1: 1 -- 2 -- 3 + +All edges are bridges +*/ +TEST(BridgesTest, Chain_AllEdgesAreBridges) { + using namespace CXXGraph; + + // Nodes + Node node1("1", 1); + Node node2("2", 2); + Node node3("3", 3); + + // Undirected edges + UndirectedWeightedEdge e1("e1", node1, node2, 1); + UndirectedWeightedEdge e2("e2", node2, node3, 1); + + T_EdgeSet edgeSet; + edgeSet.insert(make_shared>(e1)); + edgeSet.insert(make_shared>(e2)); + + Graph graph(edgeSet); + + auto bridge = graph.bridges(node1); + + // Expected bridges + ASSERT_TRUE(has_bridge(bridge, node1, node2)); + ASSERT_TRUE(has_bridge(bridge, node2, node3)); +} + +/* +Test 2: + 1 + / \ + 2---3---4 + +(3,4) is the only bridge + */ +TEST(BridgesTest, TrianglePlusTail_OnlyTailIsBridge) { + using namespace CXXGraph; + + // Nodes + Node node1("1", 1); + Node node2("2", 2); + Node node3("3", 3); + Node node4("4", 4); + + // Undirected edges + UndirectedWeightedEdge e1("e1", node1, node2, 1); + UndirectedWeightedEdge e2("e2", node2, node3, 1); + UndirectedWeightedEdge e3("e3", node1, node3, 1); + UndirectedWeightedEdge e4("e4", node3, node4, 1); + + T_EdgeSet edgeSet; + edgeSet.insert(make_shared>(e1)); + edgeSet.insert(make_shared>(e2)); + edgeSet.insert(make_shared>(e3)); + edgeSet.insert(make_shared>(e4)); + + Graph graph(edgeSet); + + auto bridge = graph.bridges(node1); + + // Only (3,4) should be a bridge + ASSERT_EQ(bridge.size(), 1u); + ASSERT_TRUE(has_bridge(bridge, node3, node4)); + + // All other edges should NOT be bridges + ASSERT_FALSE(has_bridge(bridge, node1, node2)); + ASSERT_FALSE(has_bridge(bridge, node2, node3)); + ASSERT_FALSE(has_bridge(bridge, node1, node3)); +} + + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bd459f1ba..ad48ead19 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,6 +62,11 @@ if(TEST) file (GLOB TEST_FILES "*.cpp" "*.hpp" "*.h") add_executable(test_exe ${TEST_FILES}) + if (MINGW) + target_link_libraries(test_exe PRIVATE bcrypt) + endif() + + target_compile_definitions(test_exe PUBLIC WITH_COMPRESSION )