Алгоритмы Дейкстры и A*: поиск кратчайшего пути

Как найти самый быстрый путь на карте? Как ИИ в игре находит путь до цели, обходя стены?​

Ответ — в алгоритмах поиска кратчайшего пути. Сегодня разберём два самых популярных:

✅ Дейкстра
✅ A* (эй-стар)

---

🧮 Что решают эти алгоритмы​


И Дейкстра, и A* ищут путь из точки A в точку B по графу, где:

  • Вершины — узлы (например, перекрёстки, клетки карты, точки маршрута)
  • Рёбра — связи между ними с весами (расстояние, время, цена и т.д.)

Они гарантируют самый дешёвый путь, если веса неотрицательные.

---

📌 Алгоритм Дейкстры (Dijkstra)​


Простой и надёжный: на каждом шаге выбираем узел с минимальной стоимостью и обновляем соседей.

  • Не использует эвристик
  • Работает во всех графах без отрицательных весов
  • Может найти все кратчайшие пути от одной вершины

✅ Python-реализация с приоритетной очередью:
Python:
import heapq

def dijkstra(graph, start):
    dist = {v: float('inf') for v in graph}
    dist[start] = 0
    queue = [(0, start)]

    while queue:
        cost, u = heapq.heappop(queue)
        if cost > dist[u]:
            continue
        for v, weight in graph[u]:
            alt = dist[u] + weight
            if alt < dist[v]:
                dist[v] = alt
                heapq.heappush(queue, (alt, v))

    return dist

✅ Формат графа:
Python:
graph = {
    'A': [('B', 1), ('C', 4)],
    'B': [('C', 2), ('D', 5)],
    'C': [('D', 1)],
    'D': []
}

Код на Си:

C:
#include <stdio.h>
#include <limits.h>

#define V 4  // количество вершин

int min_distance(int dist[], int visited[]) {
    int min = INT_MAX, index = -1;
    for (int i = 0; i < V; i++) {
        if (!visited[i] && dist[i] < min) {
            min = dist[i];
            index = i;
        }
    }
    return index;
}

void dijkstra(int graph[V][V], int start) {
    int dist[V];
    int visited[V] = {0};

    for (int i = 0; i < V; i++)
        dist[i] = INT_MAX;

    dist[start] = 0;

    for (int count = 0; count < V - 1; count++) {
        int u = min_distance(dist, visited);
        visited[u] = 1;

        for (int v = 0; v < V; v++) {
            if (!visited[v] && graph[u][v] && dist[u] != INT_MAX &&
                dist[u] + graph[u][v] < dist[v])
            {
                dist[v] = dist[u] + graph[u][v];
            }
        }
    }

    printf("Кратчайшие расстояния от вершины %d:\n", start);
    for (int i = 0; i < V; i++)
        printf("До %d = %d\n", i, dist[i]);
}

int main() {
    // Матрица смежности (веса)
    int graph[V][V] = {
        {0, 1, 4, 0},
        {0, 0, 2, 5},
        {0, 0, 0, 1},
        {0, 0, 0, 0}
    };

    dijkstra(graph, 0);  // из вершины 0
    return 0;
}

---

🧭 Алгоритм A* (A-star)​


Это оптимизированная версия Дейкстры с «подсказками».
Он использует эвристику — оценку расстояния до цели.

Что такое эвристика в A*​

Эвристика — это оценка оставшегося расстояния от текущей вершины до цели.
Она помогает A* "предугадывать", где находится цель, и двигаться более умно, чем Дейкстра.

📌 Формула:
Код:
f(n) = g(n) + h(n)

  • g(n) — расстояние от старта до n (как у Дейкстры)
  • h(n) — приближённая оценка от n до цели (например, Евклидово или Manhattan-расстояние)

✅ Работает быстрее, если эвристика хороша.

✅ Python-пример:
Python:
import heapq

def a_star(graph, start, goal, heuristic):
    open_set = [(0, start)]
    g = {start: 0}
    came_from = {}

    while open_set:
        _, current = heapq.heappop(open_set)
        if current == goal:
            break

        for neighbor, cost in graph[current]:
            tentative_g = g[current] + cost
            if neighbor not in g or tentative_g < g[neighbor]:
                g[neighbor] = tentative_g
                f = tentative_g + heuristic(neighbor, goal)
                heapq.heappush(open_set, (f, neighbor))
                came_from[neighbor] = current

    return g.get(goal, float('inf'))

✅ Пример эвристики:

Что происходит по шагам:​

  1. g[n] — стоимость пути от старта до узла n (накапливаем её)
  2. heuristic(n, goal) — оценка до цели
  3. f = g + h — итоговая "стоимость" маршрута
  4. Используется heapq — приоритетная очередь, которая выбирает вершину с наименьшим f
Пример функции эвристики:

Python:
def manhattan(node, goal):
    x1, y1 = node
    x2, y2 = goal
    return abs(x1 - x2) + abs(y1 - y2)

Как вызывать:

Python:
res = a_star(graph, 'A', 'D', manhattan)
print("Минимальное расстояние A → D:", res)

Пример на Си, ориентирован на поле 5×5, где можно двигаться по 4 направлениям (вверх, вниз, влево, вправо), а 1 — проходимо, 0 — препятствие.

Код:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>

#define N 5

typedef struct {
    int x, y;
    int g;  // стоимость от старта
    int f;  // общая стоимость (g + h)
} Node;

int goal_x = 4, goal_y = 4;

int heuristic(int x, int y) {
    return abs(goal_x - x) + abs(goal_y - y);  // Манхэттен
}

int in_bounds(int x, int y) {
    return x >= 0 && x < N && y >= 0 && y < N;
}

int is_passable(int grid[N][N], int x, int y) {
    return grid[x][y] == 1;
}

// простая очередь с приоритетом (по f)
void push(Node* open[], int* size, Node* n) {
    int i = *size;
    while (i > 0 && open[i - 1]->f > n->f) {
        open[i] = open[i - 1];
        i--;
    }
    open[i] = n;
    (*size)++;
}

Node* pop(Node* open[], int* size) {
    return open[--(*size)];
}

void a_star(int grid[N][N], int start_x, int start_y) {
    int closed[N][N] = {0};
    int came_from[N][N][2];

    Node* open[100];
    int open_size = 0;

    Node* start = malloc(sizeof(Node));
    start->x = start_x;
    start->y = start_y;
    start->g = 0;
    start->f = heuristic(start_x, start_y);

    push(open, &open_size, start);

    int dirs[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};

    while (open_size > 0) {
        Node* current = pop(open, &open_size);

        if (current->x == goal_x && current->y == goal_y) {
            printf("Путь найден:\n");
            int x = goal_x, y = goal_y;
            while (!(x == start_x && y == start_y)) {
                printf("(%d, %d) <- ", x, y);
                int px = came_from[x][y][0];
                int py = came_from[x][y][1];
                x = px;
                y = py;
            }
            printf("(%d, %d)\n", start_x, start_y);
            return;
        }

        closed[current->x][current->y] = 1;

        for (int i = 0; i < 4; i++) {
            int nx = current->x + dirs[i][0];
            int ny = current->y + dirs[i][1];

            if (!in_bounds(nx, ny) || !is_passable(grid, nx, ny) || closed[nx][ny])
                continue;

            int new_g = current->g + 1;
            Node* neighbor = malloc(sizeof(Node));
            neighbor->x = nx;
            neighbor->y = ny;
            neighbor->g = new_g;
            neighbor->f = new_g + heuristic(nx, ny);

            came_from[nx][ny][0] = current->x;
            came_from[nx][ny][1] = current->y;

            push(open, &open_size, neighbor);
        }
    }

    printf("Путь не найден.\n");
}

Использование:

C:
int main() {
    int grid[N][N] = {
        {1, 1, 1, 1, 1},
        {0, 0, 0, 1, 0},
        {1, 1, 1, 1, 1},
        {1, 0, 0, 0, 1},
        {1, 1, 1, 1, 1}
    };

    a_star(grid, 0, 0);
    return 0;
}

---

📊 Сравнение: Дейкстра vs A*​


КритерийДейкстраA*
ЭвристикаНетДа
ОптимальностьГарантированаЕсли эвристика допустима
СкоростьМедленнееБыстрее на практике
Подходит дляВсе кратчайшие путиЦелевой путь (start → goal)
Гарантирует точный путьДаДа (если h не переоценивает)

---

🧠 Где применяются?​


✅ Дейкстра:
  • Маршруты в GPS (все пути из одной точки)
  • Планирование в графах без эвристик
  • Сети и маршрутизаторы

✅ A*:
  • Искусственный интеллект в играх
  • Роботы и дроны (путь от A до B)
  • Динамическая навигация
---

📎 Вывод​


Алгоритмы поиска кратчайшего пути — основа навигации, планирования, анализа маршрутов.
  • Дейкстра — надёжен, универсален
  • A\* — быстрее, если есть эвристика
💬 А ты где впервые сталкивался с этими алгоритмами — в играх, в олимпиадном коде, в задачах? Делись историями 👇