From 23c4f0e4912a83cc79e9d33ec5c00d17eaf4f45e Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Fri, 16 Jul 2021 18:16:04 -0400 Subject: [PATCH] Add example of finding MST using Kruskal's algorithm + Using example graph and pseudocode from MIT Algorithms --- cpp/algorithms/graphs/object/CMakeLists.txt | 2 +- cpp/algorithms/graphs/weighted/graph.cpp | 109 ++++++++++++------- cpp/algorithms/graphs/weighted/lib-graph.cpp | 42 +++++-- cpp/algorithms/graphs/weighted/lib-graph.hpp | 81 ++++++++++++-- 4 files changed, 176 insertions(+), 58 deletions(-) diff --git a/cpp/algorithms/graphs/object/CMakeLists.txt b/cpp/algorithms/graphs/object/CMakeLists.txt index ac639a3..0e5d2e6 100644 --- a/cpp/algorithms/graphs/object/CMakeLists.txt +++ b/cpp/algorithms/graphs/object/CMakeLists.txt @@ -1,7 +1,7 @@ ################################################################################ ## Author: Shaun Reed ## ## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## -## About: A basic CMakeLists configuration to test RBT implementation ## +## About: A root project for practicing graph algorithms in C++ ## ## ## ## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## ################################################################################ diff --git a/cpp/algorithms/graphs/weighted/graph.cpp b/cpp/algorithms/graphs/weighted/graph.cpp index 990db59..97dd446 100644 --- a/cpp/algorithms/graphs/weighted/graph.cpp +++ b/cpp/algorithms/graphs/weighted/graph.cpp @@ -1,7 +1,7 @@ /*############################################################################## ## Author: Shaun Reed ## ## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## -## About: An example of an object graph implementation ## +## About: An example of a weighted graph implementation ## ## Algorithms in this example are found in MIT Intro to Algorithms ## ## ## ## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## @@ -15,14 +15,14 @@ int main (const int argc, const char * argv[]) { // We could initialize the graph with some localNodes... std::vector localNodes{ - {1, {2, 5}}, // Node 1 - {2, {1, 6}}, // Node 2 - {3, {4, 6, 7}}, - {4, {3, 7, 8}}, - {5, {1}}, - {6, {2, 3, 7}}, - {7, {3, 4, 6, 8}}, - {8, {4, 6}}, + {1, {{2, 0}, {5, 0}}}, // Node 1 + {2, {{1, 0}, {6, 0}}}, // Node 2 + {3, {{4, 0}, {6, 0}, {7, 0}}}, + {4, {{3, 0}, {7, 0}, {8, 0}}}, + {5, {{1, 0}}}, + {6, {{2, 0}, {3, 0}, {7, 0}}}, + {7, {{3, 0}, {4, 0}, {6, 0}, {8, 0}}}, + {8, {{4, 0}, {6, 0}}}, }; Graph bfsGraphInit(localNodes); @@ -32,14 +32,14 @@ int main (const int argc, const char * argv[]) // Initialize a example graph for Breadth First Search Graph bfsGraph( { - {1, {2, 5}}, // Node 1 - {2, {1, 6}}, // Node 2... - {3, {4, 6, 7}}, - {4, {3, 7, 8}}, - {5, {1}}, - {6, {2, 3, 7}}, - {7, {3, 4, 6, 8}}, - {8, {4, 6}}, + {1, {{2, 0}, {5, 0}}}, // Node 1 + {2, {{1, 0}, {6, 0}}}, // Node 2... + {3, {{4, 0}, {6, 0}, {7, 0}}}, + {4, {{3, 0}, {7, 0}, {8, 0}}}, + {5, {{1, 0}}}, + {6, {{2, 0}, {3, 0}, {7, 0}}}, + {7, {{3, 0}, {4, 0}, {6, 0}, {8, 0}}}, + {8, {{4, 0}, {6, 0}}}, } ); // The graph traversed in this example is seen in MIT Intro to Algorithms @@ -68,12 +68,12 @@ int main (const int argc, const char * argv[]) // Initialize an example graph for Depth First Search Graph dfsGraph( { - {1, {2, 4}}, - {2, {5}}, - {3, {5, 6}}, - {4, {2}}, - {5, {4}}, - {6, {6}}, + {1, {{2, 0}, {4, 0}}}, + {2, {{5, 0}}}, + {3, {{5, 0}, {6, 0}}}, + {4, {{2, 0}}}, + {5, {{4, 0}}}, + {6, {{6, 0}}}, } ); // The graph traversed in this example is seen in MIT Intro to Algorithms @@ -89,15 +89,15 @@ int main (const int argc, const char * argv[]) // The book starts on the 'shirt' node (with the number 6, in this example) Graph topologicalGraph ( { - {1, {4, 5}}, // undershorts - {2, {5}}, // socks - {3, {}}, // watch - {4, {5, 7}}, // pants - {5, {}}, // shoes - {6, {8, 7}}, // shirt - {7, {9}}, // belt - {8, {9}}, // tie - {9, {}}, // jacket + {1, {{4, 0}, {5, 0}}}, // undershorts + {2, {{5, 0}}}, // socks + {3, {}}, // watch + {4, {{5, 0}, {7, 0}}}, // pants + {5, {}}, // shoes + {6, {{8, 0}, {7, 0}}}, // shirt + {7, {{9, 0}}}, // belt + {8, {{9, 0}}}, // tie + {9, {}}, // jacket } ); @@ -119,15 +119,15 @@ int main (const int argc, const char * argv[]) // + We have to initialize the graph carefully to get this result - Graph topologicalGraph2 ( { - {6, {8, 7}}, // shirt - {8, {9}}, // tie - {7, {9}}, // belt - {9, {}}, // jacket - {3, {}}, // watch - {1, {4, 5}}, // undershorts - {4, {5, 7}}, // pants - {5, {}}, // shoes - {2, {5}}, // socks + {6, {{8, 0}, {7, 0}}}, // shirt + {8, {{9, 0}}}, // tie + {7, {{9, 0}}}, // belt + {9, {}}, // jacket + {3, {}}, // watch + {1, {{4, 0}, {5, 0}}}, // undershorts + {4, {{5, 0}, {7, 0}}}, // pants + {5, {}}, // shoes + {2, {{5, 0}}}, // socks } ); auto order2 = topologicalGraph2.TopologicalSort(*topologicalGraph2.NodeBegin()); @@ -137,4 +137,31 @@ int main (const int argc, const char * argv[]) order2.pop_back(); } std::cout << std::endl; + + + std::cout << "\n\n##### Minimum Spanning Trees #####\n"; + // This example graph is seen in MIT Algorithms chapter 23, figure 23.4 + // + The result we produce is the same in total weight + // + Differs only in the connection of nodes (2->3) *instead of* (8->1) + // ++ Both of these edges have the same weight, and we do not create a cycle + Graph graphMST( + { + {1, {{2, 4}}}, + {2, {{3, 8}}}, + {3, {{4, 7}}}, + {4, {{5, 9}}}, + {5, {{6, 10}}}, + {6, {{3, 4}, {4, 14}, {7, 2}}}, + {7, {{8, 1}}}, + {8, {{1, 8}, {2, 11}, {9, 7}}}, + {9, {{3, 2}, {7, 6}}} + } + ); + InfoMST resultMST = graphMST.KruskalMST(); + std::cout << "Finding MST using Kruskal's...\n\nMST result: \n"; + for (const auto &edge : resultMST.edgesMST) { + std::cout << "Connected nodes: " << edge.second.first << "->" + << edge.second.second << " with weight of " << edge.first << "\n"; + } + std::cout << "Total MST weight: " << resultMST.weightMST << std::endl; } diff --git a/cpp/algorithms/graphs/weighted/lib-graph.cpp b/cpp/algorithms/graphs/weighted/lib-graph.cpp index c9b019d..cc92072 100644 --- a/cpp/algorithms/graphs/weighted/lib-graph.cpp +++ b/cpp/algorithms/graphs/weighted/lib-graph.cpp @@ -33,18 +33,18 @@ InfoBFS Graph::BFS(const Node& startNode) const // Check if we have already discovered all the adjacentNodes to thisNode for (const auto &adjacent : thisNode->adjacent) { - if (searchInfo[adjacent.GetNumber()].discovered == White) { - std::cout << "Found undiscovered adjacentNode: " << adjacent.GetNumber() + if (searchInfo[adjacent.first].discovered == White) { + std::cout << "Found undiscovered adjacentNode: " << adjacent.first << "\n"; // Mark the adjacent node as in progress - searchInfo[adjacent.GetNumber()].discovered = Gray; - searchInfo[adjacent.GetNumber()].distance = + searchInfo[adjacent.first].discovered = Gray; + searchInfo[adjacent.first].distance = searchInfo[thisNode->number].distance + 1; - searchInfo[adjacent.GetNumber()].predecessor = + searchInfo[adjacent.first].predecessor = &GetNode(thisNode->number); // Add the discovered node the the visitQueue - visitQueue.push(&GetNode(adjacent.GetNumber())); + visitQueue.push(&GetNode(adjacent.first)); } } // We are finished with this node and the adjacent nodes; Mark it discovered @@ -154,12 +154,12 @@ void Graph::DFSVisit(int &time, const Node& startNode, InfoDFS &searchInfo) cons // Check the adjacent nodes of the startNode for (const auto &adjacent : startNode.adjacent) { auto iter = std::find(nodes_.begin(), nodes_.end(), - Node(adjacent.GetNumber(), {})); + Node(adjacent.first, {})); // If the adjacentNode is undiscovered, visit it // + Offset by 1 to account for 0 index of discovered vector if (searchInfo[iter->number].discovered == White) { std::cout << "Found undiscovered adjacentNode: " - << GetNode(adjacent.GetNumber()).number << std::endl; + << GetNode(adjacent.first).number << std::endl; // Visiting the undiscovered node will check it's adjacent nodes DFSVisit(time, *iter, searchInfo); } @@ -187,3 +187,29 @@ std::vector Graph::TopologicalSort(const Node &startNode) const return order; } +InfoMST Graph::KruskalMST() const +{ + InfoMST searchInfo(nodes_); + // The ctor for InfoMST initializes all edges within the graph into a multimap + // + Key for multimap is edge weight, so they're already sorted in ascending + + // For each edge in the graph, check if they are part of the same tree + // + Since we do not want to create a cycle in the MST forest - + // + we don't connect nodes that are part of the same tree + for (const auto &edge : searchInfo.edges) { + // Two integers representing the node.number for the connected nodes + const int u = edge.second.first; + const int v = edge.second.second; + // Check if the nodes are of the same tree + if (searchInfo.FindSet(u) != searchInfo.FindSet(v)) { + // If they are not, add the edge to our MST + searchInfo.edgesMST.emplace(edge); + searchInfo.weightMST += edge.first; + // Update the forest to reflect this change + searchInfo.Union(u, v); + } + } + + return searchInfo; +} + diff --git a/cpp/algorithms/graphs/weighted/lib-graph.hpp b/cpp/algorithms/graphs/weighted/lib-graph.hpp index e7c2a54..3944a14 100644 --- a/cpp/algorithms/graphs/weighted/lib-graph.hpp +++ b/cpp/algorithms/graphs/weighted/lib-graph.hpp @@ -51,6 +51,11 @@ struct DFS : SearchInfo { std::pair discoveryFinish; }; +struct MST : SearchInfo { + int32_t parent = INT32_MIN; + int rank = 0; +}; + // Store search information in unordered_maps so we can pass it around easily // + Allows each node to store relative information on the traversal using InfoBFS = std::unordered_map; @@ -59,7 +64,6 @@ using InfoDFS = std::unordered_map; /******************************************************************************/ // Node structure for representing a graph -struct Link; struct Node { public: @@ -70,7 +74,11 @@ public: swap(*this, rhs); return *this; } - Node(int num, std::vector adj) : number(num), adjacent(std::move(adj)) {} + Node(int num, const std::vector> &adj) : number(num) + { + // Place each adjacent node in vector into our unordered_map of edges + for (const auto &i : adj) adjacent.emplace(i.first, i.second); + } friend void swap(Node &a, Node &b) { std::swap(a.number, b.number); @@ -78,7 +86,8 @@ public: } int number; - std::vector adjacent; + // Adjacent stored in an unordered_map + std::unordered_map adjacent; // Define operator== for std::find; And comparisons between nodes bool operator==(const Node &b) const { return this->number == b.number;} @@ -86,12 +95,66 @@ public: bool operator!=(const Node &b) const { return this->number != b.number;} }; -struct Link { - explicit Link(Node *n, int w=0) : node(n), weight(w) {} +using Edges = std::multimap>; +struct InfoMST { + explicit InfoMST(const std::vector &nodes) { + for (const auto &node : nodes){ + // Initialize the default values for forest tracked by this struct + // + This data is used in KruskalMST() to find the MST + MakeSet(node.number); + for (const auto adj : node.adjacent) { + // node.number is the number that represents this node + // adj.first is the node number that is connected to this node + // adj.second is the weight of the connected edge + edges.emplace(adj.second, std::make_pair(node.number, adj.first)); + // So we initialize the multimap> + // + Since a multimap sorts by key, we have sorted our edges by weight + } + } + } + + std::unordered_map searchInfo; + // All of the edges within our graph + // + Since each node stores its own edges, this is initialized in InfoMST ctor + Edges edges; + + // A multimap of the edges found for our MST + Edges edgesMST; + // The total weight of our resulting MST + int weightMST = 0; + + void MakeSet(int x) + { + searchInfo[x].parent = x; + searchInfo[x].rank = 0; + } + + void Union(int x, int y) + { + Link(FindSet(x), FindSet(y)); + } + + void Link(int x, int y) + { + if (searchInfo[x].rank > searchInfo[y].rank) { + searchInfo[y].parent = x; + } + else { + searchInfo[x].parent = y; + if (searchInfo[x].rank == searchInfo[y].rank) { + searchInfo[y].rank += 1; + } + } + } + + int FindSet(int x) + { + if (x != searchInfo[x].parent) { + searchInfo[x].parent = FindSet(searchInfo[x].parent); + } + return searchInfo[x].parent; + } - Node *node; - int weight; - inline int GetNumber() const { return node->number;} }; /******************************************************************************/ @@ -113,6 +176,8 @@ public: void DFSVisit(int &time, const Node& startNode, InfoDFS &searchInfo) const; // Topological sort, using DFS std::vector TopologicalSort(const Node &startNode) const; + // Kruskal's MST + InfoMST KruskalMST() const; // Returns a copy of a node with the number i within the graph // + This uses the private, non-const accessor GetNode() and returns a copy