Skip to content

Commit ac2f0c8

Browse files
authored
Add Prims's Algorithm for MST (#248)
1 parent 84d5d78 commit ac2f0c8

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

graph_algorithms/prim_mst.r

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# Prim's Algorithm for Minimum Spanning Tree (MST)
2+
#
3+
# Prim's algorithm finds the MST of a connected, weighted, undirected graph.
4+
# It starts from an arbitrary node and repeatedly adds the smallest edge
5+
# connecting the growing MST to a new vertex.
6+
#
7+
# Time Complexity: O(V^2) using adjacency matrix, O((V + E) log V) with priority queue
8+
# Space Complexity: O(V) for key, parent, and visited arrays
9+
#
10+
# Applications:
11+
# - Network design (telecommunications, computer networks)
12+
# - Circuit design and electrical grids
13+
# - Clustering algorithms in machine learning
14+
# - Transportation and logistics planning
15+
# - Approximation algorithms for NP-hard problems
16+
17+
#' Compute the Minimum Spanning Tree using Prim's algorithm
18+
#' @param graph Adjacency matrix of the graph (use 0 or Inf for no edge)
19+
#' @param start_vertex Starting vertex (default is 1)
20+
#' @return List with MST edges, total weight, and parent array
21+
prim_mst <- function(graph, start_vertex = 1) {
22+
# Validate input
23+
if (!is.matrix(graph)) {
24+
stop("Error: Input must be a matrix")
25+
}
26+
27+
if (nrow(graph) != ncol(graph)) {
28+
stop("Error: Graph must be a square adjacency matrix")
29+
}
30+
31+
n <- nrow(graph)
32+
33+
if (start_vertex < 1 || start_vertex > n) {
34+
stop(sprintf("Error: start_vertex must be between 1 and %d", n))
35+
}
36+
37+
# Initialize arrays
38+
key <- rep(Inf, n) # Minimum weight to include vertex
39+
parent <- rep(NA, n) # Store MST edges
40+
in_mst <- rep(FALSE, n) # Vertices included in MST
41+
42+
# Start from specified vertex
43+
key[start_vertex] <- 0
44+
parent[start_vertex] <- -1 # Root of MST
45+
46+
# Build MST with n vertices
47+
for (count in 1:n) {
48+
# Pick vertex u not in MST with minimum key value
49+
min_key <- Inf
50+
u <- NA
51+
52+
for (v in 1:n) {
53+
if (!in_mst[v] && key[v] < min_key) {
54+
min_key <- key[v]
55+
u <- v
56+
}
57+
}
58+
59+
# Check if graph is disconnected
60+
if (is.na(u)) {
61+
warning("Graph is disconnected. MST is incomplete.")
62+
break
63+
}
64+
65+
# Include u in MST
66+
in_mst[u] <- TRUE
67+
68+
# Update key and parent for adjacent vertices of u
69+
for (v in 1:n) {
70+
# Check if there's an edge, v is not in MST, and edge weight is smaller
71+
if (graph[u, v] != 0 && graph[u, v] != Inf &&
72+
!in_mst[v] && graph[u, v] < key[v]) {
73+
key[v] <- graph[u, v]
74+
parent[v] <- u
75+
}
76+
}
77+
}
78+
79+
# Construct MST edges and calculate total weight
80+
mst_edges <- list()
81+
total_weight <- 0
82+
edge_count <- 0
83+
84+
for (v in 1:n) {
85+
if (!is.na(parent[v]) && parent[v] != -1) {
86+
edge_count <- edge_count + 1
87+
mst_edges[[edge_count]] <- list(
88+
from = parent[v],
89+
to = v,
90+
weight = graph[parent[v], v]
91+
)
92+
total_weight <- total_weight + graph[parent[v], v]
93+
}
94+
}
95+
96+
return(list(
97+
edges = mst_edges,
98+
total_weight = total_weight,
99+
parent = parent,
100+
num_edges = edge_count
101+
))
102+
}
103+
104+
#' Print MST in a formatted way
105+
#' @param mst Result from prim_mst function
106+
#' @param graph Original graph (for verification)
107+
print_mst <- function(mst, graph = NULL) {
108+
cat("Minimum Spanning Tree:\n")
109+
cat(strrep("=", 50), "\n\n")
110+
111+
if (length(mst$edges) == 0) {
112+
cat("No edges in MST (graph may be disconnected)\n")
113+
return()
114+
}
115+
116+
cat(sprintf("%-10s %-10s %-10s\n", "Edge", "Vertices", "Weight"))
117+
cat(strrep("-", 50), "\n")
118+
119+
for (i in seq_along(mst$edges)) {
120+
edge <- mst$edges[[i]]
121+
cat(sprintf("%-10d %-10s %-10g\n",
122+
i,
123+
paste0(edge$from, " -- ", edge$to),
124+
edge$weight))
125+
}
126+
127+
cat(strrep("-", 50), "\n")
128+
cat(sprintf("Total Weight: %g\n", mst$total_weight))
129+
cat(sprintf("Number of Edges: %d\n", mst$num_edges))
130+
131+
# Verify MST properties
132+
if (!is.null(graph)) {
133+
n <- nrow(graph)
134+
expected_edges <- n - 1
135+
if (mst$num_edges == expected_edges) {
136+
cat(sprintf("[OK] MST has correct number of edges (%d)\n", expected_edges))
137+
} else {
138+
cat(sprintf("[WARN] MST has %d edges, expected %d (graph may be disconnected)\n",
139+
mst$num_edges, expected_edges))
140+
}
141+
}
142+
cat("\n")
143+
}
144+
145+
#' Create adjacency matrix from edge list
146+
#' @param edges List of edges, each with from, to, and weight
147+
#' @param n_vertices Number of vertices
148+
#' @return Adjacency matrix
149+
create_graph_from_edges <- function(edges, n_vertices) {
150+
graph <- matrix(0, nrow = n_vertices, ncol = n_vertices)
151+
152+
for (edge in edges) {
153+
graph[edge$from, edge$to] <- edge$weight
154+
graph[edge$to, edge$from] <- edge$weight # Undirected graph
155+
}
156+
157+
return(graph)
158+
}
159+
160+
# ========== Example 1: Basic 5-vertex graph ==========
161+
cat("========== Example 1: Basic 5-Vertex Graph ==========\n\n")
162+
graph1 <- matrix(c(
163+
0, 2, 0, 6, 0,
164+
2, 0, 3, 8, 5,
165+
0, 3, 0, 0, 7,
166+
6, 8, 0, 0, 9,
167+
0, 5, 7, 9, 0
168+
), nrow = 5, byrow = TRUE)
169+
cat("Graph adjacency matrix:\n")
170+
print(graph1)
171+
cat("\n")
172+
mst1 <- prim_mst(graph1)
173+
print_mst(mst1, graph1)
174+
175+
# ========== Example 2: 6-vertex graph ==========
176+
cat("========== Example 2: 6-Vertex Graph ==========\n\n")
177+
graph2 <- matrix(c(
178+
0, 4, 0, 0, 0, 0,
179+
4, 0, 8, 0, 0, 0,
180+
0, 8, 0, 7, 0, 4,
181+
0, 0, 7, 0, 9, 14,
182+
0, 0, 0, 9, 0, 10,
183+
0, 0, 4, 14, 10, 0
184+
), nrow = 6, byrow = TRUE)
185+
cat("Graph adjacency matrix:\n")
186+
print(graph2)
187+
cat("\n")
188+
mst2 <- prim_mst(graph2, start_vertex = 1)
189+
print_mst(mst2, graph2)
190+
191+
# ========== Example 3: Using edge list representation ==========
192+
cat("========== Example 3: Graph from Edge List ==========\n\n")
193+
edges <- list(
194+
list(from = 1, to = 2, weight = 1),
195+
list(from = 1, to = 3, weight = 4),
196+
list(from = 2, to = 3, weight = 2),
197+
list(from = 2, to = 4, weight = 5),
198+
list(from = 3, to = 4, weight = 3)
199+
)
200+
graph3 <- create_graph_from_edges(edges, 4)
201+
cat("Graph from edge list:\n")
202+
for (edge in edges) {
203+
cat(sprintf("%d -- %d : %g\n", edge$from, edge$to, edge$weight))
204+
}
205+
cat("\nAdjacency matrix:\n")
206+
print(graph3)
207+
cat("\n")
208+
mst3 <- prim_mst(graph3)
209+
print_mst(mst3, graph3)
210+
211+
# ========== Example 4: Disconnected graph ==========
212+
cat("========== Example 4: Disconnected Graph ==========\n\n")
213+
graph4 <- matrix(c(
214+
0, 1, 0, 0,
215+
1, 0, 0, 0,
216+
0, 0, 0, 2,
217+
0, 0, 2, 0
218+
), nrow = 4, byrow = TRUE)
219+
cat("Disconnected graph adjacency matrix:\n")
220+
print(graph4)
221+
cat("\n")
222+
mst4 <- prim_mst(graph4)
223+
print_mst(mst4, graph4)
224+
225+
# ========== Example 5: Complete graph K4 ==========
226+
cat("========== Example 5: Complete Graph K4 ==========\n\n")
227+
graph5 <- matrix(c(
228+
0, 1, 2, 3,
229+
1, 0, 4, 5,
230+
2, 4, 0, 6,
231+
3, 5, 6, 0
232+
), nrow = 4, byrow = TRUE)
233+
cat("Complete graph K4:\n")
234+
print(graph5)
235+
cat("\n")
236+
mst5 <- prim_mst(graph5)
237+
print_mst(mst5, graph5)
238+
239+
# ========== Notes ==========
240+
cat("========== Algorithm Properties ==========\n\n")
241+
cat("1. Prim's algorithm guarantees finding the MST for connected graphs\n")
242+
cat("2. Works only on undirected graphs with non-negative weights\n")
243+
cat("3. MST has exactly (V-1) edges for a connected graph with V vertices\n")
244+
cat("4. Total weight of MST is unique, but MST itself may not be unique\n")
245+
cat("5. Starting vertex doesn't affect the total weight of the MST\n\n")
246+
247+
cat("========== Comparison with Kruskal's Algorithm ==========\n\n")
248+
cat("Prim's Algorithm:\n")
249+
cat(" - Starts from a vertex and grows the tree\n")
250+
cat(" - Better for dense graphs (many edges)\n")
251+
cat(" - O(V^2) with simple implementation\n\n")
252+
cat("Kruskal's Algorithm:\n")
253+
cat(" - Starts with edges sorted by weight\n")
254+
cat(" - Better for sparse graphs (few edges)\n")
255+
cat(" - O(E log E) or O(E log V)\n")

0 commit comments

Comments
 (0)