Я хотів би зрозуміти на фундаментальному рівні спосіб роботи A *. Будь-яка реалізація коду чи psuedo-коду, а також візуалізації будуть корисними.
Я хотів би зрозуміти на фундаментальному рівні спосіб роботи A *. Будь-яка реалізація коду чи psuedo-коду, а також візуалізації будуть корисними.
Відповіді:
В Інтернеті можна знайти багато прикладів коду та пояснень A *. Це питання також отримало багато чудових відповідей з великою кількістю корисних посилань. У своїй відповіді я спробую надати проілюстрований приклад алгоритму, який може бути легше зрозуміти, ніж код або описи.
Щоб зрозуміти A *, пропоную спершу ознайомитися з алгоритмом Дійкстри . Дозвольте мені ознайомити вас з кроками, які алгоритм Діккстри виконує для пошуку.
Наш стартовий вузол є, A
і ми хочемо знайти найкоротший шлях F
. Кожен край графіку має пов’язану з ним вартість руху (позначається чорними цифрами поруч із ребрами). Наша мета - оцінити мінімальну вартість подорожі для кожної вершини (або вузла) графіка, поки ми не потрапимо в наш вузол цілі.
Це наш вихідний пункт. У нас є вузли списку для вивчення, цей список наразі:
{ A(0) }
A
має вартість 0
, всі інші вузли встановлюються нескінченними (у типовій реалізації це буде щось подібне int.MAX_VALUE
чи подібне).
Ми беремо вузол з найнижчою вартістю зі нашого списку вузлів (оскільки в нашому списку міститься лише A
цей наш кандидат) і відвідуємо всіх його сусідів. Ми встановлюємо вартість кожного сусіда:
Cost_of_Edge + Cost_of_previous_Node
і відслідковувати попередній вузол (показаний як маленький рожевий лист нижче вузла). A
може бути позначено як вирішене (червоне) зараз, щоб ми більше не відвідували його. Наш список кандидатів зараз виглядає приблизно так:
{ B(2), D(3), C(4) }
Знову ми беремо вузол з найнижчою вартістю з нашого списку ( B
) та оцінюємо його сусідів. Шлях до D
дорожчого, ніж поточна вартість D
, тому цей шлях можна відкинути. E
буде додано до нашого списку кандидатів, який зараз виглядає так:
{ D(3), C(4), E(4) }
Наступний вузол для вивчення - зараз D
. Підключення до C
можна відмовитись, оскільки шлях не коротший за існуючу вартість. E
Однак ми знайшли коротший шлях , тому вартість E
і попередній вузол буде оновлено. Наш список зараз виглядає так:
{ E(3), C(4) }
Отже, як ми робили раніше, ми вивчаємо вузол з найнижчою вартістю з нашого списку, який зараз є E
. E
має лише один невирішений сусід, який також є цільовим вузлом. Вартість досягнення цільового вузла встановлена 10
та попередній вузол до E
. Наш список кандидатів зараз виглядає приблизно так:
{ C(4), F(10) }
Далі ми розглядаємо C
. Ми можемо оновити вартість та попередній вузол для F
. Оскільки наш список зараз має F
як вузол з найнижчою вартістю, ми закінчили. Наш шлях можна побудувати, відслідковуючи попередні найкоротші вузли.
Тож ви можете задуматися, чому я пояснив вам Dijkstra замість алгоритму A * ? Ну, різниця лише в тому, як ви зважуєте (або сортуєте) своїх кандидатів. З Dijkstra це:
Cost_of_Edge + Cost_of_previous_Node
З A * це:
Cost_of_Edge + Cost_of_previous_Node + Estimated_Cost_to_reach_Target_from(Node)
Де Estimated_Cost_to_reach_Target_from
зазвичай називають евристичну функцію. Це функція, яка спробує оцінити вартість досягнення цільового вузла. Хороша евристична функція дозволить досягти того, що для пошуку цілі доведеться відвідувати менше вузлів. Хоча алгоритм Діккстри розшириться на всі сторони, A * (завдяки евристичному) шукатиме у напрямку до цілі.
На сторінці Еміта про евристику є хороший огляд щодо поширеної евристики.
Пошук * шляху - це найкращий перший тип пошуку, який використовує додатковий евристичний характер.
Перше, що вам потрібно зробити - це розділити область пошуку. Для цього пояснення карта є квадратною сіткою плиток, оскільки більшість 2D-ігор використовують сітку плиток і тому, що це просто уявити. Однак зауважте, що область пошуку може бути розбита будь-яким способом: можливо, шістнадцятковою сіткою або навіть довільними формами, такими як ризик. Різні положення карти називаються "вузлами", і цей алгоритм буде працювати будь-який час, коли у вас буде купа вузлів для переходу та встановлення зв'язків між вузлами.
У будь-якому випадку, починаючи з заданої стартової плитки:
8 плиток навколо стартової плитки "набираються" виходячи з а) вартості переходу від поточної плитки до наступної плитки (як правило, 1 для горизонтальних або вертикальних рухів, sqrt (2) для діагонального руху).
Після цього кожній плитці присвоюється додаткова «евристична» оцінка - наближення відносної вартості переміщення до кожної плитки. Використовуються різні евристики, найпростішою є прямолінійна відстань між центрами даної плитки та торцевою плиткою.
Поточна плитка потім «закривається», і агент переміщується до сусідньої плитки, яка відкрита, має найнижчу оцінку руху і найнижчу евристичну оцінку.
Цей процес повторюється, поки не буде досягнуто цільового вузла або не буде більше відкритих вузлів (тобто агент заблокований).
Діаграми, що ілюструють ці кроки, перегляньте цей посібник для початківців .
Є деякі вдосконалення, в основному в частині поліпшення евристики:
З урахуванням відмінностей місцевості, нерівності, крутості тощо.
Також іноді корисно робити «розгортку» по всій сітці, щоб заблокувати ділянки карти, які не є ефективними шляхами: наприклад, форма U, звернена до агента. Без тесту перевірки агент спершу входив би до U, перевертався, потім виходив і обходив край У. "Справжній" розумний агент відзначав би пастку у формі U та просто уникав цього. Підмітання може допомогти імітувати це.
Це далеко не найкраще, але це реалізація, яку я зробив A * в C ++ кілька років тому.
Напевно, краще, щоб я вказав вам на ресурси, ніж намагаюся пояснити весь алгоритм. Також, читаючи статтю wiki, пограйте з демонстрацією та побачите, чи можете ви уявити, як вона працює. Залиште коментар, якщо у вас є конкретні запитання.
Стаття ActiveTut про пошук шляху може бути корисною. Він перевершує як алгоритм A *, так і Дійкстра і відмінності між ними. Він орієнтований на розробників Flash, але він повинен дати гарне уявлення про теорію, навіть якщо ви не використовуєте Flash.
Одне, що важливо візуалізувати при роботі з A * та алгоритмом Дейкстри, - це те, що A * спрямований; вона намагається знайти найкоротший шлях до певної точки, "здогадуючись", в якому напрямку шукати. Алгоритм Дейкстри знаходить найкоротший шлях до / кожного / точки.
Отже, як і перше твердження, A * лежить в основі алгоритму дослідження графіків. Зазвичай в іграх ми використовуємо як графік плитки чи іншу геометрію світу, але ви можете використовувати A * для інших речей. Два ур-алгоритми для переходу графіків - це глибина-перший-пошук та широта-перший-пошук. У DFS ви завжди повністю вивчаєте поточну гілку, перш ніж дивитися на братів та сестер поточного вузла, а в BFS ви завжди спочатку дивитесь на братів та сестер, а потім на дітей. * Намагається знайти середину між ними, де ви досліджуєте вниз гілку (так більше схоже на DFS), коли ви наближаєтесь до бажаної мети, але іноді зупиняєтесь і намагаєтеся побратимів, якщо це може мати кращі результати на її гілці. Фактична математика полягає в тому, що ви зберігаєте список можливих вузлів, щоб вивчити наступне, де кожен має "користь" оцінка, що вказує на те, наскільки близька (в якомусь абстрактному сенсі) до мети, нижчі показники кращі (0 означатиме, що ви знайшли мету). Ви вибираєте, що використовувати далі, знаходячи мінімальний бал плюс кількість вузлів від кореня (що, як правило, є поточною конфігурацією або поточною позицією в маршрутизації). Кожен раз, коли ви досліджуєте вузол, ви додаєте до цього списку всіх його дітей, а потім вибираєте новий найкращий.
На абстрактному рівні A * працює так: