Як я можу змусити A * закінчити швидше, коли пункт призначення не проходить?


31

Я створюю просту 2D гру на основі плиток, яка використовує алгоритм простеження маршруту A * ("Зірка"). У мене все добре працює, але у мене виникають проблеми з продуктивністю пошуку. Простіше кажучи, коли я клацаю непрохідною плиткою, алгоритм, мабуть, проходить всю карту, щоб знайти маршрут до непрохідної плитки - навіть якщо я стою поруч.

Як я можу це обійти? Напевно, я міг би зменшити простеження маршруту до області екрана, але, можливо, тут я пропускаю щось очевидне?


2
Чи знаєте ви, які плитки є непрохідними, чи ви просто знаєте це в результаті алгоритму AStar?
user000user

Як ви зберігаєте свій навігаційний графік?
Анко

Якщо ви зберігаєте пройдені вузли у списках, можливо, ви хочете використовувати двійкові купи для підвищення швидкості.
ChrisC

Якщо це просто занадто повільно, я маю запропонувати низку оптимізацій - чи ти взагалі намагаєшся уникати пошуку?
Стівен

1
Це питання, мабуть, краще відповідатиме інформатиці .
Рафаель

Відповіді:


45

Деякі ідеї щодо уникнення пошукових запитів, які призводять до невдалих шляхів:

Ідентифікатор острова

Один з найдешевших способів швидшого завершення пошуку A * - це взагалі не здійснювати пошуку. Якщо райони справді непрохідні всі агенти, затоплення заповніть кожну область унікальним ідентифікаційним островом навантаження (або в трубопроводі). Під час визначення маршруту перевіряйте, чи відповідає Ідентифікатор острова походження шляху, ідентифікатор острова пункту призначення. Якщо вони не збігаються, немає сенсу проводити пошук - обидві точки знаходяться на чітких, непоєднаних островах. Це допомагає лише за наявності справді непрохідних вузлів для всіх агентів.

Обмежте верхню межу

Я обмежую верхню межу максимальної кількості вузлів, які можна шукати. Це допомагає непрохідним пошукам вестися назавжди, але це означає, що деякі прохідні пошуки, які дуже довго можуть бути втрачені, можуть бути втрачені. Це число потрібно налаштувати, і це насправді не вирішує проблему, але це зменшує витрати, пов’язані з тривалими пошуками.

Якщо ви виявляєте, що це забирає занадто довго, то такі методи корисні:

Зробіть це асинхронним та обмеженням ітерацій

Нехай пошук запускається окремим потоком або трохи кожного кадру, щоб гра не затримувалась в очікуванні пошуку. Відобразити анімацію персонажа, що дряпає голову чи тупотить ногами, або що завгодно, під час очікування завершення пошуку. Щоб зробити це ефективно, я б зберігав Державу пошуку як окремий об'єкт і дозволяв би існувати декілька станів. Коли запитується шлях, візьміть об'єкт вільного стану та додайте його до черги об'єктів активного стану. У своєму оновленні шляху прослідкування витягніть активний елемент з передньої частини черги та запустіть A * до тих пір, поки він не завершить A. або B. не запуститься деяка межа ітерацій. Якщо завершено, поверніть об'єкт стану назад у список вільних об'єктів стану. Якщо він не завершився, поставте його в кінці "активних пошуків" і перейдіть до наступного.

Виберіть правильні структури даних

Переконайтеся, що ви використовуєте правильні структури даних. Ось як працює мій StateObject. Усі мої вузли заздалегідь виділяються на кінцеве число - скажімо, 1024 або 2048 - з міркувань продуктивності. Я використовую пул вузлів, що прискорює розподіл вузлів, і він також дозволяє мені зберігати індекси замість покажчиків у моїх структурах даних, які є u16s (або u8, якщо у мене є 255 max вузлів, що я роблю в деяких іграх). Для мого пошуку маршрутів я використовую чергу пріоритетів для відкритого списку, зберігаючи покажчики на об’єкти Node. Він реалізований у вигляді двійкової купи, і я сортую значення плаваючої точки як цілі числа, оскільки вони завжди позитивні, і моя платформа має повільну порівняння з плаваючою точкою. Я використовую хештел для закритої карти, щоб відстежувати вузли, які я відвідав. Він зберігає NodeID, а не Nodes, щоб економити на розмірах кешу.

Кешуйте, що можете

Коли ви вперше відвідуєте вузол і обчислюєте відстань до пункту призначення, кешуйте його у вузлі, що зберігається в об'єкті стану. Якщо ви переглянете вузол, використовуйте кешований результат, а не обчислюючи його ще раз. У моєму випадку це допомагає не робити квадратний корінь на переглянутих вузлах. Ви можете виявити, що є інші значення, які ви можете попередньо обчислити та кешувати.

Подальші області, які ви могли б дослідити: використовуйте двосторонній пошук маршрутів для пошуку з будь-якого кінця. Я цього не робив, але, як інші зауважили, це може допомогти, але не обійтися. Інша річ у моєму списку, яку слід спробувати, - це ієрархічне наведення маршрутів або пошук кластеризації шляху. У документації HavokAI є цікавий опис Тут описується їх концепція кластеризації, яка відрізняється від описаних тут реалізацій HPA * .

Удачі, і повідомте нам, що ви знайдете.


Якщо є різні агенти з різними правилами, але їх не дуже багато, це все одно можна узагальнити досить ефективно, використовуючи вектор ідентифікаторів, один на клас агента.
MSalters

4
+1 Для визнання того, що ця проблема, ймовірно, обмежена зонами (не просто непрохідними плитками), і що подібну проблему можна вирішити простіше за допомогою раннього розрахунку часу завантаження.
Сліпп Д. Томпсон

Заливайте або заливаєте кожну ділянку.
вовчий світанок

Ідентифікатори острова не повинні бути статичними. Існує простий алгоритм, який був би придатний у випадку, якщо є необхідність мати можливість з'єднати два окремі острови, але він не може потім розділити острів. Сторінки 8 до 20 на цих слайдах пояснюють саме цей алгоритм: cs.columbia.edu/~bert/courses/3137/Lecture22.pdf
kasperd

@kasperd звичайно, ніщо не заважає перерахунку, злиття під час виконання острівних ідентифікаторів. Справа в тому, що острівні ідентифікатори дозволяють вам підтвердити, чи існує шлях між двома вузлами швидко, не здійснюючи пошук astar.
Стівен

26

AStar - це повний алгоритм планування, тобто якщо існує шлях до вузла, AStar гарантовано знайде його. Отже, він повинен перевірити кожен шлях із стартового вузла, перш ніж він зможе визначити, що вузол цілі недоступний. Це дуже небажано, коли у вас занадто багато вузлів.

Способи пом'якшити це:

  • Якщо ви апріорі знаєте, що вузол недоступний (наприклад, у нього немає сусідів або він позначений UnPassable), повертайтеся, No Pathне викликаючи жодного разу AStar.

  • Обмежте кількість вузлів, що AStar буде розширюватися до завершення. Перевірте відкритий комплект. Якщо він коли-небудь стане занадто великим, припиніть і поверніться No Path. Однак це обмежить повноту Астара; тому він може планувати лише шляхи максимальної довжини.

  • Обмежте час, який потребує AStar, щоб знайти шлях. Якщо часу не вистачає, вийдіть і поверніться No Path. Це обмежує повноту, як і попередня стратегія, але масштабує зі швидкістю комп'ютера. Зауважте, що для багатьох ігор це небажано, оскільки гравці з більш швидкими або повільними комп'ютерами будуть грати по-різному.


13
Я хотів би зазначити, що зміна механіки вашої гри залежно від швидкості процесора (так, пошук маршруту - це ігровий механік) може виявитися поганою ідеєю, оскільки це може зробити гру досить непередбачуваною, а в деяких випадках навіть непрограмою на комп’ютерах 10 років відтепер. Тому я б скоріше рекомендував обмежувати A * обмеженням відкритого набору, ніж часом процесора.
Філіп

