解决N-Queens问题......我们能走多远?

时间:2009-12-07 23:02:17

标签: algorithm prolog clpfd n-queens

N-Queens问题:

这个问题表明,给定一个大小为N×N的国际象棋棋盘,找到可以在棋盘上放置N个皇后的不同排列,而不会互相威胁。

我的问题是:
程序可以在合理的时间内计算答案的N的最大值是多少?或者到目前为止我们看到的最大N是多少?

这是我在CLPFD(Prolog)中的程序:

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个皇后放置在第1列的第2行,第2列的第4行,第3行的第1行和第4行的第3行(在4乘4的棋盘中)

现在让我们看看这个程序是如何执行的(计算第一个排列所花费的时间):
对于N = 4,5 ..... 10计算在一秒内 对于N = 11-30,需要在-1-3秒之间 对于N = 40..50仍然在一分钟内计算
在N = 60时,它脱离了全局堆栈(搜索空间巨大)。

这是过去的家庭作业问题。 (最初的问题只是编码N-Queens)

我也有兴趣看到其他语言的替代实现(性能比我的实现更好)或者我的算法/程序还有改进的空间

9 个答案:

答案 0 :(得分:12)

这个讨论混淆了三个不同的计算问题:(1)找到N皇后问题的解决方案,(2)列出一些固定N的所有解,以及(3)计算某些固定N的所有解。第一个对于像N = 8这样的电路板尺寸,问题看起来很棘手。然而,正如维基百科所暗示的那样,在N很大的时候,在某些关键方面它很容易。大板上的女王不会那么多沟通。除了内存约束之外,启发式修复算法随着N的增加而变得更容易,更容易。

列出每个解决方案都是另一回事。这可以通过一个好的动态编程代码来完成,该代码的大小足够大,以至于没有必要读取输出。

问题最有趣的版本是计算解决方案。最新技术概括在一个名为The Encyclopedia of Integer Sequences的精彩参考文献中。计算结果为N = 26。我猜这也是使用动态编程,但与列出每个解决方案的情况不同,算法问题更深入,并且可以进一步推进。

答案 1 :(得分:9)

  Loren Pechtel说:“现在真的疯了:29秒花了9秒。   30花了差不多6分钟!“

对于不同电路板尺寸的回溯复杂性,这种令人着迷的缺乏可预测性是我最感兴趣的这个难题的一部分。多年来,我一直在构建一个列表,列出了为每个电路板尺寸找到第一个解决方案所需的算法步骤 - 使用简单且众所周知的深度优先算法,在递归C ++中功能

以下列出了N = 49 ... 减去N = 46和N = 48但仍处于工作中的电路板的所有“计数”

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

(我已经在整数序列百科全书(OEIS)中列为 A140450

该页面包含指向匹配的第一个解决方案列表的链接。

(我的 First Solutions 列表是OEIS序列号 A141843

我并不主要记录每个解决方案需要多少处理时间,而是记录在发现每个电路板的算法优先解决方案之前需要多少个失败的女王位置。当然,后置放置的速率取决于CPU的性能,但考虑到对特定CPU和特定电路板尺寸的快速测试运行,计算解决其中一个“找到的”解决方案所需的时间很容易。< / p>

例如,在Intel Pentium D 3.4GHz CPU上,使用单CPU线程 -

  • 对于N = 35,我的节目'每秒'放置'2400万个皇后,仅花了6分钟找到第一个解决方案。
  • 对于N = 47,我的节目'每秒'放置'2050万个女王,花了199天。

我目前的2.8GHz i7-860每秒大约有2860万个皇后,试图找到第一个N = 48的解决方案。到目前为止,它已经花费了超过550天(理论上,如果它从来没有不间断)到非成功地放置1,369,331,731,000,000(和快速攀登)皇后。

我的网站(尚未)显示任何C ++代码,但我确实在该网页上给出了一个链接,以简单说明解决N = 5板所需的15个算法步骤中的每一个。

确实是一个美味的拼图!

答案 2 :(得分:5)

raymond hettinger在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!)

答案 3 :(得分:5)

您使用的是哪种Prolog系统?例如,使用最新版本的SWI-Prolog,您可以使用原始代码在几分之一秒内轻松找到 N = 80 N = 100 的解决方案。许多其他Prolog系统将比这快得多。

即使在SWI-Prolog的一个在线示例中也出现了N皇后问题,在SWISH中以CLP(FD) queens的形式提供。

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,显示了使用SWI-Prolog N = 40 皇后的完整解决方案流程:

CLP(FD) solution process for 40 queens

答案 4 :(得分:3)

关于计算机解决的最大N是多少,文献中有参考文献,其中使用冲突修复算法(即局部搜索)找到了大约3 * 10 ^ 6的N的解。例如,参见[Sosic and Gu]的经典论文。

关于回溯的精确求解,存在一些巧妙的分支启发式算法,它们实现了正确的配置,几乎没有回溯。这些启发式方法也可用于查找问题的 first-k 解决方案:在找到初始正确配置后,搜索会回溯以查找附近的其他有效配置。

这些几乎完美的启发式的引用是[Kale 90]和[San Segundo 2011]

答案 5 :(得分:3)

  

程序可以在合理的时间内计算答案的N的最大值是多少?或者到目前为止我们看到的最大N是多少?

没有限制。也就是说,检查解决方案的有效性比构建一个解决方案加七个对称解决方案更昂贵。

参见维基百科: "Explicit solutions exist for placing n queens on an n × n board for all n ≥ 4, requiring no combinatorial search whatsoever‌​."

答案 6 :(得分:1)

我拖出了一个旧的Delphi程序,它计算了任何给定电路板尺寸的解决方案数量,做了一个快速修改,使其在一次点击后停止,我看到数据中有一个奇怪的模式:

花费1秒钟来解决的第一块板是n = 20.21在62毫秒内解决了。 (注意:这是基于现在,而不是任何高精度系统。)22花了10秒钟,直到28年才重复。

我不知道优化是多么好,因为当优化规则非常不同时,这最初是一个高度优化的例程。我确实做了一件与大多数实现截然不同的事情 - 它没有板子。相反,我正在跟踪哪些列和对角线被攻击并且每行添加一个女王。这意味着每个单元测试3个阵列查找,根本没有乘法。 (正如我所说,从规则非常不同时。)

现在出现一些真正的疯狂:29秒花了9秒。 30花了近6分钟!

答案 7 :(得分:0)

实际上受限制的随机游走(生成和测试)就像bakore概述的那样,如果您只需要一些解决方案就可以了,因为这些可以快速生成。我20岁或21岁的时候就这样做了,并在Pascal,Ada&amp; amp; Modula-2,1987年3月,“重新审视皇后问题”。我今天刚刚从那篇文章中删除了代码(这是非常低效的代码)并且在修复了几个问题之后产生了N = 26 ... N = 60个解决方案。

答案 8 :(得分:0)

如果只需要1个解,则可以在线性时间O(N)中贪婪地找到它。我在python中的代码:

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,它也会在1或2秒内获得n = 1000的第一个解决方案。