-
-
Notifications
You must be signed in to change notification settings - Fork 46.8k
Add bidirectional search algorithm implementation #12649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MaximSmolskiy
merged 21 commits into
TheAlgorithms:master
from
prajwalc22:add-bidirectional-search
May 22, 2025
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
7804180
Add bidirectional search algorithm implementation
prajwalc22 3364529
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] f71b167
Fix style and linting issues in bidirectional search
prajwalc22 c71e0cc
Fix style and linting issues in bidirectional search
prajwalc22 b39eaca
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 50ce48a
Add doctest for main function
prajwalc22 10545a3
Merge branch 'add-bidirectional-search' of github.com:prajwalc22/Pyth…
prajwalc22 334fa0f
Add doctest for main function
prajwalc22 7ab5f23
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 9aee616
fixed deprications
prajwalc22 42d8c70
Merge branch 'add-bidirectional-search' of github.com:prajwalc22/Pyth…
prajwalc22 16de735
fixed deprications
prajwalc22 d4f2918
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 7a0dc16
removed unused import
prajwalc22 a41d28f
Merge branch 'add-bidirectional-search' of github.com:prajwalc22/Pyth…
prajwalc22 8b309e6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 4b350c3
Update bidirectional_search.py
MaximSmolskiy b21086d
Update bidirectional_search.py
MaximSmolskiy 2791920
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 28a122b
Update bidirectional_search.py
MaximSmolskiy ac84888
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
""" | ||
Bidirectional Search Algorithm. | ||
|
||
This algorithm searches from both the source and target nodes simultaneously, | ||
meeting somewhere in the middle. This approach can significantly reduce the | ||
search space compared to a traditional one-directional search. | ||
|
||
Time Complexity: O(b^(d/2)) where b is the branching factor and d is the depth | ||
Space Complexity: O(b^(d/2)) | ||
|
||
https://en.wikipedia.org/wiki/Bidirectional_search | ||
""" | ||
|
||
from collections import deque | ||
|
||
|
||
def expand_search( | ||
graph: dict[int, list[int]], | ||
queue: deque[int], | ||
parents: dict[int, int | None], | ||
opposite_direction_parents: dict[int, int | None], | ||
) -> int | None: | ||
if not queue: | ||
return None | ||
|
||
current = queue.popleft() | ||
for neighbor in graph[current]: | ||
if neighbor in parents: | ||
continue | ||
|
||
parents[neighbor] = current | ||
queue.append(neighbor) | ||
|
||
# Check if this creates an intersection | ||
if neighbor in opposite_direction_parents: | ||
return neighbor | ||
|
||
return None | ||
|
||
|
||
def construct_path(current: int | None, parents: dict[int, int | None]) -> list[int]: | ||
MaximSmolskiy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
path: list[int] = [] | ||
while current is not None: | ||
path.append(current) | ||
current = parents[current] | ||
return path | ||
|
||
|
||
def bidirectional_search( | ||
graph: dict[int, list[int]], start: int, goal: int | ||
) -> list[int] | None: | ||
""" | ||
Perform bidirectional search on a graph to find the shortest path. | ||
|
||
Args: | ||
graph: A dictionary where keys are nodes and values are lists of adjacent nodes | ||
start: The starting node | ||
goal: The target node | ||
|
||
Returns: | ||
A list representing the path from start to goal, or None if no path exists | ||
|
||
Examples: | ||
>>> graph = { | ||
... 0: [1, 2], | ||
... 1: [0, 3, 4], | ||
... 2: [0, 5, 6], | ||
... 3: [1, 7], | ||
... 4: [1, 8], | ||
... 5: [2, 9], | ||
... 6: [2, 10], | ||
... 7: [3, 11], | ||
... 8: [4, 11], | ||
... 9: [5, 11], | ||
... 10: [6, 11], | ||
... 11: [7, 8, 9, 10], | ||
... } | ||
>>> bidirectional_search(graph=graph, start=0, goal=11) | ||
[0, 1, 3, 7, 11] | ||
>>> bidirectional_search(graph=graph, start=5, goal=5) | ||
[5] | ||
>>> disconnected_graph = { | ||
... 0: [1, 2], | ||
... 1: [0], | ||
... 2: [0], | ||
... 3: [4], | ||
... 4: [3], | ||
... } | ||
>>> bidirectional_search(graph=disconnected_graph, start=0, goal=3) is None | ||
True | ||
""" | ||
if start == goal: | ||
return [start] | ||
|
||
# Check if start and goal are in the graph | ||
if start not in graph or goal not in graph: | ||
return None | ||
|
||
# Initialize forward and backward search dictionaries | ||
# Each maps a node to its parent in the search | ||
forward_parents: dict[int, int | None] = {start: None} | ||
backward_parents: dict[int, int | None] = {goal: None} | ||
|
||
# Initialize forward and backward search queues | ||
forward_queue = deque([start]) | ||
backward_queue = deque([goal]) | ||
|
||
# Intersection node (where the two searches meet) | ||
intersection = None | ||
|
||
# Continue until both queues are empty or an intersection is found | ||
while forward_queue and backward_queue and intersection is None: | ||
# Expand forward search | ||
intersection = expand_search( | ||
graph=graph, | ||
queue=forward_queue, | ||
parents=forward_parents, | ||
opposite_direction_parents=backward_parents, | ||
) | ||
|
||
# If no intersection found, expand backward search | ||
if intersection is not None: | ||
break | ||
|
||
intersection = expand_search( | ||
graph=graph, | ||
queue=backward_queue, | ||
parents=backward_parents, | ||
opposite_direction_parents=forward_parents, | ||
) | ||
|
||
# If no intersection found, there's no path | ||
if intersection is None: | ||
return None | ||
|
||
# Construct path from start to intersection | ||
forward_path: list[int] = construct_path( | ||
current=intersection, parents=forward_parents | ||
) | ||
forward_path.reverse() | ||
|
||
# Construct path from intersection to goal | ||
backward_path: list[int] = construct_path( | ||
current=backward_parents[intersection], parents=backward_parents | ||
) | ||
|
||
# Return the complete path | ||
return forward_path + backward_path | ||
|
||
|
||
def main() -> None: | ||
prajwalc22 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Run example of bidirectional search algorithm. | ||
|
||
Examples: | ||
>>> main() # doctest: +NORMALIZE_WHITESPACE | ||
Path from 0 to 11: [0, 1, 3, 7, 11] | ||
Path from 5 to 5: [5] | ||
Path from 0 to 3: None | ||
""" | ||
# Example graph represented as an adjacency list | ||
example_graph = { | ||
0: [1, 2], | ||
1: [0, 3, 4], | ||
2: [0, 5, 6], | ||
3: [1, 7], | ||
4: [1, 8], | ||
5: [2, 9], | ||
6: [2, 10], | ||
7: [3, 11], | ||
8: [4, 11], | ||
9: [5, 11], | ||
10: [6, 11], | ||
11: [7, 8, 9, 10], | ||
} | ||
|
||
# Test case 1: Path exists | ||
start, goal = 0, 11 | ||
path = bidirectional_search(graph=example_graph, start=start, goal=goal) | ||
print(f"Path from {start} to {goal}: {path}") | ||
|
||
# Test case 2: Start and goal are the same | ||
start, goal = 5, 5 | ||
path = bidirectional_search(graph=example_graph, start=start, goal=goal) | ||
print(f"Path from {start} to {goal}: {path}") | ||
|
||
# Test case 3: No path exists (disconnected graph) | ||
disconnected_graph = { | ||
0: [1, 2], | ||
1: [0], | ||
2: [0], | ||
3: [4], | ||
4: [3], | ||
} | ||
start, goal = 0, 3 | ||
path = bidirectional_search(graph=disconnected_graph, start=start, goal=goal) | ||
print(f"Path from {start} to {goal}: {path}") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.