Add example of finding MST using Kruskal's algorithm
+ Using example graph and pseudocode from MIT Algorithms
This commit is contained in:
parent
835dbc7f7d
commit
23c4f0e491
|
@ -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 ##
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue