Відстеження відвіданих штатів у першому пошуку на широті


10

Тому я намагався реалізувати BFS на головоломці "Розсувні блоки" (тип номера). Тепер головне, що я помітив, це те, що якщо у вас є 4*4рада, кількість станів може бути настільки великим, 16!тому я не можу заздалегідь перерахувати всі стани.

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

Я шукав в мережі і, мабуть, вони не повертаються до щойно завершеного попереднього кроку, АЛЕ ми можемо повернутися до попереднього кроку ще й іншим маршрутом, а потім знову перерахувати всі кроки, які раніше відвідали. Тож як слідкувати за відвідуваними державами, коли всі штати вже не перелічені? (порівняння вже наявних станів із цим кроком буде дорогим).


1
Бічна примітка: я не міг придумати більш підходящий стек для опублікування цього питання. Мені відомо, що деталі щодо впровадження зазвичай не вітаються в цій стеці.
DuttaA

2
imo, це велике питання для SE: AI, оскільки мова йде не лише про впровадження, а про саму концепцію. Не кажучи вже про те, що питання за кілька годин отримало 4 законних відповіді. (Узяв на себе можливість редагувати заголовок для пошуку та створити тег BFS)
DukeZhou

Відповіді:


8

Ви можете використовувати set(в математичному сенсі цього слова, тобто колекцію, яка не може містити дублікатів) для зберігання станів, які ви вже бачили. Операції, які вам знадобляться для виконання, це:

  • вставки елементів
  • тестування, якщо елементи вже є

Практично кожна мова програмування вже повинна мати підтримку структури даних, яка може виконувати обидві ці операції в постійному режимі (O(1)) час. Наприклад:

  • set в Python
  • HashSet на Java

На перший погляд, може здатися, що додавання всіх станів, які ви коли-небудь бачите, до такого набору буде дорогим для пам'яті, але це не так вже й погано в порівнянні з пам'яттю, яка вам вже потрібна для вашого кордону; якщо ваш коефіцієнт розгалуженняb, ваш кордон буде рости b1 елементів на вузол, який ви відвідуєте (видаліть) 1 вузол від кордону, щоб "відвідати" його, додати b нових наступників / дітей), тоді як ваш набір буде лише зростати 1 додатковий вузол на відвідуваний вузол.

У псевдокоді такий набір (назвемо його closed_set, щоб він узгоджувався з псевдокодом у Вікіпедії, міг би використовуватися в пошуку за шириною-першим наступним чином:

frontier = First-In-First-Out Queue
frontier.add(initial_state)

closed_set = set()

while frontier not empty:
    current = frontier.remove_next()

    if current == goal_state:
        return something

    for each child in current.generate_children()
        if child not in closed_set:    // This operation should be supported in O(1) time regardless of closed_set's current size
            frontier.add(child)

    closed_set.add(current)    // this should also run in O(1) time

(деякі варіанти цього псевдокоду можуть також працювати і бути більш-менш ефективними залежно від ситуації; наприклад, ви також можете взяти, closed_setщоб містити всі вузли, до яких ви вже додали дітей до кордону, а потім повністю уникати generate_children()дзвінка якщо currentвже в closed_set.)


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

Будьте уважні : я не знаю жодного формального аналізу цього рішення, який би підтверджував, що він завжди уникає нескінченних циклів, хоча. Якщо я спробую "пропустити" це через голову, інтуїтивно, я підозрюю, що це повинно працювати, і це не потребує додаткової пам'яті. Можливо, є певні випадки, про які я зараз не замислююся, тому це також може просто не працювати, стандартне рішення, описане вище, стане більш безпечною ставкою (ціною більше пам'яті).


1
Я зможу це зробити, але час порівняння почне збільшуватися експоненціально
DuttaA

3
@DuttaA Ні часу порівняння не повинно зростати експоненціально. Ці набори, хеш-набори або все, що вони трапляються, називаються на обраній вами мові, повинні мати можливість перевірити, чи містять вони якийсь станS з постійною обчислювальною складністю O(1), Незалежно від того, скільки елементів вони вже містять . Вони не є списками, вони не перевіряють, чи вони вже містятьSпорівнюючи кожен елемент, який в даний час міститься.
Денніс Сомерс

1
@DuttaA Я додав псевдокод, щоб точно описати, як би використовувався набір, сподіваюся, що це може щось прояснити. Зауважте, що ми ніколи не перебираємо цілий цикл closed_set, його розмір ніколи не повинен впливати на наш (асимптотичний) час обчислення.
Денніс Сомерс

1
Насправді я робив це за допомогою c ++, я не маю уявлення про хешування ...
Зрозуміло,

3
@DuttaA У C ++ ви, мабуть, хочете використовувати std ::
unordered_set

