Решение проблемы N ферзей Как далеко мы можем зайти?

Проблема N-ферзей:

В этой задаче говорится, что для шахматной доски размером N на N найдите различные перестановки, в которых N ферзей могут быть размещены на доске, и никто не будет угрожать друг другу.

Мой вопрос:
Какое максимальное значение N, для которого программа может вычислить ответ за разумное время? Или какое наибольшее число N мы видели до сих пор?

Вот моя программа на CLPFD (Пролог):

generate([],_).
generate([H|T],N) :-
   H in 1..N ,
   generate(T,N).

lenlist(L,N) :-
   lenlist(L,0,N).

lenlist([],N,N).
lenlist([_|T],P,N) :-
   P1 is P+1,
   lenlist(T,P1,N).

queens(N,L) :-
   generate(L,N),lenlist(L,N),
   safe(L),
   !,
   labeling([ffc],L).

notattack(X,Xs) :-
   notattack(X,Xs,1).

notattack(X,[],N).
notattack(X,[Y|Ys],N) :-
   X #\= Y,
   X #\= Y - N,
   X #\= Y + N,
   N1 is N + 1,
   notattack(X,Ys,N1).

safe([]).
safe([F|T]) :-
   notattack(F,T),
   safe(T).

Эта программа работает отлично, но время, которое она требует, продолжает увеличиваться с N. Вот пример выполнения:

?- queens(4,L).

L = [2, 4, 1, 3] ;

L = [3, 1, 4, 2] ;

No

Это означает, что вы размещаете 4 ферзя в строке 2 в столбце 1, строке 4 в столбце 2, строке 1 в столбце 3 и строке 3 в 4. (на шахматной доске 4 на 4).

Теперь давайте посмотрим, как работает эта программа (время, затраченное на вычисление первой перестановки):
Для N = 4,5 ..... 10 вычислений за секунду
Для N = 11-30 Занимает от -1- 3 секунды
Для N = 40..50 Все еще вычисляется в течение минуты
При N = 60 Он выходит из глобального стека (пространство поиска огромно).

Это была проблема с домашним заданием в прошлом. (Первоначальная проблема заключалась в том, чтобы просто закодировать N-Queens)

Мне также интересно увидеть альтернативные реализации на других языках (которые работают лучше, чем моя реализация) или есть ли возможности для улучшения моего алгоритма / программы


person Community    schedule 07.12.2009    source источник
comment
В Википедии есть множество алгоритмов, и указывается, что по крайней мере один из них может решить проблему 1000000 ферзей за ~ 50 шагов. en.wikipedia.org/wiki/   -  person Carl Norum    schedule 08.12.2009
comment
Я очень мало работал с Prolog, и очень давно. Но я напоминаю, что, хотя Prolog является предписывающим (?), А не императивным языком, можно упорядочить и иным образом настроить правила таким образом, чтобы подтолкнуть обработку в предпочтительном и, надеюсь, более эффективном направлении. Я не готов к этому, но думаю, ваше решение можно было бы оптимизировать таким образом, чтобы добиться гораздо большей производительности.   -  person Carl Smotricz    schedule 08.12.2009
comment
Думаю, моя программа работает аналогично анимации, показанной в Википедии. Он размещает одного ферзя, а затем наносит удары по позициям, которые эта ферзь убивает, и так далее. Но проблема с 1 миллионом королев менее чем за 50 шагов - это безумие. Я думаю, что эта статья в Википедии не совсем правильная. Даже с использованием ограничений, я не думаю, что это можно решить за 50 шагов.   -  person    schedule 08.12.2009
comment
Также это может быть тривиально ... Но я мог бы использовать встроенную функцию length / 2, чтобы уменьшить размер кода. Но все равно производительность была бы такой же.   -  person    schedule 08.12.2009
comment
Что ж, реализация из 50 шагов не выполняет полный поиск в глубину, как ваш алгоритм. Если вы прочтете статью, то получите дополнительную информацию. В частности, для этого требуется «достаточно хорошая» начальная настройка.   -  person Carl Norum    schedule 08.12.2009
comment
@Carl Smotricz Моя программа эффективно использует Contraints. Это не слепое вычисление всех перестановок и их проверка. Он использует ограничения, чтобы разумно находить возможные будущие перестановки. То, что вы говорите, правда ... Это можно точно настроить, сделав разрезы ... Это не приведет к значительному повышению производительности.   -  person    schedule 08.12.2009
comment
@ Карл Норум .. Ну да, ты прав. Но этот алгоритм предполагает, что у вас хорошая начальная конфигурация, плюс он не гарантирует решения, поскольку может застрять в локальном минимуме. В любом случае это хорошая альтернатива.   -  person    schedule 08.12.2009
comment
math.utah.edu/~alfeld/queens/queens.html показывает реальную сложность, с которой мы имеем дело. Проверьте количество шагов по мере увеличения N   -  person    schedule 08.12.2009
comment
Вы также можете использовать length/2. Я не могу комментировать CLPFD, но считаю, что встроенные модули обычно реализуются в сильно оптимизированном коде нижнего уровня, а не в самом прологе. Таким образом, вы также можете уменьшить размер кода и воспользоваться возможными оптимизациями, которые, возможно, уже были сделаны для вас.   -  person nedned    schedule 08.12.2009
comment
Срез неуместен, также факт в generate/2 неверен.   -  person false    schedule 22.07.2015
comment
Хотя это старая ветка, она очень интересна. Было бы хорошо, если бы можно было описать, как работает этот код, желательно, добавив к нему комментарии.   -  person rnso    schedule 11.09.2017


Ответы (9)


короткое решение, представленное Раймондом Хеттингером на pycon: easy ai in python

#!/usr/bin/env python
from itertools import permutations
n = 12
cols = range(n)
for vec in permutations(cols):
    if (n == len(set(vec[i] + i for i in cols))
          == len(set(vec[i] - i for i in cols))):
        print vec

вычисление всех перестановок не масштабируется, хотя (O(n!))

person miku    schedule 07.12.2009
comment
Удивительно коротко! Есть какие-нибудь сведения о сроках исполнения? - person Carl Smotricz; 08.12.2009
comment
Я не знаю Python, но похоже, что вы используете стратегию генерации и тестирования. В котором вы просто генерируете перестановку и проверяете, удовлетворяет ли она ограничениям. Но загвоздка в том, что даже для малых N, таких как N = 60, количество перестановок равно 60! .. Это очень большое число. - person ; 08.12.2009
comment
да, это решение не столько о масштабируемости, сколько о решении классической задачи 8x8 в несколько строк .. - person miku; 08.12.2009
comment
Но вы можете использовать ограничения, чтобы уменьшить пространство для поиска. Например, если вы размещаете ферзя, вычеркните позиции, которые эта ферзь убьет, а затем аналогичным образом разместите других ферзей. (Как то, что я сделал в своей программе). В любом случае на удивление короткая реализация :) - person ; 08.12.2009
comment
Причина, по которой я выбрал это в качестве ответа, близка к тому, что я спросил. Хотя все же я очень хочу увидеть более качественные реализации. - person ; 14.12.2009
comment
Он удивительно короткий, потому что включает в себя класс Permutations. Я мог бы написать еще короче, в котором было бы просто: королевы печати (12). Вот это было бы потрясающе;) - person Milan Babuškov; 10.11.2010

Это обсуждение объединяет три различные вычислительные задачи: (1) поиск решения проблемы N ферзей, (2) перечисление всех решений для некоторого фиксированного N и (3) подсчет всех решений для некоторого фиксированного N. Первая задача выглядит сложной. сначала для размера платы, такого как N = 8. Однако, как предлагает Википедия, в некоторых отношениях это просто, когда N большое. Ферзи на большой доске не так уж много общаются. За исключением ограничений памяти, алгоритм эвристического восстановления становится все проще и легче при увеличении N.

Перечислять каждое решение - другое дело. Вероятно, это можно сделать с помощью хорошего кода динамического программирования, размер которого будет достаточно большим, чтобы не было смысла читать вывод.

Самый интересный вариант вопроса - подсчитать решения. Современное состояние дел кратко изложено в великолепном справочнике, известном как Энциклопедия целочисленных последовательностей. Рассчитано до N = 26. Я предполагаю, что здесь также используется динамическое программирование, но, в отличие от случая перечисления каждого решения, алгоритмическая проблема намного глубже и открыта для дальнейшего развития.

person Greg Kuperberg    schedule 08.12.2009

Лорен Пехтель сказала: «А теперь настоящее безумие: 29 потребовалось 9 секунд. 30 заняло почти 6 минут!»

Это поразительное отсутствие предсказуемости сложности отката для разных размеров досок было той частью головоломки, которая больше всего интересовала меня. В течение многих лет я составлял список «счетчиков» шагов алгоритма, необходимых для поиска первого решения для каждого размера платы, используя простой и хорошо известный алгоритм в глубину в рекурсивном C ++. функция.

Вот список всех этих «счетчиков» для досок до N = 49 ... минус N = 46 и N = 48, которые все еще находятся в стадии разработки:

http://queens.cspea.co.uk/csp-q-allplaced.html

(У меня это указано в Энциклопедии целочисленных последовательностей (OEIS) как A140450)

Эта страница содержит ссылку на список подходящих первых решений.

(Мой список Первые решения - это порядковый номер OEIS A141843)

Я не записываю в первую очередь, сколько времени обработки требует каждое решение, а скорее записываю, сколько неудачных размещений ферзя потребовалось до обнаружения алгоритмически первого решения каждой платы. Конечно, скорость размещения ферзя зависит от производительности ЦП, но, учитывая быстрый тестовый прогон на конкретном ЦП и определенном размере платы, легко подсчитать, сколько времени потребовалось для решения одного из этих «найденных» решений.

Например, на процессоре Intel Pentium D 3,4 ГГц с использованием одного потока процессора -

  • При N = 35 моя программа «размещала» 24 миллиона ферзей в секунду и потребовала всего 6 минут, чтобы найти первое решение.
  • При N = 47 моя программа «размещала» 20,5 миллионов маток в секунду и занимала 199 дней.

Мой нынешний i7-860 с тактовой частотой 2,8 ГГц пробивает около 28,6 миллионов ферзей в секунду, пытаясь найти первое решение для N = 48. Пока что потребовалось более 550 дней (теоретически, если бы он никогда не был непрерывным), чтобы безуспешно разместить 1 369 331 731 000 000 (и быстро растущих) ферзей.

На моем веб-сайте (пока) нет кода C ++, но я даю ссылку на этой веб-странице на мою простую иллюстрацию каждого из 15 шагов алгоритма, необходимых для решения доски N = 5.

Это действительно восхитительная головоломка!

person CSPea    schedule 21.08.2010
comment
Возможно, вас заинтересует исследование почти идеальной эвристики для N-королев? link.springer.com/article/10.1007%2Fs10898-011-9653- x - person chesslover; 28.08.2014
comment
Вы говорите о поиске ОДНОЙ конфигурации или подсчете всех возможных конфигураций? - person Beni Bogosel; 30.03.2017
comment
Я разработал код, чтобы получить ПЕРВОЕ решение для сколь угодно большого количества королев! - person abhimanyue; 23.05.2021

Какую систему Prolog вы используете? Например, в последних версиях SWI-Prolog вы можете легко найти решения для N = 80 и N = 100 за доли секунды, используя исходный код. Многие другие системы Prolog будут намного быстрее.

Проблема N-королев даже фигурирует в одном из онлайн-примеров SWI-Prolog, доступном как Королевы CLP (FD) на шведском языке.

Пример с 100 ферзями:

?- time((n_queens(100, Qs), labeling([ff], Qs))).
Qs = [1, 3, 5, 57, 59 | ...] .
2,984,158 inferences, 0.299 CPU in 0.299 seconds (100% CPU, 9964202 Lips)

SWISH также показывает вам красивые изображения решений.

Вот анимированный GIF, показывающий полный процесс решения для N = 40 ферзей с помощью SWI-Prolog:

Процесс решения CLP (FD) для 40 маток

