Сегодня мы рассмотрим алгоритм поиска пути A *, его работу и его реализацию в псевдокоде и реальном коде на Python 🐍.
Ищете только псевдокод или исходный код? Прокрутите вниз!
Если вы разработчик игр, возможно, вы всегда хотели реализовать A * как поиск пути персонажа (или врага). Я знаю, что пару лет назад я это делал, но с моим уровнем программирования в то время у меня были проблемы с текущими статьями. Я хотел написать это как простое введение с ясным примером исходного кода для поиска пути A *, чтобы любой мог легко подобрать его и использовать в своей игре.
F = G + H
Одним из важных аспектов A * является f = g + h
. Переменные f, g и h находятся в нашем классе Node и вычисляются каждый раз, когда мы создаем новый узел. Я быстро рассмотрю, что означают эти переменные.
- F - общая стоимость узла.
- G - расстояние между текущим узлом и начальным узлом.
- H - эвристически оцененное расстояние от текущего узла до конечного узла.
Давайте взглянем на небольшой рисунок, чтобы проиллюстрировать это.
Потрясающие! Допустим, node(0)
- наша начальная позиция, а node(19)
- наша конечная позиция. Также предположим, что наш текущий узел находится в красном квадрате node(4)
.
G
G - расстояние между текущим узлом и начальным узлом.
Если мы посчитаем назад, то увидим, что node(4)
находится на расстоянии 4 пробелов от нашего начального узла.
Мы также можем сказать, что G на 1 больше, чем наш родительский узел (node(3)
). Так что в данном случае для node(4)
, currentNode.g = 4
.
H
H - эвристически оцененное расстояние от текущего узла до конечного узла.
Итак, давайте посчитаем расстояние. Если мы посмотрим, то увидим, что если мы пройдем более 7 пробелов и 3 пробела, мы достигли конечного узла (node(19)
).
Применим теорему Пифагора! a² + b² = c²
. После того, как мы применим это, мы увидим, что currentNode.h = 7² + 3²
. Или currentNode.h = 58
.
Но разве нам не нужно применять квадратный корень к 58? Неа! Мы можем пропустить это вычисление на каждом узле и при этом получить тот же результат. Умно!
С помощью эвристики нам нужно убедиться, что мы действительно можем ее вычислить. Также очень важно, чтобы эвристика всегда занижала общий путь, так как завышение приведет к тому, что A * будет искать сквозные узлы, которые могут быть не «наилучшими» с точки зрения f
значения.
F
F - общая стоимость узла.
Итак, давайте сложим h и g, чтобы получить общую стоимость нашего узла. currentNode.f = currentNode.g + currentNode.h
. Или currentNode.f = 4+ 58
. Или currentNode.f = 62
.
Время использовать f
= g + h
Хорошо, это было много работы. Теперь, после всей этой работы, для чего я собираюсь использовать это значение f
?
С этим новым значением f
мы можем посмотреть на все наши узлы и сказать: «Эй, это лучший узел, который я могу выбрать, чтобы двигаться вперед прямо сейчас?». Вместо того, чтобы перебирать все узлы, мы можем выбрать те, которые имеют наибольшие шансы на достижение нашей цели.
Вот рисунок для иллюстрации. Сверху у нас есть алгоритм Дейкстры, который выполняет поиск без этого f
значения, а ниже у нас есть A *, который использует это f
значение.
Алгоритм Дейкстры
Итак, взглянув на алгоритм Дейкстры, мы видим, что он просто продолжает поиск. Он не знает, какой узел «лучший», поэтому рассчитывает пути для всех.
Алгоритм A *
С помощью A * мы видим, что как только мы преодолеем препятствие, алгоритм отдает приоритет узлу с наименьшим f
и «наилучшим» шансом достичь конца.
Шаги метода A - от Патрика Лестера
Я вставил шаги для A * из статьи Патрика Лестера, которую вы можете проверить здесь. Этот же веб-сайт также указан ниже в ресурсах. Это безумно хорошее объяснение, и поэтому я решил пойти с ним, а не писать его снова.
1. Добавьте начальный квадрат (или узел) в открытый список.
2. Повторите следующее:
A) Найдите в открытом списке квадрат с наименьшей стоимостью F. Мы называем это текущим квадратом.
Б). Переключите его в закрытый список.
C) Для каждого из 8 квадратов, примыкающих к текущему квадрату…
- Если по нему нельзя ходить или он находится в закрытом списке, игнорируйте его. В противном случае сделайте следующее.
- Если его нет в открытом списке, добавьте его в открытый список. Сделайте текущий квадрат родительским для этого квадрата. Запишите стоимость квадрата F, G и H.
- Если он уже есть в открытом списке, проверьте, лучше ли этот путь к этому квадрату, используя стоимость G в качестве меры. Более низкая стоимость G означает, что это лучший путь. Если это так, измените родительский элемент квадрата на текущий квадрат и пересчитайте оценки G и F. Если вы сохраняете свой открытый список, отсортированный по шкале F, возможно, вам придется пересчитать список, чтобы учесть это изменение.
D) Остановитесь, когда вы:
- Добавьте целевой квадрат в закрытый список, и в этом случае путь найден, или
- Не удалось найти целевой квадрат, и открытый список пуст. В этом случае пути нет.
3. Сохраните путь. Двигаясь назад от целевого квадрата, переходите от каждого квадрата к его родительскому квадрату, пока не дойдете до начального квадрата. Это твой путь.
Псевдокод
Следуя приведенному ниже примеру, вы сможете реализовать A * на любом языке.
// A* (star) Pathfinding // Initialize both open and closed list let the openList equal empty list of nodes let the closedList equal empty list of nodes // Add the start node put the startNode on the openList (leave it's f at zero) // Loop until you find the end while the openList is not empty // Get the current node let the currentNode equal the node with the least f value remove the currentNode from the openList add the currentNode to the closedList // Found the goal if currentNode is the goal Congratz! You've found the end! Backtrack to get path // Generate children let the children of the currentNode equal the adjacent nodes for each child in the children // Child is on the closedList if child is in the closedList continue to beginning of for loop // Create the f, g, and h values child.g = currentNode.g + distance between child and current child.h = distance from child to end child.f = child.g + child.h // Child is already in openList if child.position is in the openList's nodes positions if the child.g is higher than the openList node's g continue to beginning of for loop // Add the child to the openList add the child to the openList
Исходный код (на Python 🐍)
Не стесняйтесь использовать этот код в своих собственных проектах.
Обновление: см. комментарии к моей сути здесь и вилку моей сути здесь - она включает исправления ошибок, которые присутствуют в приведенном ниже коде.
Ресурсы
Ниже приведены несколько отличных веб-сайтов, на которые стоит обратить внимание. Я особенно рекомендую A * Pathfinding для начинающих.
Поиск с использованием A * (A-Star)
« Раскрашивание узла означает его пометку, чтобы показать, что мы туда попали. Это предотвращает поиск в том же узле больше… web.mit.edu »