# Helper class for Union-Find (Disjoint Set) for Kruskal's Algorithm
class DisjointSet:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u]) # Path compression
return self.parent[u]
def union(self, u, v):
rootU = self.find(u)
rootV = self.find(v)
if rootU == rootV:
return False # Already connected
# Union by rank
if self.rank[rootU] < self.rank[rootV]:
self.parent[rootU] = rootV
elif self.rank[rootU] > self.rank[rootV]:
self.parent[rootV] = rootU
else:
self.parent[rootV] = rootU
self.rank[rootU] += 1
return True
# ✅ Function to compute Minimum Spanning Tree
def spanTree(V, edges):
# Step 1: Sort edges by weight
edges.sort(key=lambda x: x[2])
ds = DisjointSet(V)
mst_weight = 0
mst_edges = []
# Step 2: Iterate over sorted edges and build MST
for u, v, weight in edges:
if ds.union(u, v):
mst_edges.append((u, v, weight))
mst_weight += weight
return mst_weight, mst_edges
# ✅ Test Cases to verify correctness
def test_spanTree():
print("Running Test Cases for spanTree...\n")
# Test Case 1: Simple triangle graph
V1 = 3
edges1 = [(0, 1, 1), (1, 2, 2), (0, 2, 3)]
weight1, mst1 = spanTree(V1, edges1)
print("Test Case 1:")
print("Expected Weight: 3")
print("Actual Weight:", weight1)
print("MST Edges:", mst1, "\n")
# Test Case 2: Disconnected graph
V2 = 4
edges2 = [(0, 1, 1), (2, 3, 2)]
weight2, mst2 = spanTree(V2, edges2)
print("Test Case 2:")
print("Expected Weight: 3 (Partial MST)")
print("Actual Weight:", weight2)
print("MST Edges:", mst2, "\n")
# Test Case 3: All edges same weight
V3 = 4
edges3 = [(0, 1, 5), (1, 2, 5), (2, 3, 5), (3, 0, 5)]
weight3, mst3 = spanTree(V3, edges3)
print("Test Case 3:")
print("Expected Weight: 15")
print("Actual Weight:", weight3)
print("MST Edges:", mst3, "\n")
# Test Case 4: Sample example with clear MST
V4 = 4
edges4 = [(0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4)]
weight4, mst4 = spanTree(V4, edges4)
print("Test Case 4:")
print("Expected Weight: 19")
print("Actual Weight:", weight4)
print("MST Edges:", mst4, "\n")
# Test Case 5: Fully connected small graph
V5 = 5
edges5 = [(i, j, i + j) for i in range(V5) for j in range(i+1, V5)]
weight5, mst5 = spanTree(V5, edges5)
print("Test Case 5:")
print("MST Weight:", weight5)
print("MST Edges:", mst5, "\n")
# ✅ Run Tests
test_spanTree()
IyBIZWxwZXIgY2xhc3MgZm9yIFVuaW9uLUZpbmQgKERpc2pvaW50IFNldCkgZm9yIEtydXNrYWwncyBBbGdvcml0aG0KY2xhc3MgRGlzam9pbnRTZXQ6CiAgICBkZWYgX19pbml0X18oc2VsZiwgbik6CiAgICAgICAgc2VsZi5wYXJlbnQgPSBsaXN0KHJhbmdlKG4pKQogICAgICAgIHNlbGYucmFuayA9IFswXSAqIG4KCiAgICBkZWYgZmluZChzZWxmLCB1KToKICAgICAgICBpZiBzZWxmLnBhcmVudFt1XSAhPSB1OgogICAgICAgICAgICBzZWxmLnBhcmVudFt1XSA9IHNlbGYuZmluZChzZWxmLnBhcmVudFt1XSkgICMgUGF0aCBjb21wcmVzc2lvbgogICAgICAgIHJldHVybiBzZWxmLnBhcmVudFt1XQoKICAgIGRlZiB1bmlvbihzZWxmLCB1LCB2KToKICAgICAgICByb290VSA9IHNlbGYuZmluZCh1KQogICAgICAgIHJvb3RWID0gc2VsZi5maW5kKHYpCiAgICAgICAgaWYgcm9vdFUgPT0gcm9vdFY6CiAgICAgICAgICAgIHJldHVybiBGYWxzZSAgIyBBbHJlYWR5IGNvbm5lY3RlZAogICAgICAgICMgVW5pb24gYnkgcmFuawogICAgICAgIGlmIHNlbGYucmFua1tyb290VV0gPCBzZWxmLnJhbmtbcm9vdFZdOgogICAgICAgICAgICBzZWxmLnBhcmVudFtyb290VV0gPSByb290VgogICAgICAgIGVsaWYgc2VsZi5yYW5rW3Jvb3RVXSA+IHNlbGYucmFua1tyb290Vl06CiAgICAgICAgICAgIHNlbGYucGFyZW50W3Jvb3RWXSA9IHJvb3RVCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi5wYXJlbnRbcm9vdFZdID0gcm9vdFUKICAgICAgICAgICAgc2VsZi5yYW5rW3Jvb3RVXSArPSAxCiAgICAgICAgcmV0dXJuIFRydWUKCiMg4pyFIEZ1bmN0aW9uIHRvIGNvbXB1dGUgTWluaW11bSBTcGFubmluZyBUcmVlCmRlZiBzcGFuVHJlZShWLCBlZGdlcyk6CiAgICAjIFN0ZXAgMTogU29ydCBlZGdlcyBieSB3ZWlnaHQKICAgIGVkZ2VzLnNvcnQoa2V5PWxhbWJkYSB4OiB4WzJdKQogICAgZHMgPSBEaXNqb2ludFNldChWKQogICAgCiAgICBtc3Rfd2VpZ2h0ID0gMAogICAgbXN0X2VkZ2VzID0gW10KCiAgICAjIFN0ZXAgMjogSXRlcmF0ZSBvdmVyIHNvcnRlZCBlZGdlcyBhbmQgYnVpbGQgTVNUCiAgICBmb3IgdSwgdiwgd2VpZ2h0IGluIGVkZ2VzOgogICAgICAgIGlmIGRzLnVuaW9uKHUsIHYpOgogICAgICAgICAgICBtc3RfZWRnZXMuYXBwZW5kKCh1LCB2LCB3ZWlnaHQpKQogICAgICAgICAgICBtc3Rfd2VpZ2h0ICs9IHdlaWdodAoKICAgIHJldHVybiBtc3Rfd2VpZ2h0LCBtc3RfZWRnZXMKCiMg4pyFIFRlc3QgQ2FzZXMgdG8gdmVyaWZ5IGNvcnJlY3RuZXNzCmRlZiB0ZXN0X3NwYW5UcmVlKCk6CiAgICBwcmludCgiUnVubmluZyBUZXN0IENhc2VzIGZvciBzcGFuVHJlZS4uLlxuIikKCiAgICAjIFRlc3QgQ2FzZSAxOiBTaW1wbGUgdHJpYW5nbGUgZ3JhcGgKICAgIFYxID0gMwogICAgZWRnZXMxID0gWygwLCAxLCAxKSwgKDEsIDIsIDIpLCAoMCwgMiwgMyldCiAgICB3ZWlnaHQxLCBtc3QxID0gc3BhblRyZWUoVjEsIGVkZ2VzMSkKICAgIHByaW50KCJUZXN0IENhc2UgMToiKQogICAgcHJpbnQoIkV4cGVjdGVkIFdlaWdodDogMyIpCiAgICBwcmludCgiQWN0dWFsIFdlaWdodDoiLCB3ZWlnaHQxKQogICAgcHJpbnQoIk1TVCBFZGdlczoiLCBtc3QxLCAiXG4iKQoKICAgICMgVGVzdCBDYXNlIDI6IERpc2Nvbm5lY3RlZCBncmFwaAogICAgVjIgPSA0CiAgICBlZGdlczIgPSBbKDAsIDEsIDEpLCAoMiwgMywgMildCiAgICB3ZWlnaHQyLCBtc3QyID0gc3BhblRyZWUoVjIsIGVkZ2VzMikKICAgIHByaW50KCJUZXN0IENhc2UgMjoiKQogICAgcHJpbnQoIkV4cGVjdGVkIFdlaWdodDogMyAoUGFydGlhbCBNU1QpIikKICAgIHByaW50KCJBY3R1YWwgV2VpZ2h0OiIsIHdlaWdodDIpCiAgICBwcmludCgiTVNUIEVkZ2VzOiIsIG1zdDIsICJcbiIpCgogICAgIyBUZXN0IENhc2UgMzogQWxsIGVkZ2VzIHNhbWUgd2VpZ2h0CiAgICBWMyA9IDQKICAgIGVkZ2VzMyA9IFsoMCwgMSwgNSksICgxLCAyLCA1KSwgKDIsIDMsIDUpLCAoMywgMCwgNSldCiAgICB3ZWlnaHQzLCBtc3QzID0gc3BhblRyZWUoVjMsIGVkZ2VzMykKICAgIHByaW50KCJUZXN0IENhc2UgMzoiKQogICAgcHJpbnQoIkV4cGVjdGVkIFdlaWdodDogMTUiKQogICAgcHJpbnQoIkFjdHVhbCBXZWlnaHQ6Iiwgd2VpZ2h0MykKICAgIHByaW50KCJNU1QgRWRnZXM6IiwgbXN0MywgIlxuIikKCiAgICAjIFRlc3QgQ2FzZSA0OiBTYW1wbGUgZXhhbXBsZSB3aXRoIGNsZWFyIE1TVAogICAgVjQgPSA0CiAgICBlZGdlczQgPSBbKDAsIDEsIDEwKSwgKDAsIDIsIDYpLCAoMCwgMywgNSksICgxLCAzLCAxNSksICgyLCAzLCA0KV0KICAgIHdlaWdodDQsIG1zdDQgPSBzcGFuVHJlZShWNCwgZWRnZXM0KQogICAgcHJpbnQoIlRlc3QgQ2FzZSA0OiIpCiAgICBwcmludCgiRXhwZWN0ZWQgV2VpZ2h0OiAxOSIpCiAgICBwcmludCgiQWN0dWFsIFdlaWdodDoiLCB3ZWlnaHQ0KQogICAgcHJpbnQoIk1TVCBFZGdlczoiLCBtc3Q0LCAiXG4iKQoKICAgICMgVGVzdCBDYXNlIDU6IEZ1bGx5IGNvbm5lY3RlZCBzbWFsbCBncmFwaAogICAgVjUgPSA1CiAgICBlZGdlczUgPSBbKGksIGosIGkgKyBqKSBmb3IgaSBpbiByYW5nZShWNSkgZm9yIGogaW4gcmFuZ2UoaSsxLCBWNSldCiAgICB3ZWlnaHQ1LCBtc3Q1ID0gc3BhblRyZWUoVjUsIGVkZ2VzNSkKICAgIHByaW50KCJUZXN0IENhc2UgNToiKQogICAgcHJpbnQoIk1TVCBXZWlnaHQ6Iiwgd2VpZ2h0NSkKICAgIHByaW50KCJNU1QgRWRnZXM6IiwgbXN0NSwgIlxuIikKCiMg4pyFIFJ1biBUZXN0cwp0ZXN0X3NwYW5UcmVlKCkK