Гра в п'ятнашки (15-puzzle): існування розв'язку
Ця гра відбувається на дошці розміром . На дошці розташовано фішок, пронумерованих від 1 до 15. Одна клітинка лишається порожньою (позначається нулем 0). Потрібно привести дошку до показаного нижче положення, послідовно переміщуючи одну з фішок на вільне місце:
Гру «15 Puzzle» створив Noyes Chapman у 1880 році.
- Чи потрібно лише перевірити існування розв'язку, а не знайти послідовність ходів?
- Чи зводиться допустимий хід до переміщення порожньої клітинки між сусідами (тобто чи інваріант — парність перестановки + рядок порожньої клітинки)?
- Чи можна закодувати позицію як перестановку й порахувати кількість інверсій?
Існування розв'язку
Розглянемо таку задачу: задано положення на дошці, потрібно визначити, чи існує послідовність ходів, яка приводить до розв'язку.
Припустимо, що ми маємо деяке положення на дошці:
де один з елементів дорівнює нулю та позначає порожню клітинку
Розглянемо перестановку:
тобто перестановку чисел, що відповідає положенню на дошці без нульового елемента
Нехай — кількість інверсій у цій перестановці (тобто кількість таких елементів та , що , але ).
Нехай — індекс рядка, у якому розташований порожній елемент (тобто, за нашою домовленістю, ).
Тоді розв'язок існує тоді й лише тоді, коли парне.
Реалізація
Описаний вище алгоритм можна проілюструвати таким програмним кодом:
- C++
- Python
- TypeScript
- Go
int a[16];
for (int i=0; i<16; ++i)
cin >> a[i];
int inv = 0;
for (int i=0; i<16; ++i)
if (a[i])
for (int j=0; j<i; ++j)
if (a[j] > a[i])
++inv;
for (int i=0; i<16; ++i)
if (a[i] == 0)
inv += 1 + i / 4;
puts ((inv & 1) ? "No Solution" : "Solution Exists");
a = [int(x) for x in input().split()] # 16 чисел через пробіл
inv = 0
for i in range(16):
if a[i]:
for j in range(i):
if a[j] > a[i]:
inv += 1
for i in range(16):
if a[i] == 0:
inv += 1 + i // 4
print("No Solution" if inv & 1 else "Solution Exists")
// a — масив із 16 чисел (зчитаних зі вводу)
let inv = 0;
for (let i = 0; i < 16; ++i)
if (a[i])
for (let j = 0; j < i; ++j)
if (a[j] > a[i])
++inv;
for (let i = 0; i < 16; ++i)
if (a[i] === 0)
inv += 1 + Math.floor(i / 4);
console.log(inv & 1 ? "No Solution" : "Solution Exists");
// a — масив із 16 чисел (зчитаних зі вводу)
inv := 0
for i := 0; i < 16; i++ {
if a[i] != 0 {
for j := 0; j < i; j++ {
if a[j] > a[i] {
inv++
}
}
}
}
for i := 0; i < 16; i++ {
if a[i] == 0 {
inv += 1 + i/4
}
}
if inv&1 != 0 {
fmt.Println("No Solution")
} else {
fmt.Println("Solution Exists")
}
Доведення
У 1879 році Johnson довів, що якщо непарне, то розв'язку не існує, а того ж року Story довів, що всі положення, для яких парне, мають розв'язок.
Однак усі ці доведення були доволі складними.
У 1999 році Archer запропонував значно простіше доведення (його статтю можна завантажити тут).