Skip to content

Commit

Permalink
Added Boruvka Algroithm implementation, fixed disjoint set implementa…
Browse files Browse the repository at this point in the history
…tion, and added unit tests (#9)
  • Loading branch information
jonghough authored Feb 20, 2024
1 parent 33cb320 commit cea9afb
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 33 deletions.
78 changes: 78 additions & 0 deletions SharpGraph.Tests/test/SpanningTreeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,84 @@ public void MinimumSpanningTreePrimTest2()
Assert.True(span.Contains(wedge1) && span.Contains(wedge2) && !span.Contains(wedge3));
}

[Fact]
public void MinimumSpanningTreeBoruvkaTest1()
{
var nodes = NodeGenerator.GenerateNodes(8);
var nodeList = new List<Node>(nodes);
var wedge1 = new Edge(nodeList[0], nodeList[1]);
var wedge2 = new Edge(nodeList[0], nodeList[2]);
var wedge3 = new Edge(nodeList[0], nodeList[7]);
var wedge4 = new Edge(nodeList[1], nodeList[2]);
var wedge5 = new Edge(nodeList[1], nodeList[3]);
var wedge6 = new Edge(nodeList[1], nodeList[5]);
var wedge7 = new Edge(nodeList[2], nodeList[4]);
var wedge8 = new Edge(nodeList[2], nodeList[5]);
var wedge9 = new Edge(nodeList[2], nodeList[6]);
var wedge10 = new Edge(nodeList[3], nodeList[5]);
var wedge11 = new Edge(nodeList[4], nodeList[6]);
var wedge12 = new Edge(nodeList[5], nodeList[6]);
var wedge13 = new Edge(nodeList[6], nodeList[7]);
var edges = new List<Edge>();
edges.Add(wedge1);
edges.Add(wedge2);
edges.Add(wedge3);
edges.Add(wedge4);
edges.Add(wedge5);
edges.Add(wedge6);
edges.Add(wedge7);
edges.Add(wedge8);
edges.Add(wedge9);
edges.Add(wedge10);
edges.Add(wedge11);
edges.Add(wedge12);
edges.Add(wedge13);
var g = new Graph(edges);

g.AddComponent<EdgeWeight>(wedge1).Weight = 2.4f;
g.AddComponent<EdgeWeight>(wedge2).Weight = 23.4f;
g.AddComponent<EdgeWeight>(wedge3).Weight = 13.5f;
g.AddComponent<EdgeWeight>(wedge4).Weight = 66.4f;
g.AddComponent<EdgeWeight>(wedge5).Weight = 19.14f;
g.AddComponent<EdgeWeight>(wedge6).Weight = 7.905f;
g.AddComponent<EdgeWeight>(wedge7).Weight = 17.05f;
g.AddComponent<EdgeWeight>(wedge8).Weight = 100.8f;
g.AddComponent<EdgeWeight>(wedge9).Weight = 88.8f;
g.AddComponent<EdgeWeight>(wedge10).Weight = 10.8f;
g.AddComponent<EdgeWeight>(wedge11).Weight = 20.8f;
g.AddComponent<EdgeWeight>(wedge12).Weight = 14.2f;
g.AddComponent<EdgeWeight>(wedge13).Weight = 10.55f;

var span = g.GenerateMinimumSpanningTree(SpanningTreeAlgorithm.Boruvka);

Assert.Equal(7, span.Count);
}

[Fact]
public void MinimumSpanningTreeBoruvkaTest2()
{
var b1 = new Node("1");
var b2 = new Node("2");
var b3 = new Node("3");
var wedge1 = new Edge(b1, b2);
var wedge2 = new Edge(b1, b3);
var wedge3 = new Edge(b2, b3);
var edges = new List<Edge>();
edges.Add(wedge1);
edges.Add(wedge2);
edges.Add(wedge3);

var g = new Graph(edges);

g.AddComponent<EdgeWeight>(wedge1).Weight = -5.1f;
g.AddComponent<EdgeWeight>(wedge2).Weight = 0f;
g.AddComponent<EdgeWeight>(wedge3).Weight = 3.5f;
var span = g.GenerateMinimumSpanningTree(SpanningTreeAlgorithm.Boruvka);

// minimum spanning tree should contain only edges 1 and 2, not edge 3.
Assert.True(span.Contains(wedge1) && span.Contains(wedge2) && !span.Contains(wedge3));
}

