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 ##
## 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 ##
################################################################################

View File

@ -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<Node> 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;
}

View File

@ -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<Node> 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;
}

View File

@ -51,6 +51,11 @@ struct DFS : SearchInfo {
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
// + Allows each node to store relative information on the traversal
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
struct Link;
struct Node {
public:
@ -70,7 +74,11 @@ public:
swap(*this, rhs);
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) {
std::swap(a.number, b.number);
@ -78,7 +86,8 @@ public:
}
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
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<int, std::pair<int, int>>;
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;
// Topological sort, using DFS
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
// + This uses the private, non-const accessor GetNode() and returns a copy