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(" \n Adjacency 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