[Fact]
public void SpanningTreeTest1()
{
Expand Down
14 changes: 12 additions & 2 deletions SharpGraph/src/algorithms/DisjointSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ namespace SharpGraph
/// <summary>
/// Disjoint set.
/// </summary>
// internal class DisjointSet
// {
// internal int Parent;
// internal int Rank;
// internal DisjointSet(int parent, int rank)
// {
// this.Parent = parent;
// this.Rank = rank;
// }
// }
internal class DisjointSet
{
internal int Parent;
internal Node Parent;

Check warning on line 24 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 24 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Check warning on line 24 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 24 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

internal int Rank;

Check warning on line 25 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 25 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Check warning on line 25 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 25 in SharpGraph/src/algorithms/DisjointSet.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)


internal DisjointSet(int parent, int rank)
internal DisjointSet(Node parent, int rank)
{
this.Parent = parent;
this.Rank = rank;
Expand Down
141 changes: 110 additions & 31 deletions SharpGraph/src/algorithms/Graph.SpanningTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace SharpGraph
{
public enum SpanningTreeAlgorithm
{
Boruvka,
Kruskal,
Prim,
}
Expand Down Expand Up @@ -67,6 +68,8 @@ public List<Edge> GenerateMinimumSpanningTree(

switch (spanningTreeAlgorithm)
{
case SpanningTreeAlgorithm.Boruvka:
return this.GenerateMinimumSpanningTreeBoruvka();
case SpanningTreeAlgorithm.Kruskal:
return this.GenerateMinimumSpanningTreeKruskal();
case SpanningTreeAlgorithm.Prim:
Expand Down Expand Up @@ -94,19 +97,22 @@ public List<Edge> GenerateSpanningTree()
var edges = this.GetEdges();
_ = new List<Edge>(edges);
var stEdges = new List<Edge>();
var ds = new List<DisjointSet>();
var nodeIndexDict = new Dictionary<Node, int>();
var ds = new Dictionary<Node, DisjointSet>();
var nodeList = this.nodes.ToList();
for (var i = 0; i < nodeList.Count; i++)
{
ds.Add(new DisjointSet(i, 0));
nodeIndexDict[nodeList[i]] = i;
}

foreach (var edge in edges)
{
var x = this.Find(ds, nodeIndexDict[edge.From()]);
var y = this.Find(ds, nodeIndexDict[edge.To()]);
var (x, b1) = this.Find(ds, edge.From());
var (y, b2) = this.Find(ds, edge.To());
if (!b1)
{
ds[x] = new DisjointSet(x, 0);
}

if (!b2)
{
ds[y] = new DisjointSet(y, 0);
}

if (x != y)
{
Expand All @@ -118,39 +124,110 @@ public List<Edge> GenerateSpanningTree()
return stEdges;
}

internal int Find(List<DisjointSet> subsets, int node)
internal (Node, bool) Find(Dictionary<Node, DisjointSet> subsets, Node node)
{
if (!subsets.ContainsKey(node))
{
return (node, false);
}

if (subsets[node].Parent != node)
{
subsets[node].Parent = this.Find(subsets, subsets[node].Parent);
var a = this.Find(subsets, subsets[node].Parent);
if (a.Item2)
{
subsets[node].Parent = a.Item1;
}
}

return subsets[node].Parent;
return (subsets[node].Parent, true);
}

internal void Union(List<DisjointSet> subsets, int a, int b)
internal void Union(Dictionary<Node, DisjointSet> subsets, Node a, Node b)
{
var rootA = this.Find(subsets, a);
var rootB = this.Find(subsets, b);

if (subsets[rootA].Rank < subsets[rootB].Rank)
if (subsets[rootA.Item1].Rank < subsets[rootB.Item1].Rank)
{
subsets[rootA].Parent = rootB;
subsets[rootA.Item1].Parent = rootB.Item1;
}
else
{
subsets[rootB].Parent = rootA;
if (subsets[rootB].Rank == subsets[rootA].Rank)
subsets[rootB.Item1].Parent = rootA.Item1;
if (subsets[rootB.Item1].Rank == subsets[rootA.Item1].Rank)
{
subsets[rootA.Item1].Rank++;
}
}
}

/// <summary>
/// Generates a minimum spanning tree on the graph. The weight is defined using the.
/// <code>EdgeWeight</code> component's. <code>Weight</code> value.
/// The algorithm uses <i>Boruvka's Algorithm</i>. The time complexity is
/// ~O(E log N), where E is number of edges, N is number of nodes.
/// </summary>
/// <returns>Minimum spanning tree, as a list of edges.</returns>
private List<Edge> GenerateMinimumSpanningTreeBoruvka()
{
var ds = new Dictionary<Node, DisjointSet>();
var nodeList = this.nodes.ToList();
var stEdges = new List<Edge>();
var minimumEdges = new Dictionary<Node, float>();

var nTrees = 0;

while (nTrees < nodeList.Count - 1)
{
foreach (var edge in this.edges)
{
subsets[rootA].Rank++;
var (x, b1) = this.Find(ds, edge.From());
var (y, b2) = this.Find(ds, edge.To());
if (!b1)
{
ds[x] = new DisjointSet(x, 0);
}

if (!b2)
{
ds[y] = new DisjointSet(y, 0);
}

if (x != y)
{
var edgeWeight = this.GetComponent<EdgeWeight>(edge).Weight;
if (
(minimumEdges.ContainsKey(x) && edgeWeight <= minimumEdges[x])
|| !minimumEdges.ContainsKey(x)
)
{
minimumEdges[x] = edgeWeight;
}

if (
(minimumEdges.ContainsKey(y) && edgeWeight <= minimumEdges[y])
|| !minimumEdges.ContainsKey(y)
)
{
minimumEdges[y] = edgeWeight;
}

stEdges.Add(edge);
nTrees++;
this.Union(ds, x, y);
}
}
}

return stEdges;
}

/// <summary>
/// Generates a minimum spanning tree on the graph. The weight is defined using the.
/// <code>EdgeWeight</code> component's. <code>Weight</code> value.
/// The algorithm uses <i>Kruskal's Algorithm</i>.
/// The algorithm uses <i>Kruskal's Algorithm</i>. The time complexity is
/// ~O(E log N), where E is number of edges, N is number of nodes.
/// </summary>
/// <returns>Minimum spanning tree, as a list of edges.</returns>
private List<Edge> GenerateMinimumSpanningTreeKruskal()
Expand All @@ -163,21 +240,22 @@ private List<Edge> GenerateMinimumSpanningTreeKruskal()
.Weight.CompareTo(this.GetComponent<EdgeWeight>(y).Weight);
}
);
var gcopy = new List<Edge>(edges);
var stEdges = new List<Edge>();
var ds = new List<DisjointSet>();
var nodeIndexDict = new Dictionary<Node, int>();
var nodeList = this.nodes.ToList();
for (var i = 0; i < nodeList.Count; i++)
{
ds.Add(new DisjointSet(i, 0));
nodeIndexDict[nodeList[i]] = i;
}
var ds = new Dictionary<Node, DisjointSet>();

foreach (var edge in edges)
{
var x = this.Find(ds, nodeIndexDict[edge.From()]);
var y = this.Find(ds, nodeIndexDict[edge.To()]);
var (x, b1) = this.Find(ds, edge.From());
var (y, b2) = this.Find(ds, edge.To());
if (!b1)
{
ds[x] = new DisjointSet(x, 0);
}

if (!b2)
{
ds[y] = new DisjointSet(y, 0);
}

if (x != y)
{
Expand All @@ -192,7 +270,8 @@ private List<Edge> GenerateMinimumSpanningTreeKruskal()
/// <summary>
/// Generates a minimum spanning tree on the graph. The weight is defined using the.
/// <code>EdgeWeight</code> component's. <code>Weight</code> value.
/// The algorithm uses <i>Prim's Algorithm</i>.
/// The algorithm uses <i>Prim's Algorithm</i>. The time complexity is
/// ~O(E log N), where E is number of edges, N is number of nodes.
/// </summary>
/// <returns>Minimum spanning tree, as a list of edges.</returns>
private List<Edge> GenerateMinimumSpanningTreePrim()
Expand Down

0 comments on commit cea9afb

Please sign in to comment.