You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/content/post/2025/07-31-maze-solver/index.mdx
+45-2Lines changed: 45 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -177,7 +177,7 @@ Now the most important and interesting part. Let's analyze the algorithms code a
177
177
178
178
### Unweighted graphs
179
179
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.
181
181
182
182
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.
183
183
@@ -271,9 +271,52 @@ In contrast, DFS will also respect initial order in the directions array but pri
271
271
272
272
### Weighted graphs
273
273
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
+
274
276
#### Dijkstra
275
277
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.
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
+
protectedheuristic(a: Coordinate, b: Coordinate): number {
302
+
// Manhattan distance as the heuristic.
303
+
// Can only move horizontally or vertically, not diagonally. In "rectangles".
304
+
returnMath.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.
0 commit comments