@Philipp. Модифікував відповідь, щоб відобразити це.
mklingen

1
Зауважте, що ви можете визначити (досить ефективно, O (вузли)) для даного графіка максимальну відстань між двома вузлами. Це найдовша проблема шляху , і вона забезпечує правильну верхню межу для кількості вузлів, які потрібно перевірити.
MSalters

2
@MSalters Як це зробити в O (n)? А що "розумно ефективно"? Якщо це лише для пар вузлів, ви не просто дублюєте роботу?
Стівен

Згідно з Вікіпедією, на жаль, проблема, яка триває найдовше, - це важкий NP.
Десті

21
  1. Запустити подвійний пошук A * з цільового вузла в зворотному порядку, а також в той же час у тому ж циклі і скасувати обидва пошуку, як тільки один виявиться нерозв’язним

Якщо в цілі є лише 6 плиток, доступних навколо неї, а джерело має 1002 плитки, доступ зупиниться на 6 (подвійних) ітераціях.

Як тільки один пошук знайде вузли, які відвідав інший, ви також можете обмежити область пошуку до відвідуваних інших вузлів і закінчити швидше.


2
Для впровадження двонаправленого пошуку зірки A є більше, ніж мається на увазі у вашій заяві, включаючи підтвердження того, що евристика залишається допустимою за цієї обставини. (Посилання: homepages.dcc.ufmg.br/~chaimo/public/ENIA11.pdf )
Pieter Geerkens

4
@StephaneHockenhull: Реалізувавши двонаправлену A- * на карті місцевості з асиметричними витратами, запевняю вас, що ігнорування академічного бла-бла призведе до неправильного вибору шляху та неправильних розрахунків вартості.
Пітер Геркенс

1
@MooingDuck: Загальна кількість вузлів не змінюється, і кожен вузол все одно буде відвідуватися лише один раз, тому найгірший випадок розділення карти точно навпіл ідентичний однонаправленому A- *.
Пітер Геркенс

1
@PieterGeerkens: У класичному A * лише половина вузлів доступна і таким чином відвідується. Якщо карта розділена рівно навпіл, тоді, коли ви шукаєте двосторонній пошук, ви торкаєтесь (майже) кожного вузла. Безумовно,
крайній

1
@MooingDuck: я неправильно говорив; найгірші випадки - це різні графіки, але мають однакову поведінку - гірший випадок для однонаправленості - це повністю ізольований вузол цілі, який вимагає відвідування всіх вузлів.
Пітер Ґеркенс

12

Якщо припустити, що місце призначення недоступне. І навігаційна сітка не динамічна. Найпростіший спосіб зробити це набагато більш рідкісний графік навігації (достатньо розріджений, щоб повний пробіг був відносно швидким) і використовувати детальний графік лише в тому випадку, якщо шлях можливий.


6
Це добре. Згрупувавши плитки в "регіони" та спершу перевіривши, чи область, у якій знаходиться ваша плитка, може бути підключена до регіону, в якому знаходиться інша плитка, ви можете викинути негативи набагато швидше.
Конерак

2
Правильно - як правило, підпадає під HPA *
Стівен

@Steven Спасибі Я був впевнений, що я не перший, хто задумався про такий підхід, але не знав, як це називається. Набагато простіше скористатися перевагами існуючих досліджень.
ClassicThunder

3

Використовуйте кілька алгоритмів з різними характеристиками

A * має деякі тонкі характеристики. Зокрема, він завжди знаходить найкоротший шлях, якщо такий існує. На жаль, ви знайшли і деякі погані характеристики. У цьому випадку він повинен вичерпно шукати всі можливі шляхи, перш ніж визнати, що рішення не існує.

"Недолік", який ви виявляєте в A *, полягає в тому, що він не знає топології. У вас може бути двовимірний світ, але він цього не знає. Наскільки це відомо, у дальньому куточку вашого світу є сходи, які приводять його прямо під світ до місця призначення.

