A graph search algorithm which explores a graph by expanding the most promising node chosen according to a specified rule. It uses a priority queue to select the next node to visit based on a heuristic cost.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
{
"status": "Starting Search",
"target": 2
}Overview
Best-first search is a family of graph searches that always expands the most promising node next, according to a heuristic score. It keeps the frontier in a priority queue rather than a plain queue (BFS) or stack (DFS). Greedy best-first search and A* are the best-known members.
In this visualizer the heuristic is simply the node's value: the smallest value in the priority queue is expanded first. That keeps the example easy to follow; in a real application the score would be something like the straight-line distance to the goal.
The default tree is not a BST (5 has children 18 and 2), and the target is 2. Starting at 10, the search queues 5 and 15, expands 5 because it has the lower score, queues 18 and 2, and then expands 2 — the target — after visiting only three nodes. The path is 10 -> 5 -> 2.
Step by step
- Put the start node in a priority queue.
- While the queue is not empty, remove the node with the best (lowest) score.
- If it is the target, stop.
- Mark it visited and add its unvisited neighbours (children) to the priority queue.
- If the queue empties, the target is unreachable.
Complexity
| Case | Cost | Why |
|---|---|---|
| Time (worst) | O(b^d) | b is the branching factor and d the depth; a poor heuristic can explore everything. |
| Time (good heuristic) | Close to O(d) | A heuristic that points toward the goal expands only nodes on the path. |
| Space | O(b^d) | The priority queue can hold the whole frontier. |
When to use it
- Path-finding in games and maps, where a distance estimate to the goal is available.
- Puzzle solving (8-puzzle, route planning) where exploring every state is too slow.
- As a stepping stone to A*, which adds the cost so far to the heuristic and becomes optimal.
Common mistakes
- Greedy best-first search is not guaranteed to find the shortest path; it can be lured down a path that looks good locally.
- Without a visited set, graphs with cycles can make it loop forever.
- Re-sorting an array on every step, as this demo does for clarity, is O(n log n) per step; real code uses a binary heap.
A dry run is the best way to check your understanding of Best-First Search: follow each row, predict the next one, and compare. The run below takes 29 steps in total. The Line column refers to the line numbers in the code listing above.
- Input
- Binary tree (see the input box above)
- Target
- 2
- Result
- status = Target found!, final_path = 10 -> 5 -> 2
| Step | Line | What happens |
|---|---|---|
| 1 | 1 | status = Starting Search, target = 2 |
| 2 | 3 | action = Add start node (10) to priority queue, priority_queue = 10 |
| 3 | 5 | condition = 1 > 0, priority_queue = 10 |
| 4 | 6 | action = Sort priority queue, priority_queue = 10 |
| 5 | 8 | action = Expand best node: 10, current_path = 10, priority_queue = |
| 6 | 9 | current_node = 10, target = 2, condition = 10 === 2 |
| 7 | 11 | action = Mark 10 as visited, visited = 10 |
| 8 | 13 | action = Checking child 5 of 10 |
| 9 | 14 | child = 5, status = Not visited. Adding to queue. |
| 10 | 15 | action = Add 5 to queue, priority_queue = 5 |
| 11 | 13 | action = Checking child 15 of 10 |
| 12 | 14 | child = 15, status = Not visited. Adding to queue. |
| 13 | 15 | action = Add 15 to queue, priority_queue = 5, 15 |
| 14 | 5 | condition = 2 > 0, priority_queue = 5, 15 |
| 15 | 6 | action = Sort priority queue, priority_queue = 5, 15 |
| 16 | 8 | action = Expand best node: 5, current_path = 10 -> 5, priority_queue = 15 |
| 17 | 9 | current_node = 5, target = 2, condition = 5 === 2 |
| 18 | 11 | action = Mark 5 as visited, visited = 10, 5 |
| 19 | 13 | action = Checking child 18 of 5 |
| 20 | 14 | child = 18, status = Not visited. Adding to queue. |
| 21 | 15 | action = Add 18 to queue, priority_queue = 15, 18 |
| 22 | 13 | action = Checking child 2 of 5 |
| 23 | 14 | child = 2, status = Not visited. Adding to queue. |
| 24 | 15 | action = Add 2 to queue, priority_queue = 15, 18, 2 |
| 25 | 5 | condition = 3 > 0, priority_queue = 15, 18, 2 |
| 26 | 6 | action = Sort priority queue, priority_queue = 2, 15, 18 |
| 27 | 8 | action = Expand best node: 2, current_path = 10 -> 5 -> 2, priority_queue = 15, 18 |
| 28 | 9 | current_node = 2, target = 2, condition = 2 === 2 |
| 29 | 10 | status = Target found!, final_path = 10 -> 5 -> 2 |