Bidirectional Bfs
S
# ==============================================================
# 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
))