mirror of
https://github.com/mfocko/blog.git
synced 2024-11-22 04:53:47 +01:00
algorithms(bf-to-astar): add intro
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
d99297a19c
commit
d16eaafd97
5 changed files with 295 additions and 0 deletions
169
algorithms/11-paths/2024-01-01-bf-to-astar/index.md
Normal file
169
algorithms/11-paths/2024-01-01-bf-to-astar/index.md
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
---
|
||||||
|
id: index
|
||||||
|
slug: /paths/bf-to-astar
|
||||||
|
title: From BF to A*
|
||||||
|
description: |
|
||||||
|
Figuring out shortest-path problem from the BF to the A* algorithm.
|
||||||
|
tags:
|
||||||
|
- cpp
|
||||||
|
- brute force
|
||||||
|
- bellman ford
|
||||||
|
- dynamic programming
|
||||||
|
- dijkstra
|
||||||
|
- a star
|
||||||
|
last_update:
|
||||||
|
date: 2024-01-01
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intro
|
||||||
|
|
||||||
|
We will delve into the details and ideas of the most common path-finding
|
||||||
|
algorithms. For the purpose of demonstrating some “features” of the improved
|
||||||
|
algorithms, we will use a 2D map with some rules that will allow us to show cons
|
||||||
|
and pros of the shown algorithms.
|
||||||
|
|
||||||
|
Let's have a look at the example map:
|
||||||
|
```
|
||||||
|
#############
|
||||||
|
#..#..*.*.**#
|
||||||
|
##***.....**#
|
||||||
|
#..########.#
|
||||||
|
#...###...#.#
|
||||||
|
#..#...##.#.#
|
||||||
|
#..#.*.#..#.#
|
||||||
|
#....#....#.#
|
||||||
|
########*.*.#
|
||||||
|
#...........#
|
||||||
|
#############
|
||||||
|
```
|
||||||
|
|
||||||
|
We can see three different kinds of cells:
|
||||||
|
1. `#` which represent walls, that cannot be entered at all
|
||||||
|
2. `*` which represent vortices that can be entered at the cost of 5 coins
|
||||||
|
3. `.` which represent normal cells that can be entered for 1 coin (which is the
|
||||||
|
base price of moving around the map)
|
||||||
|
|
||||||
|
Let's dissect a specific position on the map to get a better grasp of the rules:
|
||||||
|
```
|
||||||
|
.
|
||||||
|
#S*
|
||||||
|
.
|
||||||
|
```
|
||||||
|
We are standing in the cell marked with `S` and we have the following options
|
||||||
|
* move to the north (`.`) with the cost of 1 coin,
|
||||||
|
* move to the west (`#`) **is not** allowed because of the wall,
|
||||||
|
* move to the east (`*`) is allowed with the cost of 5 coins, and finally
|
||||||
|
* move to the south (`.`) with the cost of 1 coin.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
Further on I will follow the same scheme for marking cells with an addition of
|
||||||
|
`D` to denote the _destination_ to which we will be finding the shortest path.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Boilerplate
|
||||||
|
|
||||||
|
For working with this map I have prepared a basic structure for the graph in C++
|
||||||
|
that will abstract some of the internal workings of our map, namely:
|
||||||
|
* remembers the costs of moving around
|
||||||
|
* provides a simple function that returns price for moving **directly** between
|
||||||
|
two positions on the map
|
||||||
|
* allows us to print the map out, just in case we'd need some adjustments to be
|
||||||
|
made
|
||||||
|
|
||||||
|
We can see the `graph` header here:
|
||||||
|
```cpp
|
||||||
|
#ifndef _GRAPH_HPP
|
||||||
|
#define _GRAPH_HPP
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
#include <ostream>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using vertex_t = std::pair<int, int>;
|
||||||
|
|
||||||
|
struct graph {
|
||||||
|
graph(const std::vector<std::vector<char>>& map)
|
||||||
|
: map(map),
|
||||||
|
_height(static_cast<int>(map.size())),
|
||||||
|
_width(map.empty() ? 0 : static_cast<int>(map[0].size())) {}
|
||||||
|
|
||||||
|
static auto unreachable() -> int { return UNREACHABLE; }
|
||||||
|
static auto normal_cost() -> int { return NORMAL_COST; }
|
||||||
|
static auto vortex_cost() -> int { return VORTEX_COST; }
|
||||||
|
|
||||||
|
auto cost(const vertex_t& u, const vertex_t& v) const -> int {
|
||||||
|
auto [ux, uy] = u;
|
||||||
|
auto [vx, vy] = v;
|
||||||
|
|
||||||
|
auto hd = std::abs(ux - vx) + std::abs(uy - vy);
|
||||||
|
switch (hd) {
|
||||||
|
// ‹u = v›; staying on the same cell
|
||||||
|
case 0:
|
||||||
|
return 0;
|
||||||
|
// ‹u› and ‹v› are neighbours
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
// ‹u› and ‹v› are not neighbouring cells
|
||||||
|
default:
|
||||||
|
return UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// boundary check
|
||||||
|
if (vy < 0 || vy >= _height || vx < 0 || vx >= _width) {
|
||||||
|
return UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (map[vy][vx]) {
|
||||||
|
case '#':
|
||||||
|
return UNREACHABLE;
|
||||||
|
case '*':
|
||||||
|
return VORTEX_COST;
|
||||||
|
default:
|
||||||
|
return NORMAL_COST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto width() const -> int { return _width; }
|
||||||
|
auto height() const -> int { return _height; }
|
||||||
|
auto has(const vertex_t& v) const -> bool {
|
||||||
|
auto [x, y] = v;
|
||||||
|
return (0 <= y && y < _height) && (0 <= x && x < _width);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const graph& g);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::vector<char>> map;
|
||||||
|
int _height, _width;
|
||||||
|
|
||||||
|
const static int UNREACHABLE = std::numeric_limits<int>::max();
|
||||||
|
// XXX: modify here to change the price of entering the vortex
|
||||||
|
const static int VORTEX_COST = 5;
|
||||||
|
const static int NORMAL_COST = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const graph& g) {
|
||||||
|
for (const auto& row : g.map) {
|
||||||
|
for (const char cell : row) {
|
||||||
|
os << cell;
|
||||||
|
}
|
||||||
|
os << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _GRAPH_HPP */
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info Source code
|
||||||
|
|
||||||
|
**TODO** link the sources
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Let's finally start with some algorithms!
|
0
static/files/algorithms/paths/bf-to-astar/.archive
Normal file
0
static/files/algorithms/paths/bf-to-astar/.archive
Normal file
84
static/files/algorithms/paths/bf-to-astar/graph.hpp
Normal file
84
static/files/algorithms/paths/bf-to-astar/graph.hpp
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#ifndef _GRAPH_HPP
|
||||||
|
#define _GRAPH_HPP
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
#include <ostream>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using vertex_t = std::pair<int, int>;
|
||||||
|
|
||||||
|
struct graph {
|
||||||
|
graph(const std::vector<std::vector<char>>& map)
|
||||||
|
: map(map),
|
||||||
|
_height(static_cast<int>(map.size())),
|
||||||
|
_width(map.empty() ? 0 : static_cast<int>(map[0].size())) {}
|
||||||
|
|
||||||
|
static auto unreachable() -> int { return UNREACHABLE; }
|
||||||
|
static auto normal_cost() -> int { return NORMAL_COST; }
|
||||||
|
static auto vortex_cost() -> int { return VORTEX_COST; }
|
||||||
|
|
||||||
|
auto cost(const vertex_t& u, const vertex_t& v) const -> int {
|
||||||
|
auto [ux, uy] = u;
|
||||||
|
auto [vx, vy] = v;
|
||||||
|
|
||||||
|
auto md = std::abs(ux - vx) + std::abs(uy - vy);
|
||||||
|
switch (md) {
|
||||||
|
// ‹u = v›; staying on the same cell
|
||||||
|
case 0:
|
||||||
|
return 0;
|
||||||
|
// ‹u› and ‹v› are neighbours
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
// ‹u› and ‹v› are not neighbouring cells
|
||||||
|
default:
|
||||||
|
return UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// boundary check
|
||||||
|
if (vy < 0 || vy >= _height || vx < 0 || vx >= _width) {
|
||||||
|
return UNREACHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (map[vy][vx]) {
|
||||||
|
case '#':
|
||||||
|
return UNREACHABLE;
|
||||||
|
case '*':
|
||||||
|
return VORTEX_COST;
|
||||||
|
default:
|
||||||
|
return NORMAL_COST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto width() const -> int { return _width; }
|
||||||
|
auto height() const -> int { return _height; }
|
||||||
|
auto has(const vertex_t& v) const -> bool {
|
||||||
|
auto [x, y] = v;
|
||||||
|
return (0 <= y && y < _height) && (0 <= x && x < _width);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const graph& g);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::vector<char>> map;
|
||||||
|
int _height, _width;
|
||||||
|
|
||||||
|
const static int UNREACHABLE = std::numeric_limits<int>::max();
|
||||||
|
// XXX: modify here to change the price of entering the vortex
|
||||||
|
const static int VORTEX_COST = 5;
|
||||||
|
const static int NORMAL_COST = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const graph& g) {
|
||||||
|
for (const auto& row : g.map) {
|
||||||
|
for (const char cell : row) {
|
||||||
|
os << cell;
|
||||||
|
}
|
||||||
|
os << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _GRAPH_HPP */
|
34
static/files/algorithms/paths/bf-to-astar/main.cpp
Normal file
34
static/files/algorithms/paths/bf-to-astar/main.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "astar.hpp"
|
||||||
|
#include "bf.hpp"
|
||||||
|
#include "dijkstra.hpp"
|
||||||
|
#include "graph.hpp"
|
||||||
|
|
||||||
|
auto line_to_vector(const std::string& l) -> std::vector<char> {
|
||||||
|
return std::vector(l.begin(), l.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto main() -> int {
|
||||||
|
graph g{std::vector{
|
||||||
|
line_to_vector(std::string("#############")),
|
||||||
|
line_to_vector(std::string("#..#..*.*.**#")),
|
||||||
|
line_to_vector(std::string("##***.....**#")),
|
||||||
|
line_to_vector(std::string("#..########.#")),
|
||||||
|
line_to_vector(std::string("#...###...#.#")),
|
||||||
|
line_to_vector(std::string("#..#...##.#.#")),
|
||||||
|
line_to_vector(std::string("#..#.*.#..#.#")),
|
||||||
|
line_to_vector(std::string("#....#....#.#")),
|
||||||
|
line_to_vector(std::string("########*.*.#")),
|
||||||
|
line_to_vector(std::string("#...........#")),
|
||||||
|
line_to_vector(std::string("#############")),
|
||||||
|
}};
|
||||||
|
std::cout << "Normal cost: " << g.normal_cost() << "\n";
|
||||||
|
std::cout << "Vortex cost: " << g.vortex_cost() << "\n";
|
||||||
|
std::cout << "Graph:\n" << g;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
8
static/files/algorithms/paths/bf-to-astar/makefile
Normal file
8
static/files/algorithms/paths/bf-to-astar/makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
CXX=c++
|
||||||
|
CXXFLAGS=-std=c++20 -Wall -Wextra -g
|
||||||
|
|
||||||
|
all: format
|
||||||
|
$(CXX) $(CXXFLAGS) main.cpp -o main
|
||||||
|
|
||||||
|
format:
|
||||||
|
clang-format -i -style=google *.hpp *.cpp
|
Loading…
Reference in a new issue