Bidirectional search
Bidirectional search is a graph search algorithm that finds a shortest path from an initial vertex to a goal vertex in a directed graph. It runs two simultaneous searches: one forward from the initial state, and one backward from the goal, stopping when the two meet. The reason for this approach is that in many cases it is faster: for instance, in a simplified model of search problem complexity in which both searches expand a tree with branching factor b, and the distance from start to goal is d, each of the two searches has complexity O(bd/2) (in Big O notation), and the sum of these two search times is much less than the O(bd) complexity that would result from a single search from the beginning to the goal.
Andrew Goldberg and others explained the correct termination conditions for the bidirectional version of Dijkstra’s Algorithm.[1]
As with A* search, bidirectional search can be guided by a heuristic estimate of the remaining distance—toward the goal () in the forward tree and from the start () in the backward tree — a detailed explanation and example of Bidirectional A* search provided in the example section below.
Ira Pohl was the first one to design and implement a bi-directional heuristic search algorithm.[2] Search trees emanating from the start and goal nodes failed to meet in the middle of the solution space. The BHFFA algorithm of de Champeaux fixed this defect.[3]
A solution found by the uni-directional A* algorithm using an admissible heuristic has a shortest path length; the same property holds for the BHFFA2 bidirectional heuristic version described by de Champeaux .[4] BHFFA2 has, among others, more careful termination conditions than BHFFA.
Description
[edit]A Bidirectional Heuristic Search is a state space search from some state to another state , searching from to and from to simultaneously. It returns a valid list of operators that if applied to will give us .
While it may seem as though the operators have to be invertible for the reverse search, it is only necessary to be able to find, given any node , the set of parent nodes of such that there exists some valid operator from each of the parent nodes to . This has often been likened to a one-way street in the route-finding domain: it is not necessary to be able to travel down both directions, but it is necessary when standing at the end of the street to determine the beginning of the street as a possible route.
Similarly, for those edges that have inverse arcs (i.e. arcs going in both directions) it is not necessary that each direction be of equal cost. The reverse search will always use the inverse cost (i.e. the cost of the arc in the forward direction). More formally, if is a node with parent , then , defined as being the cost from to .[5]
Terminology and notation
[edit]The following terms and notations are commonly used in the context of search algorithms, particularly bidirectional heuristic search:
- : The branching factor of a search tree, representing the average number of child nodes per node.
- : The set of non-leaf nodes in , containing nodes already visited or explored by the search in direction .
- : The current search direction. By convention, denotes the forward direction (from start to goal), and denotes the backward direction (from goal to start).[6]
- : The opposite search direction, defined as . For example, if , then , and vice versa.
- : The cost of the path from the root node to node in the search tree.
- : The heuristic estimate of the cost from node to the goal node, used to guide the search.
- : The cost associated with moving from node to node in the search tree.
- : The set of leaf nodes of , sometimes called . This set represents the nodes available for expansion in direction . In bidirectional search, these are often referred to as the search "frontiers" or "wavefronts" when visualized graphically. A "collision" occurs when a node from one wavefront has successors in the opposing wavefront during expansion.
- : The start state or initial node of the search.
- : The goal state or target node of the search (sometimes denoted , though not to be confused with the cost function ).
- : The search tree in direction . If , the root is (forward search); if , the root is (backward search).
Approaches for bidirectional heuristic search
[edit]Bidirectional algorithms can be broadly split into three categories: Front-to-Front, Front-to-Back (or Front-to-End), and Perimeter Search.[7] These differ by the function used to calculate the heuristic.
Front-to-back
[edit]Front-to-Back algorithms calculate the value of a node by using the heuristic estimate between and the root of the opposite search tree, or .
Front-to-Back is the most actively researched of the three categories. As of 2004, the current best algorithm (at least in the Fifteen puzzle domain) is the BiMAX-BS*F algorithm.[5]
Front-to-front
[edit]Front-to-Front algorithms calculate the h value of a node n by using the heuristic estimate between n and some subset of . The canonical example is that of the BHFFA (bidirectional heuristic front-to-front algorithm),[3][4] where the h function is defined as the minimum of all heuristic estimates between the current node and the nodes on the opposing front. Or, formally:
where returns an admissible (i.e. not overestimating) heuristic estimate of the distance between nodes n and o.
Front-to-Front suffers from being excessively computationally demanding. Every time a node n is put into the open list, its value must be calculated. This involves calculating a heuristic estimate from n to every node in the opposing OPEN set, as described above. The OPEN sets increase in size exponentially for all domains with b > 1.
Example
[edit]Adding A* to bidirectional search
[edit]When combined with A* (Bidirectional A*), the algorithm enhances efficiency by guiding both searches with a heuristic function. In unidirectional A*, the cost function is , where is the cost from the start to node , and is the heuristic estimate to the goal. In Bidirectional A*, the forward search uses to estimate the cost from to , while the backward search estimates the cost from to . This heuristic guidance prioritizes nodes likely to lie on the shortest path, reducing the number of nodes explored compared to basic bidirectional search (e.g., using BFS or Dijkstra’s algorithm). For example, while basic bidirectional search expands nodes based solely on path cost, Bidirectional A* uses to focus on promising paths, often achieving practical speedups in domains like route planning or puzzle solving.
Class | Search algorithm, Graph traversal |
---|---|
Data structure | Graph, Priority queue |
Worst-case performance | [8] |
Worst-case space complexity |
Historical context
[edit]The concept of bidirectional search predates A*, with early mentions in the 1960s, but its integration with A* heuristics was formalized later. Ira Pohl pioneered bidirectional heuristic search in the 1970s, though his initial implementation struggled with search trees failing to meet effectively.[2] and its successor, BHFFA2, introduced refined termination conditions.[4] Andrew Goldberg and others later clarified termination for bidirectional Dijkstra’s,[9] while Bidirectional A* emerged as a prominent variant in the 1980s and 1990s, notably refined for applications like robotics and road networks (e.g., Nannicini et al., 2012).[10]
Properties
[edit]Like unidirectional A*, Bidirectional A* guarantees an optimal path if the heuristic is admissible and consistent. However, it requires careful termination logic: the algorithm stops when the frontiers meet at a node such that the total path cost () is less than or equal to the minimum -values in both open sets, ensuring no better path exists. This makes it particularly effective in large graphs, balancing efficiency and optimality.
The algorithm terminates when a node is reached by both searches, indicating the frontiers have met. The path is then reconstructed by tracing back through the parent pointers from the meeting point.
Key Features
[edit]- Admissibility: If the heuristic is admissible (never overestimates the true cost), Bidirectional A* guarantees an optimal path.
- Consistency: If the heuristic is consistent (satisfies ), the algorithm avoids re-expanding nodes unnecessarily.
- Efficiency: By searching from both ends, it often explores fewer nodes than unidirectional A*, especially in graphs with high branching factors.
Pseudocode
[edit]The following pseudocode describes the bidirectional A* algorithm, using two priority queues and heuristic-guided searches that terminate upon intersection:
function buildPath(cameFromForward, cameFromBackward, meetingNode)
// Build path from start to meeting node
pathFromStart := {meetingNode};
currentNode := meetingNode;
while currentNode in cameFromForward do
currentNode := cameFromForward[currentNode];
pathFromStart.prepend(currentNode);
end;
// Build path from meeting node to goal
pathToGoal := {};
currentNode := meetingNode;
while currentNode in cameFromBackward do
currentNode := cameFromBackward[currentNode];
pathToGoal.append(currentNode);
end;
return pathFromStart + pathToGoal;
end;
function bidirectionalAStar(graph, startNode, goalNode, heuristic)
// Priority queues for nodes to explore
openSetForward := {startNode};
openSetBackward := {goalNode};
// Track parent nodes for path reconstruction
cameFromForward := empty map;
cameFromBackward := empty map;
// Actual costs from start and goal
costFromStart := map with infinity default, startNode at 0;
costFromGoal := map with infinity default, goalNode at 0;
// Estimated total costs (cost so far + heuristic)
totalCostForward := map with infinity default, startNode at heuristic(startNode, goalNode);
totalCostBackward := map with infinity default, goalNode at heuristic(goalNode, startNode);
// Track best intersection point
bestIntersection := null;
minPathCost := infinity;
while (openSetForward is not empty) and (openSetBackward is not empty) do
// Process forward search
currentNode := node in openSetForward with lowest totalCostForward;
if currentNode in costFromGoal then
totalCost := costFromStart[currentNode] + costFromGoal[currentNode];
if totalCost < minPathCost then
bestIntersection := currentNode;
minPathCost := totalCost;
end;
end;
openSetForward.Remove(currentNode);
for each neighbor of currentNode in graph do
tentativeCost := costFromStart[currentNode] + graph.distance(currentNode, neighbor);
if tentativeCost < costFromStart[neighbor] then
cameFromForward[neighbor] := currentNode;
costFromStart[neighbor] := tentativeCost;
totalCostForward[neighbor] := tentativeCost + heuristic(neighbor, goalNode);
if neighbor not in openSetForward then
openSetForward.add(neighbor);
end;
end;
end;
// Process backward search
currentNode := node in openSetBackward with lowest totalCostBackward;
if currentNode in costFromStart then
totalCost := costFromStart[currentNode] + costFromGoal[currentNode];
if totalCost < minPathCost then
bestIntersection := currentNode;
minPathCost := totalCost;
end;
end;
openSetBackward.Remove(currentNode);
for each neighbor of currentNode in graph do
tentativeCost := costFromGoal[currentNode] + graph.distance(currentNode, neighbor);
if tentativeCost < costFromGoal[neighbor] then
cameFromBackward[neighbor] := currentNode;
costFromGoal[neighbor] := tentativeCost;
totalCostBackward[neighbor] := tentativeCost + heuristic(neighbor, startNode);
if neighbor not in openSetBackward then
openSetBackward.add(neighbor);
end;
end;
end;
// Check if we’ve found the optimal path
if bestIntersection is not null then
minRemainingCost := min(min(totalCostForward), min(totalCostBackward));
if minPathCost <= minRemainingCost then
return buildPath(cameFromForward, cameFromBackward, bestIntersection);
end;
end;
end;
return 'no path found';
end;
Illustration
[edit]
The GIF demonstrates the algorithm's execution with the following steps:
- Step 1: Node 1 (green → orange) explored forward.
- Step 2: Node 12 (blue → purple) explored backward.
- Step 3: Node 3 (orange) explored forward.
- Step 4: Node 10 (purple) explored backward.
- Step 5: Node 6 (orange) explored forward.
- Step 6: Node 9 (purple) explored backward.
- Step 7: Node 9 (orange) explored forward (meeting point).
- Step 8: Final path (1 → 3 → 6 → 9 → 12) in red.
This illustration highlights the bidirectional nature of the algorithm:
- The forward search (orange) starts at 1 and moves toward 9 (1 → 3 → 6 → 9).
- The backward search (purple) starts at 12 and moves toward 9 (12 → 10 → 9).
- They meet at node 9, simulating the bidirectional process.
Execution Table
[edit]Step | Open Set Forward | Open Set Backward | Visited Forward | Visited Backward | Meeting Node |
---|---|---|---|---|---|
1 | {1:0} | {12:0} | {1} | {12} | - |
2 | {2:3, 3:2} | {9:1, 10:2, 11:4} | {1, 2, 3} | {12, 9, 10, 11} | - |
3 | {6:7} | {6:3} | {1, 2, 3, 6} | {12, 9, 10, 11, 6} | 6 |
4 | {9:9} | - | {1, 2, 3, 6, 9} | {12, 9, 10, 11, 6} | 9 |
5 | - | - | Path Found | Path Found | 9 |
Properties
[edit]Termination and Completeness
[edit]On finite graphs with non-negative edge weights, Bidirectional A* is guaranteed to terminate and is complete if a path exists. On infinite graphs with bounded edge costs, termination requires a solution to exist.
Admissibility
[edit]Bidirectional A* is admissible if both forward and backward heuristics are admissible. The meeting point ensures the combined path is optimal when reconstructed correctly.
Optimality and Consistency
[edit]With consistent heuristics in both directions, Bidirectional A* is optimally efficient among A*-like bidirectional algorithms, expanding a subset of nodes compared to alternatives. Inconsistent heuristics may lead to suboptimal paths unless corrected.
Complexity
[edit]- Time Complexity: in the worst case, where is the branching factor and is the solution depth. The bidirectional approach often reduces effectively.
- Space Complexity: , as it stores nodes in both forward and backward open sets.
Applications
[edit]Bidirectional A* is widely used in:
- GPS Navigation: Efficiently finds routes in road networks.
- Video Games: Optimizes AI pathfinding in large maps.
- Robotics: Plans motion in dynamic environments.
Relations to Other Algorithms
[edit]- Dijkstra's algorithm: A special case of Bidirectional A* with in both directions.
Variants
[edit]- New Bidirectional A* (NBA*): Enhances efficiency with adaptive heuristics.[11]
- Time-Dependent Bidirectional A*: Adapts to dynamic graphs (e.g., traffic).
See Also
[edit]Notes
[edit]- The heuristic for the backward search must estimate the cost to the start node, which may differ from the forward heuristic unless the graph is symmetric.
- The stopping criterion requires careful design to ensure optimality, often using a condition like , where is the shortest path cost.
References
[edit]- ^ Goldberg, Andrew V.; Harrelson, Chris; Kaplan, Haim; Werneck, Renato T. (2006-04-05). "Efficient point-to-point shortest path algorithms, COS423 handout" (PDF). Princeton University.
- ^ a b Pohl, Ira (1971). Meltzer, Bernard; Michie, Donald (eds.). "Bi-directional Search" (PDF). Machine Intelligence. 6. Edinburgh University Press: 127–140.
- ^ a b de Champeaux, Dennis; Sint, Lenie (1977). "An improved bidirectional heuristic search algorithm". Journal of the ACM. 24 (2): 177–191. doi:10.1145/322003.322004.
- ^ a b c de Champeaux, Dennis (1983). "Bidirectional heuristic search again". Journal of the ACM. 30 (1): 22–32. doi:10.1145/322358.322360.
- ^ a b Auer, Andreas; Kaindl, Hermann (2004). A case study of revisiting best-first vs. depth-first search (PDF). The 16th European Conference on Artificial Intelligence.
- ^ Kwa, James B.H. (1989). "BS∗: An admissible bidirectional staged heuristic search algorithm". Artificial Intelligence. 38 (1). Elsevier BV: 95–109. doi:10.1016/0004-3702(89)90069-6. ISSN 0004-3702.
- ^ Kaindl, H.; Kainz, G. (1997-12-01). "Bidirectional Heuristic Search Reconsidered". Journal of Artificial Intelligence Research. 7. AI Access Foundation: 283–317. arXiv:cs/9712102. doi:10.1613/jair.460. ISSN 1076-9757.
- ^ The worst case is still graph-dependent, but the heuristic and bidirectional nature reduce the average-case complexity.
- ^ Goldberg, Andrew V.; Harrelson, Chris (January 23, 2005). "Computing the shortest path: A search meets graph theory". Society for Industrial and Applied Mathematics. pp. 156–165 – via ACM Digital Library.
- ^ Nannicini, Giacomo; Delling, Daniel; Schultes, Dominik; Liberti, Leo (2012). "Bidirectional A * search on time-dependent road networks". Networks. 59 (2): 240–251. doi:10.1002/net.20438.
- ^ Pijls, Wim; Post, Henk (2009). Yet another bidirectional algorithm for shortest paths (PDF) (Report). Econometric Institute, Erasmus University Rotterdam.
Further reading
[edit]- Russell, Stuart J.; Norvig, Peter (2002). "3.4 Uninformed search strategies". Artificial Intelligence: A Modern Approach (2nd ed.). Prentice Hall..