C99 - 3x3 плата за 0,084s
Редагувати: я відновив свій код і зробив глибший аналіз результатів.
Подальші зміни: додано обрізку симетріями. Це робить 4 конфігурації алгоритму: з або без симетрії X з або без альфа-бета обрізки
Найдальші редагування: Додано запам'ятовування за допомогою хеш-таблиці, нарешті досягти неможливого: вирішити 3x3 дошку!
Основні характеристики:
- пряма реалізація minimax з альфа-бета обрізкою
- дуже мало управління пам’яттю (підтримує dll дійсних кроків; O (1) оновлення на гілку в дереві пошуку)
- другий файл з обрізкою симетріями. Все ще досягається оновлення O (1) на галузь (технічно O (S), де S - кількість симетрій. Це 7 для квадратних дощок і 3 для неквадратичних дощок)
- третій і четвертий файли додають запам'ятовування. Ви маєте контроль над розміром хештеля (
#define HASHTABLE_BITWIDTH
). Коли цей розмір більший або рівний кількості стін, він гарантує відсутність зіткнень та оновлень O (1). Менші хештелі матимуть більше зіткнень та будуть трохи повільнішими.
- компілювати з
-DDEBUG
для роздруківки
Потенційні поліпшення:
виправити невелику витік пам'яті, виправлену в першій редакції
альфа / бета обрізка додана у другій редакції
симетрії чорносливу додано в 3-й редакції (зауважте, що це не симетрії редакції обробляються запам'ятовуванням, так що залишається окрема оптимізація.)
запам'ятовування додано в 4 редакції
- В даний час для меморіації використовується індикаторний біт для кожної стіни. Дошка 3х4 має 31 стіну, тому цей метод не міг обробляти дошки 4х4 незалежно від часових обмежень. вдосконаленням було б емуляція X-бітових цілих чисел, де X принаймні така велика, як кількість стінок.
Код
Через відсутність організації кількість файлів вийшла з рук. Весь код переміщено до цього сховища Github . У редагуванні запам'ятовування я додав сценарій makefile та тестування.
Результати
Примітки про складність
Підходи грубої сили до крапок і коробок дуже швидко складються .
Розгляньте дошку з R
рядками та C
стовпцями. Є R*C
площі, R*(C+1)
вертикальні стіни та C*(R+1)
горизонтальні стіни. Це загаломW = 2*R*C + R + C
.
Оскільки Лембік попросив нас розв’язати гру з мінімаксом, нам потрібно пройти до листя ігрового дерева. Зараз ігноруємо обрізку, тому що важливо - це порядки.
Є W
варіанти для першого кроку. Для кожного з них наступний гравець може зіграти будь-яку з W-1
інших стін тощо. Це дає нам простір пошуку SS = W * (W-1) * (W-2) * ... * 1
або SS = W!
. Факторіалів величезна кількість, але це лише початок. SS
- кількість вузлів аркушів у просторі пошуку. Більш важливим для нашого аналізу є загальна кількість рішень, які потрібно було прийняти (тобто кількість гілок B
у дереві). Перший шар гілок має W
варіанти. Для кожного з них наступний рівень має W-1
тощо.
B = W + W*(W-1) + W*(W-1)*(W-2) + ... + W!
B = SUM W!/(W-k)!
k=0..W-1
Давайте розглянемо кілька невеликих розмірів таблиці:
Board Size Walls Leaves (SS) Branches (B)
---------------------------------------------------
1x1 04 24 64
1x2 07 5040 13699
2x2 12 479001600 1302061344
2x3 17 355687428096000 966858672404689
Ці цифри стають смішними. Принаймні, вони пояснюють, чому, схоже, брутальний код навічно вішає на дошці 2x3. Простір пошуку дошки 2x3 в 742560 разів більше, ніж 2x2 . Якщо 2x2 потребує 20 секунд, консервативна екстраполяція прогнозує понад 100 днів часу виконання 2x3. Очевидно, що нам потрібно обрізати.
Аналіз обрізки
Я почав з додавання дуже простої обрізки за допомогою алфавіту альфа-бета. В основному, він припиняє пошук, якщо ідеальний противник ніколи не дасть йому своїх сучасних можливостей. "Гляньте, я виграю багато, якщо мій опонент дозволить мені отримати кожен квадрат!", - ніколи не думав, AI.
редагувати Я також додав обрізки на основі симетричних дощок. Я не використовую підхід до запам'ятовування, про всяк випадок, коли-небудь я додаю пам'ятку і хочу, щоб цей аналіз був окремим. Натомість це працює так: більшість ліній мають "симетричну пару" десь ще в сітці. Існує до 7 симетрій (горизонтальна, вертикальна, 180 обертання, 90 обертання, 270 обертання, діагональна та інша діагональна). Усі 7 стосуються квадратних дощок, але останні 4 не стосуються не квадратних дощок. Кожна стіна має вказівник на "пару" для кожної з цих симетрій. Якщо, переходячи в свою чергу, дошка горизонтально симетрична, тоді потрібно грати лише одну з кожної горизонтальної пари .
редагувати редагувати Пам'ятка! Кожна стіна отримує унікальний ідентифікатор, який я зручно встановив як бітний показник; на n-й стіні є ідентифікатор 1 << n
. Тоді хеш дошки - це просто АБО всіх зіграних стін. Це оновлюється в кожній гілці за O (1) час. Розмір хештеля встановлений у #define
. Усі тести проводили з розміром 2 ^ 12, бо чому б ні? Якщо стінок більше, ніж бітів, що індексують хешбел (12 біт у цьому випадку), найменш значущі 12 маскуються та використовуються як індекс. Зіткнення обробляються зв'язаним списком у кожному індексі хештибу. Наступний графік - мій швидкий і брудний аналіз того, як розмір хештету впливає на продуктивність. На комп'ютері з нескінченною оперативною пам’яттю ми завжди встановлювали б розмір таблиці на кількість стінок. Дошка розміром 3х4 матиме хешбіл довжиною 2 ^ 31. На жаль, у нас немає такої розкоші.
Добре, назад до обрізки .. Зупинивши пошук високо в дереві, ми можемо заощадити багато часу, не спускаючись до листя. "Фактор обрізки" - це частина всіх можливих гілок, яку нам довелося відвідати. Груба сила має коефіцієнт обрізки 1. Чим менше, тим краще.