Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ca445f5

Browse files
prajwalc22pre-commit-ci[bot]MaximSmolskiy
authored
Add bidirectional search algorithm implementation (#12649)
* Add bidirectional search algorithm implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix style and linting issues in bidirectional search * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add doctest for main function * Add doctest for main function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixed deprications * fixed deprications * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * removed unused import * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bidirectional_search.py * Update bidirectional_search.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update bidirectional_search.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
1 parent 26ad689 commit ca445f5

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

‎graphs/bidirectional_search.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
"""
2+
Bidirectional Search Algorithm.
3+
4+
This algorithm searches from both the source and target nodes simultaneously,
5+
meeting somewhere in the middle. This approach can significantly reduce the
6+
search space compared to a traditional one-directional search.
7+
8+
Time Complexity: O(b^(d/2)) where b is the branching factor and d is the depth
9+
Space Complexity: O(b^(d/2))
10+
11+
https://en.wikipedia.org/wiki/Bidirectional_search
12+
"""
13+
14+
from collections import deque
15+
16+
17+
def expand_search(
18+
graph: dict[int, list[int]],
19+
queue: deque[int],
20+
parents: dict[int, int | None],
21+
opposite_direction_parents: dict[int, int | None],
22+
) -> int | None:
23+
if not queue:
24+
return None
25+
26+
current = queue.popleft()
27+
for neighbor in graph[current]:
28+
if neighbor in parents:
29+
continue
30+
31+
parents[neighbor] = current
32+
queue.append(neighbor)
33+
34+
# Check if this creates an intersection
35+
if neighbor in opposite_direction_parents:
36+
return neighbor
37+
38+
return None
39+
40+
41+
def construct_path(current: int | None, parents: dict[int, int | None]) -> list[int]:
42+
path: list[int] = []
43+
while current is not None:
44+
path.append(current)
45+
current = parents[current]
46+
return path
47+
48+
49+
def bidirectional_search(
50+
graph: dict[int, list[int]], start: int, goal: int
51+
) -> list[int] | None:
52+
"""
53+
Perform bidirectional search on a graph to find the shortest path.
54+
55+
Args:
56+
graph: A dictionary where keys are nodes and values are lists of adjacent nodes
57+
start: The starting node
58+
goal: The target node
59+
60+
Returns:
61+
A list representing the path from start to goal, or None if no path exists
62+
63+
Examples:
64+
>>> graph = {
65+
... 0: [1, 2],
66+
... 1: [0, 3, 4],
67+
... 2: [0, 5, 6],
68+
... 3: [1, 7],
69+
... 4: [1, 8],
70+
... 5: [2, 9],
71+
... 6: [2, 10],
72+
... 7: [3, 11],
73+
... 8: [4, 11],
74+
... 9: [5, 11],
75+
... 10: [6, 11],
76+
... 11: [7, 8, 9, 10],
77+
... }
78+
>>> bidirectional_search(graph=graph, start=0, goal=11)
79+
[0, 1, 3, 7, 11]
80+
>>> bidirectional_search(graph=graph, start=5, goal=5)
81+
[5]
82+
>>> disconnected_graph = {
83+
... 0: [1, 2],
84+
... 1: [0],
85+
... 2: [0],
86+
... 3: [4],
87+
... 4: [3],
88+
... }
89+
>>> bidirectional_search(graph=disconnected_graph, start=0, goal=3) is None
90+
True
91+
"""
92+
if start == goal:
93+
return [start]
94+
95+
# Check if start and goal are in the graph
96+
if start not in graph or goal not in graph:
97+
return None
98+
99+
# Initialize forward and backward search dictionaries
100+
# Each maps a node to its parent in the search
101+
forward_parents: dict[int, int | None] = {start: None}
102+
backward_parents: dict[int, int | None] = {goal: None}
103+
104+
# Initialize forward and backward search queues
105+
forward_queue = deque([start])
106+
backward_queue = deque([goal])
107+
108+
# Intersection node (where the two searches meet)
109+
intersection = None
110+
111+
# Continue until both queues are empty or an intersection is found
112+
while forward_queue and backward_queue and intersection is None:
113+
# Expand forward search
114+
intersection = expand_search(
115+
graph=graph,
116+
queue=forward_queue,
117+
parents=forward_parents,
118+
opposite_direction_parents=backward_parents,
119+
)
120+
121+
# If no intersection found, expand backward search
122+
if intersection is not None:
123+
break
124+
125+
intersection = expand_search(
126+
graph=graph,
127+
queue=backward_queue,
128+
parents=backward_parents,
129+
opposite_direction_parents=forward_parents,
130+
)
131+
132+
# If no intersection found, there's no path
133+
if intersection is None:
134+
return None
135+
136+
# Construct path from start to intersection
137+
forward_path: list[int] = construct_path(
138+
current=intersection, parents=forward_parents
139+
)
140+
forward_path.reverse()
141+
142+
# Construct path from intersection to goal
143+
backward_path: list[int] = construct_path(
144+
current=backward_parents[intersection], parents=backward_parents
145+
)
146+
147+
# Return the complete path
148+
return forward_path + backward_path
149+
150+
151+
def main() -> None:
152+
"""
153+
Run example of bidirectional search algorithm.
154+
155+
Examples:
156+
>>> main() # doctest: +NORMALIZE_WHITESPACE
157+
Path from 0 to 11: [0, 1, 3, 7, 11]
158+
Path from 5 to 5: [5]
159+
Path from 0 to 3: None
160+
"""
161+
# Example graph represented as an adjacency list
162+
example_graph = {
163+
0: [1, 2],
164+
1: [0, 3, 4],
165+
2: [0, 5, 6],
166+
3: [1, 7],
167+
4: [1, 8],
168+
5: [2, 9],
169+
6: [2, 10],
170+
7: [3, 11],
171+
8: [4, 11],
172+
9: [5, 11],
173+
10: [6, 11],
174+
11: [7, 8, 9, 10],
175+
}
176+
177+
# Test case 1: Path exists
178+
start, goal = 0, 11
179+
path = bidirectional_search(graph=example_graph, start=start, goal=goal)
180+
print(f"Path from {start} to {goal}: {path}")
181+
182+
# Test case 2: Start and goal are the same
183+
start, goal = 5, 5
184+
path = bidirectional_search(graph=example_graph, start=start, goal=goal)
185+
print(f"Path from {start} to {goal}: {path}")
186+
187+
# Test case 3: No path exists (disconnected graph)
188+
disconnected_graph = {
189+
0: [1, 2],
190+
1: [0],
191+
2: [0],
192+
3: [4],
193+
4: [3],
194+
}
195+
start, goal = 0, 3
196+
path = bidirectional_search(graph=disconnected_graph, start=start, goal=goal)
197+
print(f"Path from {start} to {goal}: {path}")
198+
199+
200+
if __name__ == "__main__":
201+
main()

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /