1+ # ==============================================================
2+ # Bidirectional Breadth-First Search (BFS) Shortest Path Algorithm
3+ # ==============================================================
4+ #
5+ # Description:
6+ # Finds the shortest path between a source and target in an
7+ # unweighted graph using Bidirectional BFS.
8+ #
9+ # Time Complexity: O(b^(d/2)) — much faster than normal BFS O(b^d)
10+ # Space Complexity: O(V)
11+ #
12+ # Input:
13+ # graph - adjacency list (list of integer vectors)
14+ # source - integer (starting vertex)
15+ # target - integer (destination vertex)
16+ #
17+ # Output:
18+ # A list containing:
19+ # path - vector of vertices representing the path
20+ # distance - number of edges in the shortest path
21+ # found - logical flag (TRUE if path found, else FALSE)
22+ #
23+ # Example usage at bottom of file.
24+ # ==============================================================
25+
26+ bidirectional_bfs <- function (graph , source , target ) {
27+ if (source == target ) {
28+ return (list (path = c(source ), distance = 0 , found = TRUE ))
29+ }
30+
31+ # Initialize BFS from both ends
32+ visited_from_source <- setNames(rep(FALSE , length(graph )), names(graph ))
33+ visited_from_target <- setNames(rep(FALSE , length(graph )), names(graph ))
34+
35+ parent_from_source <- rep(NA , length(graph ))
36+ parent_from_target <- rep(NA , length(graph ))
37+
38+ queue_source <- c(source )
39+ queue_target <- c(target )
40+
41+ visited_from_source [source ] <- TRUE
42+ visited_from_target [target ] <- TRUE
43+
44+ meeting_node <- NA
45+
46+ # Function to check intersection
47+ get_intersection <- function () {
48+ common <- which(visited_from_source & visited_from_target )
49+ if (length(common ) > 0 ) return (common [1 ])
50+ return (NA )
51+ }
52+
53+ # Main loop
54+ while (length(queue_source ) > 0 && length(queue_target ) > 0 ) {
55+ # Expand one level from source side
56+ next_queue <- c()
57+ for (u in queue_source ) {
58+ for (v in graph [[as.character(u )]]) {
59+ if (! visited_from_source [v ]) {
60+ visited_from_source [v ] <- TRUE
61+ parent_from_source [v ] <- u
62+ next_queue <- c(next_queue , v )
63+ }
64+ }
65+ }
66+ queue_source <- next_queue
67+
68+ # Check intersection
69+ meeting_node <- get_intersection()
70+ if (! is.na(meeting_node )) break
71+
72+ # Expand one level from target side
73+ next_queue <- c()
74+ for (u in queue_target ) {
75+ for (v in graph [[as.character(u )]]) {
76+ if (! visited_from_target [v ]) {
77+ visited_from_target [v ] <- TRUE
78+ parent_from_target [v ] <- u
79+ next_queue <- c(next_queue , v )
80+ }
81+ }
82+ }
83+ queue_target <- next_queue
84+
85+ # Check intersection again
86+ meeting_node <- get_intersection()
87+ if (! is.na(meeting_node )) break
88+ }
89+
90+ if (is.na(meeting_node )) {
91+ return (list (path = NULL , distance = Inf , found = FALSE ))
92+ }
93+
94+ # Reconstruct path from source → meeting_node
95+ path1 <- c()
96+ node <- meeting_node
97+ while (! is.na(node )) {
98+ path1 <- c(node , path1 )
99+ node <- parent_from_source [node ]
100+ }
101+
102+ # Reconstruct path from meeting_node → target
103+ path2 <- c()
104+ node <- parent_from_target [meeting_node ]
105+ while (! is.na(node )) {
106+ path2 <- c(path2 , node )
107+ node <- parent_from_target [node ]
108+ }
109+
110+ full_path <- c(path1 , path2 )
111+ return (list (path = full_path , distance = length(full_path ) - 1 , found = TRUE ))
112+ }
113+
114+ # ==============================================================
115+ # Example Usage and Test
116+ # ==============================================================
117+
118+ cat(" === Bidirectional BFS Shortest Path ===\n " )
119+
120+ # Example Graph (Unweighted)
121+ # 1 -- 2 -- 3
122+ # | |
123+ # 4 -- 5 -- 6
124+
125+ graph <- list (
126+ " 1" = c(2 , 4 ),
127+ " 2" = c(1 , 3 , 5 ),
128+ " 3" = c(2 , 6 ),
129+ " 4" = c(1 , 5 ),
130+ " 5" = c(2 , 4 , 6 ),
131+ " 6" = c(3 , 5 )
132+ )
133+
134+ cat(" Graph adjacency list:\n " )
135+ for (v in names(graph )) {
136+ cat(" Vertex" , v , " -> [" , paste(graph [[v ]], collapse = " , " ), " ]\n " )
137+ }
138+
139+ cat(" \n Running Bidirectional BFS from 1 to 6...\n " )
140+ result <- bidirectional_bfs(graph , 1 , 6 )
141+
142+ if (result $ found ) {
143+ cat(" Shortest Path Found!\n " )
144+ cat(" Path:" , paste(result $ path , collapse = " -> " ), " \n " )
145+ cat(" Distance:" , result $ distance , " \n " )
146+ } else {
147+ cat(" No path found between source and target.\n " )
148+ }
149+ return (list (
150+ distances = distances ,
151+ predecessor = predecessor ,
152+ found = found
153+ ))
0 commit comments