person mat    schedule 14.01.2011
comment
... Полный процесс решения? Я думаю, это показывает только процесс маркировки для первого решения - person false; 31.03.2016

Что касается наибольшего N, решаемого компьютерами, в литературе есть ссылки, в которых решение для N около 3 * 10 ^ 6 было найдено с использованием алгоритма устранения конфликтов (то есть локального поиска). См., Например, классическую статью [Сосича и Гу].

Что касается точного решения с возвратом, существует несколько умных эвристик ветвления, которые позволяют получить правильные конфигурации практически без возврата. Эту эвристику также можно использовать для поиска решений проблемы first-k: после нахождения исходной правильной конфигурации поиск возвращается назад, чтобы найти другие допустимые конфигурации поблизости.

Ссылки для этой почти идеальной эвристики: [Kale 90 ] и [San Segundo 2011]

person chesslover    schedule 31.07.2014
comment
Не на 100% без возврата, но почти. Например, в [San Segundo 2011] требовалось 0 откатов, чтобы найти первую допустимую конфигурацию для 992 случаев из 997 возможных значений N от 4 до 1000. Для N = 15 требовалось 6 откатов, 8 для N = 14 и 7 суммирование для значений N = {8, 11,13} - person chesslover; 31.07.2014
comment
Да, я тоже имел в виду первое решение. Эвристика ветвления была идеальной (не требовала возврата, так что это ответ на ваш вопрос) для 992 случаев из 997 значений N (N в диапазоне от 4 до 1000). Но для N = {8, 11, 13, 14, 15} потребовался небольшой возврат. Я не знаю ни одной эвристики ветвления, которая идеально подходила бы для всех N. - person chesslover; 31.07.2014
comment
Существуют явные решения для размещения n ферзей на доске n × n для всех n ≥ 4, не требуя комбинаторных поискать вообще. Таким образом: просто закажите переменные так, чтобы первая попытка была решением. Однако, если эти переменные будут дополнительно ограничены таким образом, чтобы исключить это решение, производительность будет очень плохой. - person false; 01.08.2014
comment
... по этой причине решения first-k с k ›1 могут быть более интересными. - person false; 01.08.2014
comment
Согласен, но ваш вопрос касался первого решения для КОНСТРУКТИВНЫХ алгоритмов. Что касается первых k решений (где, как вы отметили, не известно ни одно решение в закрытой форме), в статье San Segundo также есть интересная информация. В частности, для k = 100 большинство возвратов составляет всего около 200. Оно увеличивается примерно до 1200. для малых значений N. - person chesslover; 01.08.2014
comment
Разве это не больше о том, что именно подразумевается под конструктивным? Для каждого явного решения существует тривиальный конструктивный алгоритм. - person false; 01.08.2014

Каково максимальное значение N, для которого программа может вычислить ответ за разумное время? Или какое наибольшее число N мы видели до сих пор?

Нет предела. То есть проверка достоверности решения обходится дороже, чем построение одного решения плюс семь симметричных.

См. Википедию: «Существуют явные решения для размещения n ферзей на доске n × n для всех n ≥ 4, не требует никакого комбинаторного поиска‌. ".

person false    schedule 01.08.2014
comment
@SterlingVix: Это ваше недоразумение: как только вы сможете построить единое решение, просто прикажите пометить переменные именно в таком порядке. Так что соответствующий подход, который находит все решения, очень близок. - person false; 07.09.2016

Я вытащил старую программу Delphi, которая подсчитывала количество решений для любого заданного размера платы, сделал быструю модификацию, чтобы остановить ее после одного удара, и я вижу странный образец в данных:

Первая доска, на решение которой ушло более 1 секунды, была n = 20. Однако 21 решена за 62 миллисекунды. (Примечание: это основано на Now, а не на какой-либо системе высокой точности.) 22 потребовалось 10 секунд, не повторяется до 28.