16

Відповідь Денніса Сомерса правильна: ви повинні використовувати HashSet або подібну структуру, щоб відстежувати відвідувані стани в BFS Graph Search.

Однак це не зовсім відповідає на ваше запитання. Ви маєте рацію, що в гіршому випадку BFS вимагатиме від вас зберігати 16! вузли. Незважаючи на те, що час вставки та перевірки в наборі буде O (1), вам все одно знадобиться абсурдна кількість пам'яті.

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

Набагато більш ефективним алгоритмом пам'яті є ітеративне поглиблення . Він має всі бажані властивості BFS, але використовує лише O (n) пам'ять, де n - кількість рухів, щоб досягти найближчого рішення. Це може зайняти деякий час, але ви потрапите на обмеження пам’яті задовго до обмеження процесора.

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


2
так, це більш практично корисна відповідь для людей, які хочуть ефективно вирішити головоломку. Моя відповідь - це для людей, які наполягають на використанні BFS (тому що вони просто хочуть бачити це в дії, або навчитися його реалізовувати, або з будь-якої причини). Зауважте, що сподіватися, що BFS не потрібно буде зберігати16!вузли до речі; це лише найгірший випадок, можливо, вдасться знайти рішення до цього часу.
Денніс Сомерс

@DennisSoemers вірно .. Ви також правильно..Я просто намагався відточити свої навички ... Я перейду до більш просунутих методів пошуку пізніше
DuttaA

