前序中的N皇后问题。如何优化女王选择以提高效率?

时间:2018-09-28 18:04:36

标签: prolog n-queens

我用SWI Prolog实现了N皇后问题,但我想优化皇后的选择,因此效率更高。 这是我的代码:

safe(_,[],pos(A,B)).
safe(N,[pos(X,Y)|Rest],pos(A,B)):-
   safe(N,Rest,pos(A,B)),
   between(1,N,Y),
   noattack(pos(X,Y),Rest,pos(A,B)).

noattack(Queen,[],pos(A,B)).
noattack(pos(X,Y),[pos(X1,Y1)|Rest],pos(A,B)):-
   X=\=X1, Y=\=Y1, Y1-Y=\=X1-X,Y-Y1=\=X-X1,
   A=\=X, B=\=Y, A=\=X1, B=\=Y1, Y-Y1=\=A, X-X1=\=B,
   noattack(pos(X,Y),Rest,pos(A,B)).

template(N,L):-
   findall(pos(I,_),between(1,N,I),L). 

execute(N,N1,L,pos(A,B)):-
   template(N1,L),safe(N,L,pos(A,B)).

solution(L):-
   write('Please give number N and coordinates for super tower:'), nl,
   read(N),
   read(pos(A,B)),
   N1 is N-2,
   execute(N,N1,L,pos(A,B)).

是否有一种方法可以在(1,N,Y)之间进行转换,因此Y的选择将变得“更智能”?

1 个答案:

答案 0 :(得分:0)

我认为作为“低落的果实”,我们可以通过动态地减少“域”来强制在同一列上没有两个皇后(在每一代中,我们确定下一行的位置)。这意味着我们(a)永远不会产生两个皇后在同一列上的情况; (b)我们也可以获得性能,因为我们不再需要检查这一点。

因此,例如,我们可以从诸如[1, 2, 3, 4]之类的可能列的列表开始。如果我们为第一个皇后选择2,则将列表[1, 3, 4]传递给递归调用,无论递归调用选择了什么,我们都知道这不能是同一列。为此,我们需要一个谓词来选择一个项目,同时生成一个列表,在该列表中我们删除了该项目,例如:

pick_rem([H|T], H, T).
pick_rem([H|T], P, [H|R]) :-
    pick_rem(T, P, R).

这将生成:

?- pick_rem([1,2,3], P, R).
P = 1,
R = [2, 3] ;
P = 2,
R = [1, 3] ;
P = 3,
R = [1, 2] ;
false.

一个额外的好处是,如果列表为空,那么我们已经知道我们已经到达董事会的尽头,因此我们找到了有效的解决方案。因此可以通过简单的模式匹配而不是索引的数值比较来检查终止。

我们还需要一个生成范围的谓词,例如:

range(X, Y, []) :-
    X > Y.
range(X, Y, [X|T]) :-
    X =< Y,
    X1 is X + 1,
    range(X1, Y, T).

关于检查皇后不对角攻击的情况,我们可以构造一个包含两个数的谓词,每次更新两个数时,例如:

not_attack([], _, _).
not_attack([H|T], C, D) :-
    \+ H is C + D,
    \+ H is C - D,
    D1 is D+1,
    not_attack(T, C, D1).

因此,我们需要跟踪皇后区的位置,并且以“相反”的顺序进行跟踪(因此,最接近的行是该列表的开头)。

因此,现在我们可以将其合并为一个生成谓词:

gen([], _, []).
gen(Dom, Old, [C|Res]) :-
    pick_rem(Dom, C, Dom1),
    not_attack(Old, C, 1),
    gen(Dom1, [C|Old], Res).

这里,第一个参数是使用一个元素“缩小”每个递归调用的域,第二个元素是一个列表,该列表由我们放在板上的皇后区的列组成。最后,最后一个参数将“构造”电路板的位置。

所以现在我们只需要用一些逻辑将谓词包装在一个更方便的逻辑中即可:

to_pos(R, C, pos(R, C)).

n_queens(N, Sol) :-
    range(1, N, Dom),
    gen(Dom, [], Res),
    maplist(to_pos, Dom, Res, Sol).

然后非常有效地为 N = 4 N = 5 生成 N -queens解:

?- n_queens(4, S).
S = [pos(1, 2), pos(2, 4), pos(3, 1), pos(4, 3)] ;
S = [pos(1, 3), pos(2, 1), pos(3, 4), pos(4, 2)] ;
false.

?- n_queens(5, S).
S = [pos(1, 1), pos(2, 3), pos(3, 5), pos(4, 2), pos(5, 4)] ;
S = [pos(1, 1), pos(2, 4), pos(3, 2), pos(4, 5), pos(5, 3)] ;
S = [pos(1, 2), pos(2, 4), pos(3, 1), pos(4, 3), pos(5, 5)] ;
S = [pos(1, 2), pos(2, 5), pos(3, 3), pos(4, 1), pos(5, 4)] ;
S = [pos(1, 3), pos(2, 1), pos(3, 4), pos(4, 2), pos(5, 5)] ;
S = [pos(1, 3), pos(2, 5), pos(3, 2), pos(4, 4), pos(5, 1)] ;
S = [pos(1, 4), pos(2, 1), pos(3, 3), pos(4, 5), pos(5, 2)] ;
S = [pos(1, 4), pos(2, 2), pos(3, 5), pos(4, 3), pos(5, 1)] ;
S = [pos(1, 5), pos(2, 2), pos(3, 4), pos(4, 1), pos(5, 3)] ;
S = [pos(1, 5), pos(2, 3), pos(3, 1), pos(4, 4), pos(5, 2)] ;
false.

在我的计算机上,它在 N = 19 的情况下仍然有效。上面当然不是 perfect 。可以考虑一些启发式方法等来选择更好的位置,或者预先查看某些模式将失败。现在,如果无法将皇后放在棋盘上,我们只会停止搜索分支。但是有时某些皇后的组合以后再也不会产生好的结果,可以添加这种模式以避免详尽搜索分支。