1+ # Dijkstra's Shortest Path Algorithm
2+ #
3+ # Dijkstra's algorithm finds the shortest path between a source vertex and all other vertices
4+ # in a weighted graph with non-negative edge weights. It uses a greedy approach with a priority queue.
5+ #
6+ # Time Complexity: O((V + E) log V) with binary heap, O(V^2) with simple array
7+ # Space Complexity: O(V) for distance and visited arrays
8+ #
9+ # Input: A weighted graph represented as adjacency list with weights, and a source vertex
10+ # Output: Shortest distances from source to all vertices and the paths
11+
12+ # Priority queue implementation using simple vector (for educational purposes)
13+ # In production, use more efficient data structures
14+ create_priority_queue <- function () {
15+ list (
16+ elements = data.frame (vertex = integer(0 ), distance = numeric (0 )),
17+ size = 0
18+ )
19+ }
20+
21+ # Insert element into priority queue
22+ pq_insert <- function (pq , vertex , distance ) {
23+ pq $ elements <- rbind(pq $ elements , data.frame (vertex = vertex , distance = distance ))
24+ pq $ size <- pq $ size + 1
25+ return (pq )
26+ }
27+
28+ # Extract minimum element from priority queue
29+ pq_extract_min <- function (pq ) {
30+ if (pq $ size == 0 ) {
31+ return (list (pq = pq , min_element = NULL ))
32+ }
33+
34+ min_idx <- which.min(pq $ elements $ distance )
35+ min_element <- pq $ elements [min_idx , ]
36+ pq $ elements <- pq $ elements [- min_idx , ]
37+ pq $ size <- pq $ size - 1
38+
39+ return (list (pq = pq , min_element = min_element ))
40+ }
41+
42+ # Check if priority queue is empty
43+ pq_is_empty <- function (pq ) {
44+ return (pq $ size == 0 )
45+ }
46+
47+ # Main Dijkstra's algorithm implementation
48+ dijkstra_shortest_path <- function (graph , source ) {
49+ # Get all vertices in the graph
50+ all_vertices <- unique(c(names(graph ), unlist(lapply(graph , function (x ) x $ vertex ))))
51+ num_vertices <- max(all_vertices )
52+
53+ # Initialize distances and previous vertices
54+ distances <- rep(Inf , num_vertices )
55+ previous <- rep(- 1 , num_vertices )
56+ visited <- rep(FALSE , num_vertices )
57+
58+ # Set source distance to 0
59+ distances [source ] <- 0
60+
61+ # Create priority queue and add source
62+ pq <- create_priority_queue()
63+ pq <- pq_insert(pq , source , 0 )
64+
65+ while (! pq_is_empty(pq )) {
66+ # Extract vertex with minimum distance
67+ result <- pq_extract_min(pq )
68+ pq <- result $ pq
69+ current <- result $ min_element
70+
71+ if (is.null(current )) break
72+
73+ u <- current $ vertex
74+
75+ # Skip if already visited
76+ if (visited [u ]) next
77+
78+ # Mark as visited
79+ visited [u ] <- TRUE
80+
81+ # Update distances to neighbors
82+ if (as.character(u ) %in% names(graph )) {
83+ for (edge in graph [[as.character(u )]]) {
84+ v <- edge $ vertex
85+ weight <- edge $ weight
86+
87+ # Relaxation step
88+ if (! visited [v ] && distances [u ] + weight < distances [v ]) {
89+ distances [v ] <- distances [u ] + weight
90+ previous [v ] <- u
91+ pq <- pq_insert(pq , v , distances [v ])
92+ }
93+ }
94+ }
95+ }
96+
97+ return (list (
98+ distances = distances ,
99+ previous = previous
100+ ))
101+ }
102+
103+ # Reconstruct shortest path from source to target
104+ get_shortest_path <- function (dijkstra_result , source , target ) {
105+ previous <- dijkstra_result $ previous
106+ distances <- dijkstra_result $ distances
107+
108+ # Check if target is reachable
109+ if (distances [target ] == Inf ) {
110+ return (list (
111+ path = NULL ,
112+ distance = Inf
113+ ))
114+ }
115+
116+ # Reconstruct path by backtracking
117+ path <- c()
118+ current <- target
119+
120+ while (current != - 1 ) {
121+ path <- c(current , path )
122+ current <- previous [current ]
123+ }
124+
125+ return (list (
126+ path = path ,
127+ distance = distances [target ]
128+ ))
129+ }
130+
131+ # Find shortest paths to all vertices
132+ get_all_shortest_paths <- function (dijkstra_result , source ) {
133+ distances <- dijkstra_result $ distances
134+ previous <- dijkstra_result $ previous
135+ paths <- list ()
136+
137+ for (target in 1 : length(distances )) {
138+ if (distances [target ] != Inf ) {
139+ path_result <- get_shortest_path(dijkstra_result , source , target )
140+ paths [[as.character(target )]] <- path_result
141+ }
142+ }
143+
144+ return (paths )
145+ }
146+
147+ # Example usage and testing
148+ cat(" === Dijkstra's Shortest Path Algorithm ===\n " )
149+
150+ # Create a weighted graph as adjacency list
151+ # Graph structure with weights:
152+ # 1
153+ # / \
154+ # 4/ \2
155+ # / \
156+ # 2 3
157+ # |3 /1
158+ # | /
159+ # 4-----5
160+ # 2
161+ weighted_graph <- list (
162+ " 1" = list (
163+ list (vertex = 2 , weight = 4 ),
164+ list (vertex = 3 , weight = 2 )
165+ ),
166+ " 2" = list (
167+ list (vertex = 4 , weight = 3 )
168+ ),
169+ " 3" = list (
170+ list (vertex = 5 , weight = 1 )
171+ ),
172+ " 4" = list (
173+ list (vertex = 5 , weight = 2 )
174+ ),
175+ " 5" = list ()
176+ )
177+
178+ cat(" Weighted graph structure:\n " )
179+ for (vertex in names(weighted_graph )) {
180+ edges <- weighted_graph [[vertex ]]
181+ if (length(edges ) > 0 ) {
182+ edge_strs <- sapply(edges , function (e ) paste0(e $ vertex , " (" , e $ weight , " )" ))
183+ cat(" Vertex" , vertex , " -> [" , paste(edge_strs , collapse = " , " ), " ]\n " )
184+ } else {
185+ cat(" Vertex" , vertex , " -> []\n " )
186+ }
187+ }
188+
189+ # Run Dijkstra's algorithm from vertex 1
190+ cat(" \n Running Dijkstra's algorithm from vertex 1:\n " )
191+ result <- dijkstra_shortest_path(weighted_graph , 1 )
192+
193+ # Display shortest distances
194+ cat(" Shortest distances from vertex 1:\n " )
195+ for (i in 1 : length(result $ distances )) {
196+ if (result $ distances [i ] != Inf ) {
197+ cat(" To vertex" , i , " : distance =" , result $ distances [i ], " \n " )
198+ }
199+ }
200+
201+ # Get shortest path to specific vertex
202+ cat(" \n Shortest path from 1 to 5:\n " )
203+ path_to_5 <- get_shortest_path(result , 1 , 5 )
204+ if (! is.null(path_to_5 $ path )) {
205+ cat(" Path:" , paste(path_to_5 $ path , collapse = " -> " ), " \n " )
206+ cat(" Distance:" , path_to_5 $ distance , " \n " )
207+ }
208+
209+ # Get all shortest paths
210+ cat(" \n All shortest paths from vertex 1:\n " )
211+ all_paths <- get_all_shortest_paths(result , 1 )
212+ for (target in names(all_paths )) {
213+ path_info <- all_paths [[target ]]
214+ cat(" To vertex" , target , " : " , paste(path_info $ path , collapse = " -> " ),
215+ " (distance:" , path_info $ distance , " )\n " )
216+ }
217+
218+ # Example with a more complex graph
219+ cat(" \n === More Complex Weighted Graph Example ===\n " )
220+ complex_weighted_graph <- list (
221+ " 1" = list (
222+ list (vertex = 2 , weight = 7 ),
223+ list (vertex = 3 , weight = 9 ),
224+ list (vertex = 6 , weight = 14 )
225+ ),
226+ " 2" = list (
227+ list (vertex = 3 , weight = 10 ),
228+ list (vertex = 4 , weight = 15 )
229+ ),
230+ " 3" = list (
231+ list (vertex = 4 , weight = 11 ),
232+ list (vertex = 6 , weight = 2 )
233+ ),
234+ " 4" = list (
235+ list (vertex = 5 , weight = 6 )
236+ ),
237+ " 5" = list (),
238+ " 6" = list (
239+ list (vertex = 5 , weight = 9 )
240+ )
241+ )
242+
243+ cat(" Complex weighted graph from vertex 1:\n " )
244+ complex_result <- dijkstra_shortest_path(complex_weighted_graph , 1 )
245+
246+ cat(" Shortest distances:\n " )
247+ for (i in 1 : length(complex_result $ distances )) {
248+ if (complex_result $ distances [i ] != Inf ) {
249+ cat(" To vertex" , i , " : distance =" , complex_result $ distances [i ], " \n " )
250+ }
251+ }
252+
253+ # Shortest path to vertex 5
254+ path_to_5_complex <- get_shortest_path(complex_result , 1 , 5 )
255+ if (! is.null(path_to_5_complex $ path )) {
256+ cat(" Shortest path from 1 to 5:" , paste(path_to_5_complex $ path , collapse = " -> " ), " \n " )
257+ cat(" Distance:" , path_to_5_complex $ distance , " \n " )
258+ }
0 commit comments