Skip to content

Commit ddfecb1

Browse files
implement the union find structure aswell as kruskal for MST
1 parent f4ed634 commit ddfecb1

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package algorithms.graph
2+
3+
import datastructures.graph.Graph
4+
import datastructures.union.UnionFind
5+
import scala.collection.mutable
6+
7+
/// Kruskal’s Algorithm
8+
/// Computes the Minimum Spanning Tree (MST) of a weighted graph using a greedy approach
9+
/// It sorts all the edges and adds them to the MST, ensuring no cycles are formed by using Union-Find.
10+
///
11+
/// The algorithm runs in O(E log E) time complexity due to sorting the edges
12+
13+
def kruskal[T](graph: Graph[T]): Set[(T, T, Double)] = {
14+
val edges = graph.getEdges.toList
15+
if (edges.isEmpty) return Set.empty // Handle empty graph
16+
17+
// Initialize UnionFind for vertices
18+
val uf = new UnionFind[Int]()
19+
graph.getVertices.foreach(v => uf.addElement(graph.getVertices.toList.indexOf(v)))
20+
21+
// Sort edges by their weight
22+
val sortedEdges = edges.sortBy(_.weight)
23+
24+
val mstEdges = mutable.Set[(T, T, Double)]()
25+
26+
// Iterate through sorted edges and add to MST if they don't form a cycle
27+
for (edge <- sortedEdges) {
28+
val srcIndex = graph.getVertices.toList.indexOf(edge.src)
29+
val destIndex = graph.getVertices.toList.indexOf(edge.dest)
30+
31+
if (!uf.isConnected(srcIndex, destIndex)) {
32+
uf.union(srcIndex, destIndex)
33+
mstEdges.add((edge.src, edge.dest, edge.weight))
34+
}
35+
}
36+
37+
mstEdges.toSet
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package datastructures.union
2+
3+
import scala.collection.mutable.ArrayBuffer
4+
5+
/// A Union-Find (Disjoint Set) implementation with path compression and union by rank.
6+
///
7+
/// Time Complexity:
8+
/// - Find: O(α(n)), where α is the inverse Ackermann function (amortized time)
9+
/// - Union: O(α(n))
10+
/// - Size: O(1)
11+
/// - IsConnected: O(α(n))
12+
/// - AddElement: O(1)
13+
class UnionFind[T] {
14+
private val parent: ArrayBuffer[Int] = ArrayBuffer.empty[Int]
15+
private val rank: ArrayBuffer[Int] = ArrayBuffer.empty[Int]
16+
private val elements: ArrayBuffer[T] = ArrayBuffer.empty[T]
17+
18+
def addElement(element: T): Unit = {
19+
elements.append(element)
20+
parent.append(parent.size)
21+
rank.append(0)
22+
}
23+
24+
private def find(index: Int): Int = {
25+
if (parent(index) != index) {
26+
parent(index) = find(parent(index)) // Path compression
27+
}
28+
parent(index)
29+
}
30+
31+
def union(index1: Int, index2: Int): Unit = {
32+
val root1 = find(index1)
33+
val root2 = find(index2)
34+
35+
if (root1 != root2) {
36+
// Union by rank
37+
if (rank(root1) > rank(root2)) {
38+
parent(root2) = root1
39+
} else if (rank(root1) < rank(root2)) {
40+
parent(root1) = root2
41+
} else {
42+
parent(root2) = root1
43+
rank(root1) += 1
44+
}
45+
}
46+
}
47+
48+
def isConnected(index1: Int, index2: Int): Boolean = {
49+
find(index1) == find(index2)
50+
}
51+
52+
def getParent(index: Int): Int = find(index)
53+
54+
def size: Int = parent.size
55+
56+
def getElement(index: Int): T = elements(index)
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package algorithms.graph
2+
3+
import munit.FunSuite
4+
import datastructures.graph.Graph
5+
import algorithms.graph.kruskal
6+
7+
class KruskalTest extends FunSuite {
8+
9+
test("Kruskal should compute MST correctly") {
10+
val graph = new Graph[String](isDirected = false)
11+
graph.addEdge("A", "B", 1.0)
12+
graph.addEdge("A", "C", 3.0)
13+
graph.addEdge("B", "C", 1.0)
14+
graph.addEdge("B", "D", 4.0)
15+
graph.addEdge("C", "D", 1.0)
16+
graph.addEdge("C", "E", 5.0)
17+
graph.addEdge("D", "E", 2.0)
18+
19+
val mst = kruskal(graph)
20+
21+
val expectedMST = Set(
22+
("A", "B", 1.0),
23+
("B", "C", 1.0),
24+
("C", "D", 1.0),
25+
("D", "E", 2.0)
26+
)
27+
28+
assertEquals(mst.size, expectedMST.size)
29+
assertEquals(mst.map(e => (e._1, e._2, e._3)), expectedMST)
30+
}
31+
32+
test("Kruskal should handle a single-node graph") {
33+
val graph = new Graph[String](isDirected = false)
34+
graph.addVertex("A")
35+
36+
val mst = kruskal(graph)
37+
38+
assertEquals(mst, Set.empty)
39+
}
40+
41+
}

0 commit comments

Comments
 (0)