Skip to content

Commit e525640

Browse files
committed
dijkstra and a star
1 parent 8fbfa3c commit e525640

File tree

1 file changed

+45
-2
lines changed
  • src/content/post/2025/07-31-maze-solver

1 file changed

+45
-2
lines changed

src/content/post/2025/07-31-maze-solver/index.mdx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ Now the most important and interesting part. Let's analyze the algorithms code a
177177

178178
### Unweighted graphs
179179

180-
BFS and DFS are basic traversal algorithms that ignore the weights of the edges so they are applicable only on unweighted graphs and not on weighted graphs (edges don't have uniform weights) where Dijkstra should be used.
180+
BFS and DFS are basic traversal algorithms that ignore the weights of the edges so they are applicable only on unweighted graphs.
181181

182182
The actual code for BFS and DFS has only a single line difference, but they have a completely opposite behavior. BFS uses a queue (FIFO), and DFS uses a stack (LIFO) and this has a fundamental impact on the way the next node candidate for the path is selected.
183183

@@ -271,9 +271,52 @@ In contrast, DFS will also respect initial order in the directions array but pri
271271

272272
### Weighted graphs
273273

274+
Not all graphs have edges with uniform weights. In such cases we must use algorithms that are aware of weights (cost between two nodes), Dijkstra and A* in this case.
275+
274276
#### Dijkstra
275277

276-
#### A*
278+
Dijkstra is aware of the cost between two nodes (edge weight) and it will take it into account when selecting the next node. It uses a priority queue to keep the cost history.
279+
280+
```ts
281+
// Take the first element from the priority queue.
282+
// Choose the node that ads minimal cost.
283+
queue.sort((a, b) => a.cost - b.cost);
284+
const { coord, path, cost } = queue.shift()!;
285+
286+
// ...
287+
288+
// Test how much cost every new node ads to the path before adding it to the queue.
289+
const nextCost = cost + this.maze.getCost(nextCoord);
290+
// ...
291+
if( ... && !costMap.has(coordKey) || nextCost < costMap.get(coordKey)!)
292+
```
293+
294+
Dijkstra keeps a history of the cost of the current path and when selecting the next node it will choose the node that does minimal addition to the cost. If there are cycles it will access the same node from multiple paths and will choose the one with minimal weight (shortest path). In graphs with constant edge weights it reduces to BFS. That can be observed in the screenshot above where both BFS and Dijkstra take an equal number of steps, because the maze has uniform weight of 1 and Infinity.
295+
296+
#### A\*
297+
298+
A\* is same as Dijkstra but beside the keeping the history it uses the heuristics function to predict the future - the direction in which the end node could be.
299+
300+
```ts
301+
protected heuristic(a: Coordinate, b: Coordinate): number {
302+
// Manhattan distance as the heuristic.
303+
// Can only move horizontally or vertically, not diagonally. In "rectangles".
304+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
305+
}
306+
307+
// ...
308+
309+
openSet.sort(
310+
(a, b) =>
311+
// The most important line. The only difference from Dijkstra.
312+
// Cost = history + Manhattan distance from the end node.
313+
// prettier-ignore
314+
(a.cost + this.heuristic(a.coord, end)) -
315+
(b.cost + this.heuristic(b.coord, end))
316+
);
317+
```
318+
319+
If the heuristic function is well chosen it will make A\* more efficient than the before mentioned algorithms. Consequently, if the heuristic function is poorly chosen it will degrade the algorithm efficiency.
277320

278321
## Completed code
279322

0 commit comments

Comments
 (0)