Розглянемо створення другого алгоритму, який усвідомлює топологію. Як перший пропуск, ви можете заповнити світ «вузлами» кожні 10 або 100 пробілів, а потім підтримувати графік зв’язку між цими вузлами. Цей алгоритм мав би знайти шлях, знайшовши доступні вузли поблизу початку та кінця, а потім намагаючись знайти шлях між ними на графіку, якщо такий існує.

Один з простих способів зробити це - призначити кожну плитку вузлу. Доцільно показати, що вам потрібно призначити по одному вузлу кожній плитці (ви ніколи не можете мати доступ до двох вузлів, які не пов'язані у графіку). Тоді краю графіка визначаються так, щоб були скрізь дві плитки з різними вузлами.

Цей графік має недолік: він не знаходить оптимального шляху. Він просто знаходить в шлях. Однак зараз ви показали, що A * може знайти оптимальний шлях.

Він також пропонує евристику для покращення ваших недооцінок, необхідних для функціонування A *, тому що тепер ви більше знаєте про свій пейзаж. Вам рідше доведеться повністю вивчити глухий кут, перш ніж з’ясувати, що вам потрібно відступити, щоб піти вперед.


У мене є підстави вважати, що такі алгоритми, як алгоритми для Карт Google, працюють аналогічно (хоча і більш вдосконалено).
Корт Аммон - Відновіть Моніку

Неправильно. A * дуже добре знає топологію через вибір допустимої евристики.
MSalters

Щодо Google, на моїй попередній роботі ми проаналізували ефективність Google Maps і виявили, що це не могло бути A *. Ми вважаємо, що вони використовують ArcFlags або інші подібні алгоритми, які залежать від попередньої обробки карт.
MSalters

@MSalters: Це цікава грань. Я стверджую, що A * не знає топології, оскільки стосується лише найближчих сусідів. Я заперечую, що справедливіше сказати, що алгоритм, що генерує допустиму евристику, усвідомлює топологію, а не сам А *. Розглянемо випадок, коли є алмаз. A * трохи проходить один шлях, перш ніж створити резервну копію, щоб спробувати іншу сторону алмазу. Немає можливості сповістити A * про те, що єдиний "вихід" з цієї гілки - це вже відвідуваний вузол (економія обчислень) з евристикою.
Корт Аммон - Відновіть Моніку

1
Не можу говорити на Картах Google, але Bing Map використовує паралельну двонаправлену зірку з орієнтирами та нерівністю трикутника (ALT) з попередньо обчисленими відстанями від (і до) невеликої кількості орієнтирів і кожного вузла.
Пітер Геркенс

2

Ще кілька ідей на додаток до вищезазначених відповідей:

  1. Результати кешування пошуку A *. Збережіть дані про шлях від клітини A до комірки B і, якщо можливо, повторно використовуйте. Це більше стосується статичних карт, і вам доведеться більше працювати з динамічними картами.

  2. Кешуйте сусідів кожної комірки. * Реалізація повинна розширити кожен вузол і додати його сусідів до відкритого набору для пошуку. Якщо цей сусід обчислюється щоразу, а не кешований, це може різко уповільнити пошук. Якщо ви вже зробили це, використовуйте чергу пріоритетів для A *.


1

Якщо ваша карта статична, ви можете просто мати кожен окремий розділ там власним кодом і перевірити це перш, ніж запустити A *. Це можна зробити при створенні карти або навіть закодувати на карті.

На непрохідних плитках повинен бути прапор, і при переміщенні до такої плитки ви можете вирішити не запускати A * або вибрати плитку поруч із нею, яка доступна.

Якщо у вас є динамічні карти, які часто змінюються, вам майже не пощастило. Вам потрібно зробити так, щоб ваше рішення зупинило алгоритм перед його завершенням або робити перевірки на секції часто закриваються.


Це саме те, що я пропонував у своїй відповіді ідентифікатор області.
Стівен

Ви також можете зменшити кількість використовуваного процесора / часу, якщо карта динамічна, але не змінюється часто. Тобто ви могли повторно обчислити ідентифікатори площі кожного разу, коли зачинені двері відчинені або заблоковані. Оскільки це зазвичай відбувається у відповідь на дії гравця, ви принаймні виключаєте заблоковані ділянки підземелля.
uliwitness

1

Як я можу змусити A * швидше зробити висновок, що вузол є непрохідним?

Профілюйте свою Node.IsPassable()функцію, з’ясуйте найповільніші частини, пришвидшіть їх.

Вирішуючи, чи є вузол прохідним, поставте найбільш вірогідні ситуації вгорі, щоб більшу частину часу функція поверталася відразу, не турбуючись перевіряти більш незрозумілі можливості.

Але це для того, щоб швидше перевірити один вузол. Ви можете профайлювати, щоб побачити, скільки часу витрачається на запитання вузлів, але звучить, що проблема полягає в тому, що перевіряється занадто багато вузлів.

коли я натискаю непрохідну плитку, алгоритм, мабуть, проходить всю карту, щоб знайти маршрут до непрохідної плитки

Якщо плитка призначення сама по собі є непрохідною, алгоритм взагалі не повинен перевіряти жодну плитку. Перш ніж навіть почати виконувати пошук маршрутів, слід запитати плитку призначення, щоб перевірити, чи це можливо, а якщо ні, повернути результат без шляху.

Якщо ви маєте на увазі, що місце призначення є прохідним, але оточене непрохідними плитками, таким чином, що шляху немає, тоді A * нормально перевіряти всю карту. Як ще можна було б знати, що шляху немає?

У останньому випадку ви можете прискорити його, зробивши двосторонній пошук - таким чином пошук, починаючи з пункту призначення, може швидко виявити, що шляху немає, і зупинити пошук. Дивіться цей приклад , оточіть місце призначення стінами та порівняйте двонаправлене проти одинарного напрямку.


0

Зробіть пошук шляху назад.

Якщо тільки на вашій карті немає великих безперервних ділянок недосяжних плиток, це спрацює. Замість того, щоб шукати всю карту, яка доступна, пошук шляху буде шукати лише закриту недоступну область.


Це ще повільніше, якщо недосяжні плитки перевищують
доступну

1
@MooingDuck Ви маєте на увазі підключені недоступні плитки. Це рішення, яке працює практично з будь-яким розумним дизайном карти, і це дуже просто здійснити. Я не збираюся пропонувати щось більш фантазійне без кращого знання точної проблеми, наприклад, як реалізація A * може бути настільки повільною, що відвідування всіх плиток насправді є проблемою.
aaaaaaaaaaaa

0

Якщо області, до яких плеєр підключений (відсутні телепорти тощо), і недоступні області, як правило, не дуже добре з’єднані, ви можете просто зробити A *, починаючи з вузла, до якого ви хочете досягти. Таким чином, ви все ще можете знайти будь-який можливий маршрут до пункту призначення, і A * перестане швидко шукати недоступні райони.


Справа повинна була бути швидшою, ніж звичайна A *.
Геккель

0

коли я натискаю непрохідну плитку , алгоритм, мабуть, проходить всю карту, щоб знайти маршрут до непрохідної плитки - навіть якщо я стою поруч.

Інші відповіді чудові, але я маю зазначити очевидне - Ви взагалі не повинні запускати простеження до непрохідної плитки.

Це має бути ранній вихід з альго:

if not IsPassable(A) or not IsPasable(B) then
    return('NoWayExists');

0

Щоб перевірити найбільшу відстань у графі між двома вузлами:

(якщо всі краї мають однакову вагу)

  1. Запустити BFS з будь-якої вершини v .
  2. За допомогою результатів виберіть вершину, віддалену від vнас, ми її назвемо d.
  3. Запустити BFS від u.
  4. Знайдіть вершину, віддалену від uнас, ми її назвемо w.
  5. Відстань між uта wє найдовшою відстані в графі.

Доказ:

                D1                            D2
(v)---------------------------r_1-----------------------------(u)
                               |
                            R  | (note it might be that r1=r2)
                D3             |              D4
(x)---------------------------r_2-----------------------------(y)
  • Скажімо, відстань між yі xбільша!
  • Тоді відповідно до цього D2 + R < D3
  • Потім D2 < R + D3
  • Тоді відстань між vі і xбільше, ніж у vта u?
  • Тоді uб не вибирали в першій фазі.

Заслуга проф. Шломі Рубінштейн

Якщо ви використовуєте зважені ребра, ви можете виконати те ж саме за поліноміальний час, запустивши Dijkstra замість BFS, щоб знайти найдальшу вершину.

Зверніть увагу, я припускаю, що це зв'язаний графік. Я також припускаю, що це непрямо.


A * не дуже корисний для простої гри на 2d плитці, тому що якщо я правильно зрозумів, якщо припускати, що істоти рухаються в 4 напрямках, BFS досягне тих же результатів. Навіть якщо істоти можуть рухатись у 8 напрямках, лінивий BFS, який віддає перевагу вузлам ближче до цілі, все одно досягне тих же результатів. A * - це модифікація Dijkstra, яка набагато дорожче обчислювальна, ніж використання BFS.

BFS = O (| V |) нібито O (| V | + | E |), але насправді не у випадку з картою зверху вниз. A * = O (| V | log | V |)

Якщо у нас є карта із всього 32 х 32 плитки, BFS коштуватиме максимум 1024, а справжній A * може коштувати вам цілих 10 000. Це різниця між 0,5 секундами і 5 секундами, можливо більше, якщо ви враховуєте кеш. Тому переконайтеся, що ваш A * поводиться як ледачий BFS, який віддає перевагу плитці, яка наближається до потрібної цілі.

* * Корисно для навігаційних карт, якщо вартість країв важлива в процесі прийняття рішення. У простих накладних іграх на основі плитки, вартість країв, мабуть, не є важливою увагою. Якщо вона є, (різні плитки коштують по-різному), ви можете запустити модифіковану версію BFS, яка відкладає та карає шляхи, що проходять через плитки, що сповільнюють характер.

Так, так, BFS> A * у багатьох випадках, коли справа стосується плитки.


Я не впевнений, що я розумію цю частину "Якщо у нас є карта із всього 32 х 32 плитки, BFS коштуватиме максимум 1024, а справжній A * може коштувати вам колосальних 10000" Чи можете ви пояснити, як ви прийшли до 10k номер будь ласка?
Кромстер каже, що підтримую Моніку

Що саме ви маєте на увазі під "ледачим BFS, який віддає перевагу вузлам ближче до цілі"? Ви маєте на увазі Dijkstra, звичайний BFS або той, хто має евристику (добре, ви тут відтворили A *, або як обрати наступний найкращий вузол із відкритого набору)? Це log|V|в складності A * дійсно пов'язане з підтриманням цього відкритого набору або розміром бахроми, а для сіткових карт він надзвичайно малий - про журнал (sqrt (| V |)) з використанням вашої позначення. Журнал | V | відображається лише у гіперзв'язаних графіках. Це приклад, коли наївне застосування найгіршої складності дає неправильний висновок.
congusbongus

@congusbongus Це саме те, що я маю на увазі. Не використовуйте
ванільну

@KromStern Припустимо, що ви використовуєте ванільну реалізацію A * для гри на основі плитки, ви отримуєте V * logV складність, V - кількість плиток, для сітки 32 на 32 - це 1024. logV, що є приблизно приблизно кількістю біт потрібно представити 1024, що становить 10. Отже, ви закінчуєтесь працювати довше часу. Звичайно, якщо ви спеціалізуєтесь на впровадженні, щоб скористатися тим, що ви працюєте на сітці плиток, ви подолаєте це обмеження, саме таке я і мав на увазі
вовчий світанок
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.