Add example of finding MST using Kruskal's algorithm

+ Using example graph and pseudocode from MIT Algorithms
This commit is contained in:
Shaun Reed 2021-07-16 18:16:04 -04:00
parent 835dbc7f7d
commit 23c4f0e491
4 changed files with 176 additions and 58 deletions

View File

@ -1,7 +1,7 @@
################################################################################ ################################################################################
## Author: Shaun Reed ## ## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## ## 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 ## ## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
################################################################################ ################################################################################

View File

@ -1,7 +1,7 @@
/*############################################################################## /*##############################################################################
## Author: Shaun Reed ## ## Author: Shaun Reed ##
## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## ## 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 ## ## Algorithms in this example are found in MIT Intro to Algorithms ##
## ## ## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## ## 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... // We could initialize the graph with some localNodes...
std::vector<Node> localNodes{ std::vector<Node> localNodes{
{1, {2, 5}}, // Node 1 {1, {{2, 0}, {5, 0}}}, // Node 1
{2, {1, 6}}, // Node 2 {2, {{1, 0}, {6, 0}}}, // Node 2
{3, {4, 6, 7}}, {3, {{4, 0}, {6, 0}, {7, 0}}},
{4, {3, 7, 8}}, {4, {{3, 0}, {7, 0}, {8, 0}}},
{5, {1}}, {5, {{1, 0}}},
{6, {2, 3, 7}}, {6, {{2, 0}, {3, 0}, {7, 0}}},
{7, {3, 4, 6, 8}}, {7, {{3, 0}, {4, 0}, {6, 0}, {8, 0}}},
{8, {4, 6}}, {8, {{4, 0}, {6, 0}}},
}; };
Graph bfsGraphInit(localNodes); Graph bfsGraphInit(localNodes);
@ -32,14 +32,14 @@ int main (const int argc, const char * argv[])
// Initialize a example graph for Breadth First Search // Initialize a example graph for Breadth First Search
Graph bfsGraph( Graph bfsGraph(
{ {
{1, {2, 5}}, // Node 1 {1, {{2, 0}, {5, 0}}}, // Node 1
{2, {1, 6}}, // Node 2... {2, {{1, 0}, {6, 0}}}, // Node 2...
{3, {4, 6, 7}}, {3, {{4, 0}, {6, 0}, {7, 0}}},
{4, {3, 7, 8}}, {4, {{3, 0}, {7, 0}, {8, 0}}},
{5, {1}}, {5, {{1, 0}}},
{6, {2, 3, 7}}, {6, {{2, 0}, {3, 0}, {7, 0}}},
{7, {3, 4, 6, 8}}, {7, {{3, 0}, {4, 0}, {6, 0}, {8, 0}}},
{8, {4, 6}}, {8, {{4, 0}, {6, 0}}},
} }
); );
// The graph traversed in this example is seen in MIT Intro to Algorithms // 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 // Initialize an example graph for Depth First Search
Graph dfsGraph( Graph dfsGraph(
{ {
{1, {2, 4}}, {1, {{2, 0}, {4, 0}}},
{2, {5}}, {2, {{5, 0}}},
{3, {5, 6}}, {3, {{5, 0}, {6, 0}}},
{4, {2}}, {4, {{2, 0}}},
{5, {4}}, {5, {{4, 0}}},
{6, {6}}, {6, {{6, 0}}},
} }
); );
// The graph traversed in this example is seen in MIT Intro to Algorithms // The graph traversed in this example is seen in MIT Intro to Algorithms
@ -89,14 +89,14 @@ int main (const int argc, const char * argv[])
// The book starts on the 'shirt' node (with the number 6, in this example) // The book starts on the 'shirt' node (with the number 6, in this example)
Graph topologicalGraph ( Graph topologicalGraph (
{ {
{1, {4, 5}}, // undershorts {1, {{4, 0}, {5, 0}}}, // undershorts
{2, {5}}, // socks {2, {{5, 0}}}, // socks
{3, {}}, // watch {3, {}}, // watch
{4, {5, 7}}, // pants {4, {{5, 0}, {7, 0}}}, // pants
{5, {}}, // shoes {5, {}}, // shoes
{6, {8, 7}}, // shirt {6, {{8, 0}, {7, 0}}}, // shirt
{7, {9}}, // belt {7, {{9, 0}}}, // belt
{8, {9}}, // tie {8, {{9, 0}}}, // tie
{9, {}}, // jacket {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 - // + We have to initialize the graph carefully to get this result -
Graph topologicalGraph2 ( Graph topologicalGraph2 (
{ {
{6, {8, 7}}, // shirt {6, {{8, 0}, {7, 0}}}, // shirt
{8, {9}}, // tie {8, {{9, 0}}}, // tie
{7, {9}}, // belt {7, {{9, 0}}}, // belt
{9, {}}, // jacket {9, {}}, // jacket
{3, {}}, // watch {3, {}}, // watch
{1, {4, 5}}, // undershorts {1, {{4, 0}, {5, 0}}}, // undershorts
{4, {5, 7}}, // pants {4, {{5, 0}, {7, 0}}}, // pants
{5, {}}, // shoes {5, {}}, // shoes
{2, {5}}, // socks {2, {{5, 0}}}, // socks
} }
); );
auto order2 = topologicalGraph2.TopologicalSort(*topologicalGraph2.NodeBegin()); auto order2 = topologicalGraph2.TopologicalSort(*topologicalGraph2.NodeBegin());
@ -137,4 +137,31 @@ int main (const int argc, const char * argv[])
order2.pop_back(); order2.pop_back();
} }
std::cout << std::endl; 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;
} }

View File

@ -33,18 +33,18 @@ InfoBFS Graph::BFS(const Node& startNode) const
// Check if we have already discovered all the adjacentNodes to thisNode // Check if we have already discovered all the adjacentNodes to thisNode
for (const auto &adjacent : thisNode->adjacent) { for (const auto &adjacent : thisNode->adjacent) {
if (searchInfo[adjacent.GetNumber()].discovered == White) { if (searchInfo[adjacent.first].discovered == White) {
std::cout << "Found undiscovered adjacentNode: " << adjacent.GetNumber() std::cout << "Found undiscovered adjacentNode: " << adjacent.first
<< "\n"; << "\n";
// Mark the adjacent node as in progress // Mark the adjacent node as in progress
searchInfo[adjacent.GetNumber()].discovered = Gray; searchInfo[adjacent.first].discovered = Gray;
searchInfo[adjacent.GetNumber()].distance = searchInfo[adjacent.first].distance =
searchInfo[thisNode->number].distance + 1; searchInfo[thisNode->number].distance + 1;
searchInfo[adjacent.GetNumber()].predecessor = searchInfo[adjacent.first].predecessor =
&GetNode(thisNode->number); &GetNode(thisNode->number);
// Add the discovered node the the visitQueue // 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 // 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 // Check the adjacent nodes of the startNode
for (const auto &adjacent : startNode.adjacent) { for (const auto &adjacent : startNode.adjacent) {
auto iter = std::find(nodes_.begin(), nodes_.end(), auto iter = std::find(nodes_.begin(), nodes_.end(),
Node(adjacent.GetNumber(), {})); Node(adjacent.first, {}));
// If the adjacentNode is undiscovered, visit it // If the adjacentNode is undiscovered, visit it
// + Offset by 1 to account for 0 index of discovered vector // + Offset by 1 to account for 0 index of discovered vector
if (searchInfo[iter->number].discovered == White) { if (searchInfo[iter->number].discovered == White) {
std::cout << "Found undiscovered adjacentNode: " 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 // Visiting the undiscovered node will check it's adjacent nodes
DFSVisit(time, *iter, searchInfo); DFSVisit(time, *iter, searchInfo);
} }
@ -187,3 +187,29 @@ std::vector<Node> Graph::TopologicalSort(const Node &startNode) const
return order; 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;
}

View File

@ -51,6 +51,11 @@ struct DFS : SearchInfo {
std::pair<int, int> discoveryFinish; std::pair<int, int> 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 // Store search information in unordered_maps so we can pass it around easily
// + Allows each node to store relative information on the traversal // + Allows each node to store relative information on the traversal
using InfoBFS = std::unordered_map<int, struct BFS>; using InfoBFS = std::unordered_map<int, struct BFS>;
@ -59,7 +64,6 @@ using InfoDFS = std::unordered_map<int, struct DFS>;
/******************************************************************************/ /******************************************************************************/
// Node structure for representing a graph // Node structure for representing a graph
struct Link;
struct Node { struct Node {
public: public:
@ -70,7 +74,11 @@ public:
swap(*this, rhs); swap(*this, rhs);
return *this; return *this;
} }
Node(int num, std::vector<Link> adj) : number(num), adjacent(std::move(adj)) {} Node(int num, const std::vector<std::pair<int, int>> &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) { friend void swap(Node &a, Node &b) {
std::swap(a.number, b.number); std::swap(a.number, b.number);
@ -78,7 +86,8 @@ public:
} }
int number; int number;
std::vector<Link> adjacent; // Adjacent stored in an unordered_map<adj.number, edgeWeight>
std::unordered_map<int, int> adjacent;
// Define operator== for std::find; And comparisons between nodes // Define operator== for std::find; And comparisons between nodes
bool operator==(const Node &b) const { return this->number == b.number;} 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;} bool operator!=(const Node &b) const { return this->number != b.number;}
}; };
struct Link { using Edges = std::multimap<int, std::pair<int, int>>;
explicit Link(Node *n, int w=0) : node(n), weight(w) {} struct InfoMST {
explicit InfoMST(const std::vector<Node> &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<weight, <nodeA.number, nodeB.number>>
// + Since a multimap sorts by key, we have sorted our edges by weight
}
}
}
std::unordered_map<int, struct MST> 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; void DFSVisit(int &time, const Node& startNode, InfoDFS &searchInfo) const;
// Topological sort, using DFS // Topological sort, using DFS
std::vector<Node> TopologicalSort(const Node &startNode) const; std::vector<Node> TopologicalSort(const Node &startNode) const;
// Kruskal's MST
InfoMST KruskalMST() const;
// Returns a copy of a node with the number i within the graph // 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 // + This uses the private, non-const accessor GetNode() and returns a copy