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 ##
|
||||
## 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 ##
|
||||
################################################################################
|
||||
|
|
|
@ -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,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)
|
||||
Graph topologicalGraph (
|
||||
{
|
||||
{1, {4, 5}}, // undershorts
|
||||
{2, {5}}, // socks
|
||||
{1, {{4, 0}, {5, 0}}}, // undershorts
|
||||
{2, {{5, 0}}}, // socks
|
||||
{3, {}}, // watch
|
||||
{4, {5, 7}}, // pants
|
||||
{4, {{5, 0}, {7, 0}}}, // pants
|
||||
{5, {}}, // shoes
|
||||
{6, {8, 7}}, // shirt
|
||||
{7, {9}}, // belt
|
||||
{8, {9}}, // tie
|
||||
{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
|
||||
{6, {{8, 0}, {7, 0}}}, // shirt
|
||||
{8, {{9, 0}}}, // tie
|
||||
{7, {{9, 0}}}, // belt
|
||||
{9, {}}, // jacket
|
||||
{3, {}}, // watch
|
||||
{1, {4, 5}}, // undershorts
|
||||
{4, {5, 7}}, // pants
|
||||
{1, {{4, 0}, {5, 0}}}, // undershorts
|
||||
{4, {{5, 0}, {7, 0}}}, // pants
|
||||
{5, {}}, // shoes
|
||||
{2, {5}}, // socks
|
||||
{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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue