1. Введение
В современной информатике теория графов (Graph Theory) предоставляет мощную математическую основу для моделирования сетевых структур. В нашей повседневной жизни технологии вычисления «кратчайшего пути» используются в различных ситуациях, таких как автомобильная навигация, информация о пересадках на поездах, интернет-маршрутизация и даже поиск пути ИИ в играх.
В этой статье представлено всестороннее объяснение, начиная с математического определения теории графов, которая является основой этого поиска пути, и заканчивая механизмами типичных алгоритмов поиска: алгоритма Дейкстры (Dijkstra’s Algorithm) и его дальнейшего развития, алгоритма A* (A-Star Algorithm), их математическими доказательствами и практическими методами реализации с использованием Python.
2. Основы теории графов
Перед тем как перейти к объяснению алгоритмов, давайте сначала математически определим граф, который является целевой структурой данных.
2.1 Математическое определение графа
Граф $ G $ определяется парой из множества вершин (Vertex/Node) $ V $ и множества ребер (Edge) $ E $.
$$ G = (V, E) $$Здесь элемент $ e $ множества ребер $ E $ соединяет две вершины $ u, v \in V $ и представляется как $ e = (u, v) $.
- Неориентированный граф (Undirected Graph): Граф без направления ребер. Если $ (u, v) \in E $, то $ (v, u) \in E $.
- Ориентированный граф (Directed Graph): Граф с направлением ребер. $ (u, v) $ и $ (v, u) $ различаются.
2.2 Взвешенный граф (Weighted Graph)
При реальном поиске пути необходимо учитывать расстояние, время, стоимость и т. д. Поэтому мы рассмотрим взвешенный граф, в котором каждому ребру назначен «вес» (Weight). При введении весовой функции $ w: E \rightarrow \mathbb{R} $ граф определяется как $ G = (V, E, w) $.
$$ w(u, v) \ge 0 $$Во многих случаях, поскольку расстояние и время не могут быть отрицательными, предполагается, что веса ребер неотрицательны.
graph LR
A(("A")) -- "4" --> B(("B"))
A -- "2" --> C(("C"))
B -- "5" --> D(("D"))
C -- "1" --> B
C -- "8" --> D
C -- "10" --> E(("E"))
D -- "2" --> E
D -- "6" --> Z(("Z"))
E -- "3" --> Z
Приведенный выше рисунок представляет собой пример взвешенного ориентированного графа от вершины $ A $ до $ Z $. Числа на ребрах обозначают стоимость (вес).
2.3 Формулировка задачи о кратчайшем пути
Пусть путь (Path) $ P $ от начальной точки (Source) $ s \in V $ до конечной точки (Target) $ t \in V $ представляет собой последовательность вершин $ (v_0, v_1, \dots, v_k) $ (где $ v_0 = s, v_k = t $), и для каждого $ i $ выполняется $ (v_i, v_{i+1}) \in E $. Общая стоимость $ W(P) $ этого пути $ P $ выражается суммой весов ребер на пути.
$$ W(P) = \sum_{i=0}^{k-1} w(v_i, v_{i+1}) $$Задача о кратчайшем пути (Shortest Path Problem) — это задача нахождения пути $ P^* $, минимизирующего $ W(P) $ среди всех возможных путей $ P $.
3. Алгоритм Дейкстры (Dijkstra’s Algorithm)
Алгоритм Дейкстры, разработанный Эдсгером Дейкстрой, представляет собой алгоритм для нахождения кратчайших путей от одной начальной вершины до всех остальных вершин в графе с неотрицательными весами.
3.1 Интуитивное понимание алгоритма
Алгоритм Дейкстры основан на жадном алгоритме (Greedy Algorithm) с подходом «последовательного определения ближайшей неподтвержденной вершины от начальной точки».
- Подготовьте массив для хранения предварительного расстояния от начальной точки, инициализируйте начальную точку как
0, а остальные — какбесконечность( $ \infty $ ). - Среди неподтвержденных вершин выберите вершину $ u $ с минимальным предварительным расстоянием и отметьте её как «подтвержденную».
- Для всех смежных с вершиной $ u $ вершин $ v $, если путь через $ u $ дает меньшее предварительное расстояние, обновите расстояние (эта операция называется релаксацией (Relaxation)).
- Повторяйте шаги 2 и 3, пока все вершины не будут подтверждены или пока не будет подтверждена целевая вершина.
3.2 Математическое выражение релаксации (Relaxation)
Операция релаксации ребра от вершины $ u $ к $ v $ математически выражается следующим образом. Здесь $ d[v] $ обозначает текущее предварительное кратчайшее расстояние от начальной точки до $ v $.
$$ \text{если } d[u] + w(u, v) < d[v]: \\ d[v] = d[u] + w(u, v) $$3.3 Реализация алгоритма Дейкстры на Python
Для эффективной реализации в качестве структуры данных для получения минимального значения используется очередь с приоритетом (Priority Queue). В Python можно использовать модуль heapq.
| |
3.4 О вычислительной сложности
Если в качестве очереди с приоритетом используется бинарная куча (Binary Heap), каждая вершина извлекается из очереди один раз, а каждое ребро релаксируется один раз. Следовательно, временная сложность составляет $ O((|V| + |E|) \log |V|) $. При использовании фибоначчиевой кучи теоретически она улучшается до $ O(|E| + |V| \log |V|) $, но на практике часто применяется бинарная куча.
4. Алгоритм A* (A-Star Algorithm)
Алгоритм Дейкстры надежен, но может привести к множеству ненужных поисков, поскольку расширяет поиск во всех направлениях без учета направления цели. Эту проблему решает алгоритм A*.
4.1 Введение эвристической функции
Алгоритм A* приоритетно продвигает поиск в направлении цели, используя «оценочное расстояние» от текущего узла до цели. Функция, возвращающая это оценочное расстояние, называется эвристической функцией (Heuristic Function) $ h(n) $.
В A* функция $ f(n) $ для оценки узла $ n $ определяется следующим образом.
$$ f(n) = g(n) + h(n) $$Где,
- $ g(n) $: Фактическая стоимость от начальной точки до узла $ n $ (то же самое, что расстояние в алгоритме Дейкстры)
- $ h(n) $: Оценочная стоимость от узла $ n $ до конечной точки (эвристика)
- $ f(n) $: Оценочная общая стоимость пути от начальной точки до конечной через $ n $
4.2 Условия эвристики
Чтобы алгоритм A* всегда находил кратчайший путь (оптимальность), эвристическая функция $ h(n) $ должна удовлетворять следующим условиям.
- $$
h(n) \le h^*(n)
$$
(где $ h^*(n) $ — истинная кратчайшая стоимость от $ n $ до конечной точки)
- $$
h(m) \le c(m, n) + h(n)
$$
Здесь $ c(m, n) $ — стоимость ребра от $ m $ к $ n $. Согласованная эвристика автоматически становится допустимой.
4.3 Типичные эвристические функции
Для поиска пути на сетке часто используются следующие функции расстояния.
- Манхэттенское расстояние (Manhattan Distance): Когда движение возможно только вверх, вниз, влево и вправо $$ h(n) = |x_n - x_{goal}| + |y_n - y_{goal}| $$
- Евклидово расстояние (Euclidean Distance): Когда возможно прямолинейное движение в любом направлении $$ h(n) = \sqrt{(x_n - x_{goal})^2 + (y_n - y_{goal})^2} $$
4.4 Реализация алгоритма A* на Python
Реализация A* очень похожа на алгоритм Дейкстры, за исключением того, что ключом для очереди с приоритетом становится $ f(n) $.
| |
4.5 Сравнение алгоритма Дейкстры и A*
Следующая диаграмма Mermaid представляет собой концептуальное сравнение диапазонов поиска алгоритма Дейкстры и A*. В то время как алгоритм Дейкстры расширяет поиск концентрическими кругами, A* продвигает поиск в форме эллипса, вытянутого в направлении цели.
graph TD
subgraph "Дейкстра"
S1(("Начало")) --> A1((" "))
S1 --> B1((" "))
S1 --> C1((" "))
A1 --> D1((" "))
B1 --> Goal1(("Цель"))
C1 --> E1((" "))
style S1 fill:#4a9,stroke:#333
style Goal1 fill:#f94,stroke:#333
end
subgraph "A_Звезда"
S2(("Начало")) --> B2((" "))
B2 --> Goal2(("Цель"))
style S2 fill:#4a9,stroke:#333
style Goal2 fill:#f94,stroke:#333
end
5. Применение поиска пути и перспективы
Алгоритм Дейкстры и алгоритм A* являются базовыми методами, но они служат основой для многих прикладных технологий.
- Двунаправленный поиск (Bidirectional Search): Метод, который радикально сокращает пространство поиска путем одновременного продвижения поиска как от начальной, так и от конечной точки, объединяя их посередине.
- Алгоритм D* (Dynamic A*): Метод эффективного пересчета пути в средах, где динамически появляются неизвестные препятствия (например, автономное вождение роботов).
- JPS (Jump Point Search): Метод дальнейшего ускорения поиска A* на однородных картах-сетках. Он использует симметрию для пропуска ненужных узлов.
Алгоритмы поиска пути — это область, в которой прекрасно сочетаются математическая красота теории графов и алгоритмическая эффективность информатики.
6. Заключение
В этой статье мы начали с базовых определений теории графов и объяснили математическую подоплеку, конкретные механизмы и примеры реализации на Python алгоритма Дейкстры и алгоритма A*.
- Алгоритм Дейкстры оценивает все узлы одинаково и гарантирует нахождение надежного кратчайшего пути.
- Алгоритм A* реализует эффективный поиск к цели путем введения эвристической функции $ h(n) $.
Эти знания не ограничиваются простым пониманием алгоритмов, но станут мощным инструментом мышления для сведения сложных проблем реального мира к математической модели под названием «граф» и нахождения оптимального решения. Обязательно попробуйте запустить реальный код и ощутить его мощь.
