问题背景
八皇后问题是搜索算法中一个著名的问题。
(本段资料来自百度百科)该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在格的国际象棋棋盘上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
用计算机不难验证答案是种。那么用计算机在合理时间内(数秒),最多可以求解为多少的皇后问题呢?
我们知道,随着的增加,枚举所有可能摆法所需要的时间是指数级增加的。如果用程序去枚举随增加的解的数量变化规律,不难发现解的数量也是指数级增加的。例如,在的时候,解的数量已经高达数量级。
也就是说,就算有一个算法可以快速找到所有的解,它所消耗的时间仍然随着的增加而指数级增加。
因而我们转而考虑这样一个皇后问题:找到一种方法,在格的棋盘上摆放个皇后,使其不能互相攻击。
盲目搜索
第一类解决方法,就是用以解决八皇后问题的盲目搜索算法。这里给出博客中代码共用的Code Base。
1 | import queue |
首先,我们来看看最最最朴素,且不使用任何剪枝等优化技术的DFS搜索可以做得怎么样。(本机环境:Core i7 4700MQ)
1 | def uninformed_search(N): |
1 | $ time print_state(uninformed_search(4), 4) |
嗯,性能意料之内的糟糕。8以上基本就跑不动了。(注:你或许会说创建每个新状态用了的时间,以及python语言的执行效率等等——We don’t care about this here)
加上简单的剪枝:
1 | def uninformed_search_cut(N): |
1 | $ time print_state(uninformed_search_cut(7), 7) |
已经需要超过20s的运行时间。简单的剪枝让可以计算的值翻了一倍。
下面我们稍微换一下思路:仍然是盲目搜索,但是我们通过“把其他行可能放置皇后的位置不断剔除”的方式来判断是否还有可能解进行剪枝。大体效率应当没有提升。
1 | def determine_next(var_domains, vx): |
1 | $ time domain_reduction(15) |
由于解的输出太过庞大,就不打出来了。可以看到一件有趣的事情:对奇数的情况远比偶数的情况要快。因而,找到解的效率可能和具体结构有很大关联。为了屏蔽这种关联性,我们将筛选值域的顺序随机化。
1 | def domain_reduction_random(N): |
1 | $ time domain_reduction_random(25) |
随机化的效率提升是不是有些出乎意料?可见八皇后问题的解是有其特定结构的,而自然顺序是一个较为糟糕的顺序。
终极解法:构造法
构造法利用解的特定结构,直接构造一组解来解决问题。
皇后问题解不唯一,构造的方式也有很多。下面介绍一种由E. J. Hoffman, J. C. Loessi和R. C. Moore提出的构造方式。
首先用两种方式构造边长为偶数格的情形。
A. 若,则从第一行第二列开始,每行向右2格。到一行某尾后下一行从第一列开始,随后继续每行向右两格。例如对,构造的结果为:
B. 若,则对前行,在上放置皇后。随后沿棋盘中心点作中心对称图形。例如对,构造的结果为:
随后解决边长为奇数格的情况。
C. 若为奇数,在棋盘右下角放一个皇后,随后对左上角的棋盘使用构造方法A或B。
这样我们就得到了一组皇后问题的解的构造法。
一点花絮
局部搜索和基于Genetic-Programming的搜索效率蜜汁低下,且容易陷入局部最小的困境。本来以为可以比盲目搜索更快的来着……可能“冲突数”在这个问题中不是一个很好的评估吧。
1 | def gp(N): |