A simple C++ implementation of Kruskal’s algorithm for finding minimal spanning trees in networks. Though I have a previous posting that accomplishes exactly the same thing, I thought that a simple implementation would be useful, one using a straightforward Graph data structure for modelling network links and nodes, does not have a graphical user interface and does not use the Boost Graph Library, which can be complicated to use and not to everyone’s preference.
Pseudocode
KRUSKAL(G):
A = ∅
foreach v ∈ G.V:
MAKE-SET(v)
foreach (u, v) ordered by weight(u, v), increasing:
if FIND-SET(u) ≠ FIND-SET(v):
A = A ∪ {(u, v)}
UNION(u, v)
return A
The Graph data structure
The Graph data structure is described in more detail in a previous post under the section titled “An alternative Graph class”. Suffice to say it provides us with a means of inserting and deleting network links so as to capture a network topology. Class outline as follows:
#include <vector>
#include <algorithm>
// This code is from "Algorithms in C++, Third Edition,"
// by Robert Sedgewick, Addison-Wesley, 2002.
class Edge
{
public:
int v, w;
double wt;
Edge(int v = -1, int w = -1, double wt = 0.0) :
v(v), w(w), wt(wt) { }
};
typedef std::shared_ptr<Edge> EdgePtr;
struct EdgeSort
{
bool operator() ( const EdgePtr& ep1, const EdgePtr& ep2 )
{
return ep1->wt < ep2->wt;
}
};
template <class Edge>
class DenseGRAPH
{
private:
int Vcnt, Ecnt;
bool digraph;
std::vector <std::vector<EdgePtr>> adj;
public:
DenseGRAPH(int V, bool digraph = false) :
adj(V),
Vcnt(V),
Ecnt(0),
digraph(digraph)
{
for ( int i = 0; i < V; i++ ) adj[i].resize( V );
}
int V() const { return Vcnt; }
int E() const { return Ecnt; }
bool directed() const { return digraph; }
void insert( EdgePtr e )
{
int v = e->v;
int w = e->w;
if (adj[v][w] == 0) Ecnt++;
adj[v][w] = e;
if (!digraph) adj[w][v] = e;
}
void remove(EdgePtr e)
{
int v = e->v;
int w = e->w;
if (adj[v][w] != 0)
Ecnt--;
adj[v][w] = 0;
if (!digraph) adj[w][v] = 0;
}
EdgePtr edge(int v, int w) const
{ return adj[v][w]; }
// Get the weighted edges
void edges( std::vector<EdgePtr>& edges )
{
for( int u = 0; u < V(); ++u )
{
DenseGRAPH<Edge>::adjIterator A(*this, u);
for ( EdgePtr e = A.beg(); !A.end(); e = A.nxt() )
{
if ( NULL != e )
{
edges.push_back( e );
}
}
}
}
class adjIterator;
friend class adjIterator;
};
template <class Edge>
class DenseGRAPH<Edge>::adjIterator
{
private:
const DenseGRAPH &G;
int i, v;
public:
adjIterator(const DenseGRAPH<Edge> &G, int v) :
G(G),
v(v),
i(0) {}
EdgePtr beg() { i = -1; return nxt(); }
EdgePtr nxt()
{
for (i++; i < G.V(); i++)
if (G.edge(v, i))
return G.adj[v][i];
return 0;
}
bool end() const { return i >= G.V(); }
};
Example
Consider the following 11-node network showing the network nodes and the weight values of the links connecting them:
Which is built using multiple calls to the insert API as follows:
DenseGRAPH<Edge>* graph = new DenseGRAPH<Edge>( 11, true ); graph->insert( EdgePtr( new Edge( 0, 1, 1.0 ) ) ); graph->insert( EdgePtr( new Edge( 0, 2, 0.6) ) ); graph->insert( EdgePtr( new Edge( 0, 3, 1.0 ) ) ); graph->insert( EdgePtr( new Edge( 0, 4, 1.0 ) ) ); graph->insert( EdgePtr( new Edge( 0, 5, 1.1 ) ) ); graph->insert( EdgePtr( new Edge( 1, 4, 1.8 ) ) ); graph->insert( EdgePtr( new Edge( 1, 10, 1.9 ) ) ); graph->insert( EdgePtr( new Edge( 2, 3, 2.1 ) ) ); graph->insert( EdgePtr( new Edge( 2, 5, 2.3) ) ); graph->insert( EdgePtr( new Edge( 2, 6, 1.7 ) ) ); graph->insert( EdgePtr( new Edge( 3, 4, 1.7 ) ) ); graph->insert( EdgePtr( new Edge( 5, 6, 0.5 ) ) ); graph->insert( EdgePtr( new Edge( 5, 7, 0.7 ) ) ); graph->insert( EdgePtr( new Edge( 5, 8, 0.9 ) ) ); graph->insert( EdgePtr( new Edge( 5, 9, 0.9 ) ) ); graph->insert( EdgePtr( new Edge( 5, 10, 1.0 ) ) ); graph->insert( EdgePtr( new Edge( 6, 7, 1.9 ) ) ); graph->insert( EdgePtr( new Edge( 7, 8, 1.7 ) ) ); graph->insert( EdgePtr( new Edge( 8, 9, 1.6 ) ) ); graph->insert( EdgePtr( new Edge( 9, 10, 2.2 ) ) );
Implementation of Kruskal algorithm
Kruskal’s algorithm works by obtaining a sorted list of the network links, and then adding each least-cost link to another set while disallowing cycles:
void KruskalMST( DenseGRAPH<Edge>* graph, std::vector<EdgePtr>& mst )
{
const int V = graph->V();
// Sort edges in non-decreasing order of their weight
std::vector<EdgePtr> edges;
graph->edges( edges );
std::sort(edges.begin(), edges.end(), EdgeSort() );
// Allocate memory for creating V ssubsets
std::unique_ptr<subset[]> subsets( new subset[ V ]() );
// Create V subsets with single elements
for ( int v = 0; v < V; ++v )
{
subsets[v].parent = v;
subsets[v].rank = 0;
}
for ( std::vector<EdgePtr>::iterator it = edges.begin(); it != edges.end(); ++it )
{
EdgePtr edgePtr = *it;
int x = Find( subsets.get(), edgePtr->v );
int y = Find( subsets.get(), edgePtr->w );
// If including this edge does't cause cycle, include it
// in result and increment the index of result for next edge
if ( x != y )
{
mst.push_back( edgePtr );
Union( subsets.get(), x, y );
}
}
}
Applying Kruskal then results in the following minimal spanning tree:
Console output as follows:
C++ source code downloadable from here.