чи є випадки, коли BFS може повернути прийнятні локальні рішення? (Я маю справу з більш як 81! * Tbd-значення і ширина-перший здається оптимальним тим, що є блокуючі фактори, які можна легко пропустити без виснаження. Зараз нас не турбує справді сильна гра, скоріше "респектабельно слабка "загальна продуктивність для масиву непередбачуваних топологій
ігрової панелі

2
@DukeZhou BFS зазвичай використовується лише тоді, коли шукається повне рішення. Щоб зупинити його рано, вам потрібна функція, яка оцінює відносну якість різних часткових рішень, але якщо у вас є така функція, ви, ймовірно, можете просто використовувати A * замість цього!
Джон Дукетт

Замість того, щоб говорити "кількість рухів у грі", я б рекомендував "мінімальну кількість рухів, щоб дістатись до мети". Я б подумав про кількість ходів у грі як про кожен хід, який бере вас з одного з 16! констатує будь-який інший, який набагато більше пам'яті, ніж використання ітеративного поглиблення.
NotThatGuy

7

Хоча відповіді, як правило, правдиві, BFS у 15-головоломці не тільки цілком здійсненний, це було зроблено в 2005 році! Документ, що описує підхід, можна знайти тут:

http://www.aaai.org/Papers/AAAI/2005/AAAI05-219.pdf

Кілька ключових моментів:

  • Для цього потрібна була зовнішня пам'ять - тобто BFS використовував жорсткий диск для зберігання замість оперативної пам'яті.
  • Насправді існує лише 15! / 2 станів, оскільки простір станів має дві взаємодоступні компоненти.
  • Це працює в головоломці розсувної плитки, оскільки простір станів дійсно повільно зростає від рівня до рівня. Це означає, що загальна пам'ять, необхідна для будь-якого рівня, набагато менша, ніж повний розмір простору стану. (Це контрастує з простором стану на зразок куба Рубіка, де простір станів зростає набагато швидше.)
  • Оскільки головоломка розсувної плитки непряма, вам потрібно потурбуватися лише про дублікати в поточному або попередньому шарі. У спрямованому просторі ви можете генерувати дублікати в будь-якому попередньому шарі пошуку, що робить речі набагато складнішими.
  • У оригінальній роботі Корфа (зв'язаної вище) вони насправді не зберігали результат пошуку - пошук просто обчислював, скільки штатів було на кожному рівні. Якщо ви хочете зберегти перші результати, вам знадобиться щось на зразок WMBFS ( http://www.cs.du.edu/~sturtevant/papers/bfs_min_write.pdf )
  • Існує три основні підходи до порівняння станів попередніх шарів, коли стани зберігаються на диску.
    • Перший - на основі сортування. Якщо ви сортуєте два файли спадкоємців, ви можете сканувати їх у лінійному порядку, щоб знайти дублікати.
    • Другий - на основі хешу. Якщо ви використовуєте хеш-функцію для групування наступників у файли, ви можете завантажити файли, менші за повний простір стану, щоб перевірити наявність дублікатів. (Зауважте, що тут є дві хеш-функції - одна для надсилання стану у файл, а друга для диференціації станів у цьому файлі.)
    • Третій - структурований дублікат виявлення. Це форма виявлення на основі хешу, але це робиться таким чином, що дублікати можна перевірити негайно, коли вони генеруються, а не після того, як усі вони були створені.

Тут можна сказати набагато більше, але в документах (их), наведених вище, дано набагато більше деталей.


Це чудова відповідь ... але не для ноб, таких як я:) ... Я не такий експерт програміста ..
DuttaA

Як непрямо допоможе вам уникнути дублікатів в інших шарах? Звичайно, ви зможете повернутися до вузла в іншому шарі, перемістивши 3 плитки в колі. Якщо що-небудь, спрямоване допоможе вам уникнути дублікатів, оскільки воно більш обмежувальне. У зв'язаному документі йдеться про виявлення дублікатів, але взагалі не згадується про спрямовану чи спрямовану, і, здається, не згадується про уникнення дублікатів на різних рівнях (але я міг пропустити це під час свого дуже короткого сканування).
NotThatGuy

@NotThatGuy У непрямому графіку батько і дитина перебувають на відстані не більше 1 відстані в глибині, яку вони знаходять у BFS. Це тому, що як тільки один знайдений, непрямий край гарантує, що інший буде знайдений відразу після цього. Але у спрямованому графіку стан на глибині 10 може генерувати дітей на глибині 2, оскільки дитина на глибині 2 не повинен мати край назад до іншого стану (це зробило б його глибиною 3 замість глибини 10) .
Натан С.

@NotThatGuy Якщо ви перемістите 3 плитки по колу, ви створите цикл, але BFS буде досліджувати це в обох напрямках одночасно, тому він насправді не поверне вас на набагато меншу глибину. Повна розсувна плитка 3х2 показана на цій демонстрації, і ви можете відстежувати цикли, щоб побачити, як вони відбуваються: moveai.com/SAS/IDA
Nathan S.

1
дивовижність Ласкаво просимо до SE: AI!
DukeZhou

3

За іронією відповіді є "використовувати будь-яку систему". Хеш-набір - хороша ідея. Однак виявляється, що ваші занепокоєння щодо використання пам’яті безпідставні. BFS настільки поганий у подібних проблемах, що вирішує це питання для вас.

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

Це означає, що, обробляючи останній шар свого BFS, у вас має бути принаймні 16! / 3 станів у пам'яті. Який би підхід ви не використовували, щоб переконатися, що вміщення в пам'яті буде достатнім для того, щоб ваш попередньо відвіданий список вписався і в пам'ять.

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


2

Проблема з 15 головоломками розігрується на дошці 4x4. Реалізація цього у вихідному коді робиться поетапно. Спочатку сам ігровий двигун повинен бути запрограмований. Це дозволяє грати в гру оператором людини. У 15-головоломці є лише один вільний елемент, і над цим елементом виконуються дії. Ігровий движок приймає чотири можливі команди: вліво, вправо, вгору і вниз. Інші дії не дозволені, і керувати грою можна лише за допомогою цих інструкцій.

Наступний шар для гри - це графічний інтерфейс. Це дуже важливо, адже це дозволяє випробувати ігровий движок і спробувати вирішити гру вручну. Також графічний інтерфейс важливий, оскільки нам потрібно розібратися в потенційній евристиці. А зараз ми можемо говорити про сам ШІ. ШІ повинен надсилати команди в ігровий движок (ліворуч, праворуч, вгору та вниз). Наївним підходом до вирішувача був би алгоритм пошуку грубої сили, а це означає, що AI надсилає випадкові команди до досягнення стану мети. Більш просунута ідея - це впровадити якусь базу даних шаблонів, яка зменшує простір стану. Перший пошук по ширині не є безпосередньо евристикою, але це початок. Рівно створити графік для тестування можливих рухів хронологічно.

Відстеження існуючих станів можна виконати за допомогою графіка. Кожен стан - це вузол, має ідентифікатор та ідентифікатор батьків. AI може додавати та видаляти вузли у графіку, а планувальник може вирішити графік для пошуку шляху до мети. З точки зору програмування, ігровий двигун із 15 головоломок є об'єктом, а список багатьох об'єктів - масивом. Вони зберігаються в графі графі. Зрозуміти це у вихідному коді є дещо складним, зазвичай перша пробна версія не вийде, і проект призведе до багатьох помилок. Для управління складністю такий проект зазвичай робиться в академічному проекті, це означає, що це тема для написання документа про нього, який може мати 100 сторінок і більше.


1

Підходи до гри

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

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

Підрядок спуску

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

Функція розумної нерівності (помилки) - це просто сума всіх x нерівностей плюс сума всіх y диспропорцій і число евристично представляє, який з трьох рівнів свободи пересування існує через розміщення відкритого простору (середина, край , куточок).

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

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

Підсумок

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.