Я не знаю, насколько хороша оптимизация, поскольку изначально это была высокооптимизированная процедура, когда правила оптимизации были совсем другими. Однако я сделал одну вещь, очень отличную от большинства реализаций, - у нее нет платы. Скорее, я отслеживаю, какие столбцы и диагонали подвергаются атаке, и добавляю по одному ферзю на строку. Это означает, что на каждую проверяемую ячейку выполняется 3 поиска в массиве, и никакого умножения нет. (Как я уже сказал, с тех пор, когда правила были совсем другими.)

Теперь какое-то настоящее безумие: 29 потребовалось 9 секунд. 30 заняли почти 6 минут!

person Loren Pechtel    schedule 07.12.2009
comment
На самом деле это не кажется таким уж интересным. Это связано с количеством откатов, требуемым вашим шаблоном обхода, чтобы добраться до первого решения. Бьюсь об заклад, если бы он возился со своим порядком обхода, эти числа снова сильно изменились бы. - person Mike; 04.06.2010

Фактически ограниченное случайное блуждание (генерация и тестирование), подобное тому, что описано в bakore, - это правильный путь, если вам просто нужно несколько решений, потому что они могут быть сгенерированы быстро. Я сделал это для класса, когда мне было 20 или 21 год, и опубликовал решение в журнале Pascal, Ada & Modula-2, март 1987 г., "The Queens Problem Revisited". Я просто стряхнул пыль с кода из этой статьи сегодня (а это очень неэффективный код) и после исправления пары проблем сгенерировал N = 26 ... N = 60 решений.

person Kirt Undercoffer    schedule 14.01.2011
comment
Вот ссылка на созданное решение N = 60: kirtundercoffer.com/publications/q60- 1.txt Это текстовый файл размером 60 x 60 без пробелов между пустыми пробелами (обозначенными 1) и ферзями (обозначенными 9). Я не проверял этот результат полностью, но на основании всех предыдущих результатов я уверен, что он правильный. - person Kirt Undercoffer; 14.01.2011
comment
Вот решение N = 80: kirtundercoffer.com/publications/q80.txt также сгенерирован используя случайное блуждание. - person Kirt Undercoffer; 14.01.2011
comment
Я также сгенерировал решение N = 100 Queens за ночь - вы можете увидеть его на kirtundercoffer.com/publications/q100.txt Это решение также было сгенерировано с использованием случайного блуждания - можно прочитать в сети и, конечно, услышать в лекциях, что такого рода подход не дает решений даже для малых N. Но это неверно. Конечно, он не сгенерирует все решения, но на самом деле подход случайного блуждания может быть даже предпочтительнее обратного отслеживания, когда N велико, поскольку обратное отслеживание будет интенсивно использовать память. - person Kirt Undercoffer; 14.01.2011

Если вам нужно только одно решение, его можно с жадностью найти за линейное время O (N). Мой код на питоне:

import numpy as np

n = int(raw_input("Enter n: "))

rs = np.zeros(n,dtype=np.int64)
board=np.zeros((n,n),dtype=np.int64)

k=0

if n%6==2 :

    for i in range(2,n+1,2) :
        #print i,
        rs[k]=i-1
        k+=1

    rs[k]=3-1
    k+=1
    rs[k]=1-1
    k+=1

    for i in range(7,n+1,2) :
        rs[k]=i-1
        k+=1

    rs[k]=5-1

elif n%6==3 :

    rs[k]=4-1
    k+=1

    for i in range(6,n+1,2) :
        rs[k]=i-1
        k+=1

    rs[k]=2-1
    k+=1

    for i in range(5,n+1,2) :

        rs[k]=i-1
        k+=1

    rs[k]=1-1
    k+=1
    rs[k]=3-1

else :

    for i in range(2,n+1,2) :

        rs[k]=i-1
        k+=1

    for i in range(1,n+1,2) :

        rs[k]=i-1
        k+=1

for i in range(n) :
    board[rs[i]][i]=1

print "\n"

for i in range(n) :

    for j in range(n) :

        print board[i][j],

    print

Здесь, однако, печать занимает O (N ^ 2) времени, а также python является более медленным языком, любой может попробовать реализовать его на других языках, таких как C / C ++ или Java. Но даже с python он получит первое решение для n = 1000 в течение 1-2 секунд.

person Saket Detroja    schedule 20.09.2018