diff --git a/graph_algorithms/bidirectional_bfs.r b/graph_algorithms/bidirectional_bfs.r new file mode 100644 index 00000000..8daf6f38 --- /dev/null +++ b/graph_algorithms/bidirectional_bfs.r @@ -0,0 +1,153 @@ +# ============================================================== +# Bidirectional Breadth-First Search (BFS) Shortest Path Algorithm +# ============================================================== +# +# Description: +# Finds the shortest path between a source and target in an +# unweighted graph using Bidirectional BFS. +# +# Time Complexity: O(b^(d/2)) — much faster than normal BFS O(b^d) +# Space Complexity: O(V) +# +# Input: +# graph - adjacency list (list of integer vectors) +# source - integer (starting vertex) +# target - integer (destination vertex) +# +# Output: +# A list containing: +# path - vector of vertices representing the path +# distance - number of edges in the shortest path +# found - logical flag (TRUE if path found, else FALSE) +# +# Example usage at bottom of file. +# ============================================================== + +bidirectional_bfs <- function(graph, source, target) { + if (source == target) { + return(list(path = c(source), distance = 0, found = TRUE)) + } + + # Initialize BFS from both ends + visited_from_source <- setNames(rep(FALSE, length(graph)), names(graph)) + visited_from_target <- setNames(rep(FALSE, length(graph)), names(graph)) + + parent_from_source <- rep(NA, length(graph)) + parent_from_target <- rep(NA, length(graph)) + + queue_source <- c(source) + queue_target <- c(target) + + visited_from_source[source] <- TRUE + visited_from_target[target] <- TRUE + + meeting_node <- NA + + # Function to check intersection + get_intersection <- function() { + common <- which(visited_from_source & visited_from_target) + if (length(common) > 0) return(common[1]) + return(NA) + } + + # Main loop + while (length(queue_source) > 0 && length(queue_target) > 0) { + # Expand one level from source side + next_queue <- c() + for (u in queue_source) { + for (v in graph[[as.character(u)]]) { + if (!visited_from_source[v]) { + visited_from_source[v] <- TRUE + parent_from_source[v] <- u + next_queue <- c(next_queue, v) + } + } + } + queue_source <- next_queue + + # Check intersection + meeting_node <- get_intersection() + if (!is.na(meeting_node)) break + + # Expand one level from target side + next_queue <- c() + for (u in queue_target) { + for (v in graph[[as.character(u)]]) { + if (!visited_from_target[v]) { + visited_from_target[v] <- TRUE + parent_from_target[v] <- u + next_queue <- c(next_queue, v) + } + } + } + queue_target <- next_queue + + # Check intersection again + meeting_node <- get_intersection() + if (!is.na(meeting_node)) break + } + + if (is.na(meeting_node)) { + return(list(path = NULL, distance = Inf, found = FALSE)) + } + + # Reconstruct path from source → meeting_node + path1 <- c() + node <- meeting_node + while (!is.na(node)) { + path1 <- c(node, path1) + node <- parent_from_source[node] + } + + # Reconstruct path from meeting_node → target + path2 <- c() + node <- parent_from_target[meeting_node] + while (!is.na(node)) { + path2 <- c(path2, node) + node <- parent_from_target[node] + } + + full_path <- c(path1, path2) + return(list(path = full_path, distance = length(full_path) - 1, found = TRUE)) +} + +# ============================================================== +# Example Usage and Test +# ============================================================== + +cat("=== Bidirectional BFS Shortest Path ===\n") + +# Example Graph (Unweighted) +# 1 -- 2 -- 3 +# | | +# 4 -- 5 -- 6 + +graph <- list( + "1" = c(2, 4), + "2" = c(1, 3, 5), + "3" = c(2, 6), + "4" = c(1, 5), + "5" = c(2, 4, 6), + "6" = c(3, 5) +) + +cat("Graph adjacency list:\n") +for (v in names(graph)) { + cat("Vertex", v, "-> [", paste(graph[[v]], collapse = ", "), "]\n") +} + +cat("\nRunning Bidirectional BFS from 1 to 6...\n") +result <- bidirectional_bfs(graph, 1, 6) + +if (result$found) { + cat("Shortest Path Found!\n") + cat("Path:", paste(result$path, collapse = " -> "), "\n") + cat("Distance:", result$distance, "\n") +} else { + cat("No path found between source and target.\n") +} + return(list( + distances = distances, + predecessor = predecessor, + found = found + )) \ No newline at end of file