A Minimum Spanning Tree (MST) of a graph is a subset of the edges that connects all vertices in the graph without any cycles and with the minimum possible total edge weight. Two classic algorithms for finding the MST are Kruskal's and Prim's algorithms.
Kruskal's algorithm builds the MST by adding edges in increasing order of their weights, ensuring that no cycles are formed.
Implementation:
Code:
gopackage main
import (
"fmt"
"sort"
)
// Edge represents an edge in the graph
type Edge struct {
u, v, weight int
}
// Union-Find data structure
type UnionFind struct {
parent, rank []int
}
// NewUnionFind creates a new Union-Find data structure
func NewUnionFind(n int) *UnionFind {
uf := &UnionFind{
parent: make([]int, n),
rank: make([]int, n),
}
for i := range uf.parent {
uf.parent[i] = i
}
return uf
}
// Find finds the root of the set containing x
func (uf *UnionFind) Find(x int) int {
if uf.parent[x] != x {
uf.parent[x] = uf.Find(uf.parent[x]) // Path compression
}
return uf.parent[x]
}
// Union joins the sets containing x and y
func (uf *UnionFind) Union(x, y int) {
rootX := uf.Find(x)
rootY := uf.Find(y)
if rootX != rootY {
if uf.rank[rootX] > uf.rank[rootY] {
uf.parent[rootY] = rootX
} else if uf.rank[rootX] < uf.rank[rootY] {
uf.parent[rootX] = rootY
} else {
uf.parent[rootY] = rootX
uf.rank[rootX]++
}
}
}
// Kruskal finds the MST using Kruskal's algorithm
func Kruskal(edges []Edge, n int) []Edge {
sort.Slice(edges, func(i, j int) bool {
return edges[i].weight < edges[j].weight
})
uf := NewUnionFind(n)
var mst []Edge
for _, edge := range edges {
if uf.Find(edge.u) != uf.Find(edge.v) {
mst = append(mst, edge)
uf.Union(edge.u, edge.v)
}
}
return mst
}
func main() {
edges := []Edge{
{0, 1, 10}, {0, 2, 6}, {0, 3, 5},
{1, 3, 15}, {2, 3, 4},
}
n := 4 // Number of vertices
mst := Kruskal(edges, n)
fmt.Println("Edges in the Minimum Spanning Tree:")
for _, edge := range mst {
fmt.Printf("%d -- %d == %d\n", edge.u, edge.v, edge.weight)
}
}
Prim's algorithm builds the MST by starting with an arbitrary vertex and growing the MST one edge at a time by adding the cheapest edge from the tree to a vertex not yet in the tree.
Implementation:
Code:
gopackage main
import (
"container/heap"
"fmt"
)
// Edge represents an edge in the graph
type Edge struct {
v, weight int
}
// Item is something we manage in a priority queue.
type Item struct {
node, weight int
index int
}
// A PriorityQueue implements heap.Interface and holds Items.
type PriorityQueue []*Item
func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].weight < pq[j].weight
}
func (pq PriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
pq[i].index = i
pq[j].index = j
}
func (pq *PriorityQueue) Push(x interface{}) {
n := len(*pq)
item := x.(*Item)
item.index = n
*pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
item.index = -1 // for safety
*pq = old[0 : n-1]
return item
}
// Prim finds the MST using Prim's algorithm
func Prim(graph map[int][]Edge, start int) []Edge {
mst := []Edge{}
visited := make(map[int]bool)
pq := &PriorityQueue{}
heap.Init(pq)
heap.Push(pq, &Item{node: start, weight: 0})
for pq.Len() > 0 {
item := heap.Pop(pq).(*Item)
u := item.node
if visited[u] {
continue
}
visited[u] = true
if item.weight != 0 { // Skip the first zero-weight edge
mst = append(mst, Edge{v: u, weight: item.weight})
}
for _, edge := range graph[u] {
if !visited[edge.v] {
heap.Push(pq, &Item{node: edge.v, weight: edge.weight})
}
}
}
return mst
}
func main() {
graph := map[int][]Edge{
0: {{1, 10}, {2, 6}, {3, 5}},
1: {{0, 10}, {3, 15}},
2: {{0, 6}, {3, 4}},
3: {{0, 5}, {1, 15}, {2, 4}},
}
start := 0
mst := Prim(graph, start)
fmt.Println("Edges in the Minimum Spanning Tree:")
for _, edge := range mst {
fmt.Printf("%d -- %d == %d\n", start, edge.v, edge.weight)
}
}
Both algorithms are fundamental in finding MSTs and have their own advantages depending on the graph's properties. Understanding these algorithms and their implementations can greatly enhance a Go developer's ability to handle graph-related problems efficiently.