版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址http://www.cnblogs.com/Colin-Cai/p/9790466.html 作者:窗户QQ/微信:6679072E-mail:6679072@qq.com 

  上一章讲了用1~n的排序来表示n皇后的解,然后通过枚举1~n所有的排列、判定谓词过滤所有排列得到最终的所有解。

  在此基础上,这一章我们思考是否存在更好的解法,从而深化这个问题。

  效率问题

  为了测试效率,在代码末尾加上

  (queen (read))

  表示解决的皇后个数由输入的结果决定。

  还是先把Scheme代码编译、链接为普通可执行文件,这样运行就不是在解释的条件下了,速度可以提升数倍。

  八皇后使用这个程序出来结果还算可以接受,但是当我想解决10皇后问题的时候,却花了半分钟,而如果是解决11皇后问题,我等待了好几分钟,系统把进程杀了。

  

  我们可以意识到,程序的效率似乎并不是那么高。那么有没有提升的办法呢?

  想要找到提升的办法,我们先要分析之前的算法慢的原因。

  

  这个算法中,内存使用姑且就不说了(其实存储所有的排列需要很大的内存),我们要产生所有的排列,然后每一个排列都要单独判定是否符合条件。

  然而,我们想一想,我们真的需要每个排列都独立检查一遍吗?

  实际上,我们可能真的不需要如此。我们其实只需要检测到一个片段上存在皇后互吃,那么包含这个片段的所有排列自然都不可能是解,可以直接被排除。

  比如我们检测到如果一个排列以1、2开头的话,那么这两个点距离为1,值也相差1,两个皇后互吃,从而就可以知道,所有以1、2开头的排列都不需要检测了。

  也就是所有的(1 2 ...)排列直接因为我检测到(1 2)不能作为解而直接排除。

  这样从片段来对所有排列剪枝,当然比挨个检测效率高。

  字典顺序

  

  我们要考虑一个字典顺序的检测。

  字典顺序就是按照英文字典那样,单词出现的顺序是按字符串的大小顺序。

  

  字符串的大小比较,大家应该都很熟。

  两个字符串从头逐位比较,过程中,对应位的字符相等则继续比较,直到过程中一个字符串先到尾部或者字符上分出大小,先到尾部或者对应位上的字符小的一方的字符串较小,另一个字符串则较大。如果两个字符串同时都检测到了尾部,那么两个字符串当然一模一样,则为相等。

  C语言中字符串比较可以用strcmp函数,而Scheme里字符串比较可以用 string=?  string>? 等函数。

  但是,我们这里是要对于所有排列按照字典顺序检测,可是这里每个排列是一个数字组成的list,那么我们可以按照数字的大小来替代之前字符串比较时的字符大小,就可以做到字典序列了。

  比如1~3的全排列一共有6个排列,按照字典顺序从小到大本应该如下:

  (1 2 3)

  (1 3 2)

  (2 1 3)

  (2 3 1)

  (3 1 2)

  (3 2 1)

  

  但考虑到列表的特殊结构,我们判断两个排列的大小从最后一位开始看的话(也就是列表反过来看),在这里因为一路可以使用cons/car/cdr而不是append/take/drop之类相对复杂的递归,从而要方便很多,效率也要高,于是上述1~3的全排列按照字典顺序会如下:

  (3 2 1)

  (2 3 1)

  (3 1 2)

  (1 3 2)

  (2 1 3)

  (1 2 3)

  实际的例子

  我们需要实际来看看按照字典序列并加上之前的剪枝思路如何完成完整的解答。

  在这里,我们可以用4皇后来作为i例子。

  

  我们试图要用迭代完成我们的检测,用当前检测的列表和已有的解合成迭代的状态

  最开始的时候,我们要检测的list是空列,而解初始也为一个空列

  目前                    解                说明

  ()                         ()                 初始

  (1)       ()                 检测合法   

  (2 1)                    ()     不合法,所以以(2 1)结尾的排列都不合法

  (1)                       ()                因为上面不合法,所以2被退掉

  (3 1)                    ()                填入比刚才退掉的2大的数中最小的

...

  

  这里产生了一个问题,上面红字标注的两行,除了说明之外,其他一模一样,无法区分。而一个转化成(2 1)()状态,另外一个转化为(3 1)()状态,相同的状态转换成了不同的状态,对于迭代,

  这个完全没道理

  

  说明状态还不完善,我们刚才两次从(1)()状态出发,之所以第一次转化到了(2 1)()状态,而第二次转化到了(3 1)()状态,原因就在于其实都是在(1)的基础上,找了原则上最小的值。从而,我们可以在状态中再加一个限值,标志着列表下一个添加的元素的选取必须高于这个值。

  这样状态才是完备的。

  于是上面4皇后问题状态的转换可以如下:

  目前            限值                解                说明

  ()                  0                     ()                 初始

  (1)      0                  ()                 (1)合法,升位

  (1)                2                     ()      (2 1)非法 (满足的最小的数是2)

  (3 1)             0                     ()                (3 1)合法,升位

  (3 1)             2                     ()                (2 3 1)非法

  (3 1)             4                     ()                (4 3 1)非法

  (1)                3                     ()                不存在值,降位

  (4 1)             0                     ()                (4 1)合法,升位

  (2 4 1)          0                     ()                (2 4 1)合法,升位

  (2 4 1)          3                     ()                (3 2 4 1)非法

  (4 1)             2                     ()                不存在值,降位

  (4 1)             3                     ()                 (3 4 1)非法

  (1)                4                     ()                 不存在值,降位

  ()                  1                     ()                 不存在值,降位

  (2)                0                     ()                  (2)合法,升位

  ...

  (3 1 4 2)       0                     ()                  (3 1 4 2)合法,升位

  (1 4 2)          3                     ((3 1 4 2))     加入解,降位

  ...

  (4)                3        ((2 4 1 3) (3 1 4 2))    不存在值,降位

  ()                  4        ((2 4 1 3) (3 1 4 2))    不存在值且无法降位,结束

  状态转移

  现在,我们定下状态为 目前,限值,解

  我们来分析关于状态的一切:

  初始时,状态为 ()0()。

  结束时,一定是因为目前的是空列,而且限值已经达到了n皇后的n。

  当目前的列表包含了1~n的数时(其实就是长度为n),那么找到了一个解,把这个列表加入到解,然后降位,也就是目前的列表把最前面的一位去掉,然后限值设为最前面的这一位。

  其他情况下,找剩余的数中大于限制的最小数:

  (1)如果不存在,则降位

  (2)如果存在,假如这个值加到目前的列表前得到的新表是合法的,那么升位,新列表作为目前列表,限值设为0即可。

  (3)如果存在,假如这个值加到目前的列表前得到的新表是非法的,那么限制调整为刚才找到的最小数。

  迭代和封装

  按上述规则,迭代代码如下:

 (define (_queen n current gt result)(cond((and (null? current) (>= gt n)) result);终止条件((= n (length current)) (_queen n (cdr current) (car current) (cons current result)));记下解(else(let ((remained (filter (lambda (x) (> x gt )) (remove* current (range 1 (+ n 1)))) );剩下的数中哪些是大于下限的)(if (null? remained)(_queen n (cdr current) (car current) result)(let ((next (car remained)))(if (valid? current next)(_queen n current next result)(_queen n (cons next current) 0 result))))))))

  其中,remove*是racket里的函数,用于集合相减,并且不改顺序,但它并不属于Scheme标准。此处略去实现。

  比如(remove* '(2 3 4) '(1 2 4 5 6))返回'(1 5 6)。

  

  当然,不忘封装一下:

(define (queen n) (_queen n '() 0 '()))

  合法性检测

  

  检测所用的valid?函数并未实现。

  在这个算法中,如果一个序列是非法的,也就是存在皇后互吃的,一定是最新的元带来的。因为如果判断到这一步,那么之前的子序列一定是合法的。

  这是一个递归的思路,也是比上一章使用迭代来检测一个排列所有的可能更加快速的地方之一。

  比如要检测'(2 5 3 1)是不是合法,在检测前,我们无条件知道(cdr '(2 5 3 1))也就是'(5 3 1)是合法的。

  那么合法性判断就成了看最左边的2和后面的3个元素是否有差值的绝对值等于距离,一种比较好的做法是:

  先将'(5 3 1)的每个元素减去新加的2,得到'(3 1 -1),再取绝对值得到'(3 1 1),再和'(1 2 3)比较,看看是否在相同位置有相同元素。

  判断两个列表存在不存在相同位置有相同元素,用个递归很容易写:

(define (same_ele_pos? x y)(cond((null? x) #f)((= (car x) (car y)) #t)(else (same_ele_pos? (cdr x) (cdr y))))
)

  于是合法性检测就可以写成如下:

(define (valid? lst new)(same_ele_pos?(range 1 (+ 1 (length lst)))(map (lambda (x) (abs (- x new))) lst))
)

  上面检查'(2 5 4 1)是否合法,就可以通过(valid? '(5 4 1) 2)来检测。

  测试

  把上述代码后面加(queen 10)解决10皇后问题,编译之后,我们发现运行时间连1秒都不需要。

  而如果要求12皇后问题需要20秒。实际上,我们还可以在状态中引入一些别的东西以提高速度,从而使得运行时间变成现在的几分之一,但这已经不是我想在这里讲的了。

  算法这东西,很多时候很难做到极致,所以工程中有一个方向就是随着时间推移,软件版本在提升算法运行速度,比如PCB/FPGA的布线。工程师们依然为之不断努力。

转载于:https://www.cnblogs.com/Colin-Cai/p/9790466.html

Scheme来实现八皇后问题(2)相关推荐

  1. C语言局部搜索算法(爬山法,模拟退火法,遗传算法)求解八皇后问题

    C语言局部算法求解八皇后问题 写在前面 八皇后问题及局部搜索算法 爬山法(hill-climbing searching) 算法介绍 代码实现 退火法(simulated annealing) 算法介 ...

  2. 递归/回溯:八皇后问题N-Queens

    N皇后问题是计算机科学中最为经典的问题之一,该问题可追溯到1848年,由国 际西洋棋棋手马克斯·贝瑟尔于提出了8皇后问题. 将N个皇后放摆放在N*N的棋盘中,互相不可攻击,有多少种摆放方式,每种摆 放 ...

  3. 八皇后的一个回溯递归解法

    解法来自严蔚敏的数据结构与算法. 代码如下: #include <iostream> using namespace std; const int N = 8;//皇后数 int coun ...

  4. Prolog学习:数独和八皇后问题

    上一篇简单介绍了下Prolog的一些基本概念,今天我们来利用这些基本概念解决两个问题:数独和八皇后问题. 数独 数独是一个很经典的游戏: 玩家需要根据n×n盘面上的已知数字,推理出所有剩余空格的数字, ...

  5. 带你轻而易举的学习python——八皇后问题

    首先我们来看一下这个著名的八皇后问题 八皇后问题:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 在这个问题提出之后人们又将 ...

  6. 漫画:什么是八皇后问题?

    本文经授权转载自公众号程序员小灰 (ID:chengxuyuanxiaohui) -----  第二天  ----- 题目是什么意思呢? 国际象棋中的皇后,可以横向.纵向.斜向移动.如何在一个8X8的 ...

  7. 递归解决八皇后问题-小昝

    引言 由于大学课堂中数据结构中并没有讲一些常见的算法,只是讲的比较简单的定义.所以拿出来暑假时间去研究经典的算法.本文章是研究的八皇后问题.八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 ...

  8. 十二、八皇后问题(递归回溯)

    一.八皇后问题介绍 (本次使用回溯算法解决,之后会用贪心算法优化) 在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行. 同一列或同一斜线上,问有多少种摆法(9 ...

  9. 八皇后算法python_Python学习二(生成器和八皇后算法)

    看书看到迭代器和生成器了,一般的使用是没什么问题的,不过很多时候并不能用的很习惯 书中例举了经典的八皇后问题,作为一个程序员怎么能够放过做题的机会呢,于是乎先自己来一遍,于是有了下面这个ugly的代码 ...

最新文章

  1. 防止酒后删库!日本人用 3 小时做了个酒精测试软件
  2. 你应该掌握的七种回归技术
  3. Build boost 1.66.0 with c++11
  4. linux运维必学python吗_linux运维一定要学python吗?
  5. 软件工程课堂作业——寻找“水王”
  6. 如何解决error message Data cannot be maintained for set type COM_TA_R3_ID
  7. 一起谈.NET技术,C#序列化与反序列化(Serializable and Deserialize)
  8. Delphi中的基础数据类型
  9. python伪造邮件发件地址_Python:向多个地址发送电子邮件
  10. Linux查找树莓派ip地址,让树莓派“说”出自己的IP地址
  11. jq获取验证码成功之后弹出的提示框_验证码填写错误,请重新填写。。。
  12. 职称计算机 将计算机broad_1下的e盘映射为k盘网络驱动器,职称计算机考试网络基础)试题及答案操作.doc...
  13. 获取键盘上某键的状态
  14. endnote layout can not be formatted because it is no longer open
  15. Django菜鸟教程学习记录(一)
  16. ps软件怎么测试性能,怎么用ps测试电脑性能 设计师要知道
  17. Django操作views(一)
  18. string容器模拟实现及使用——C++
  19. 前端本地静态模板下载功能
  20. Excel2019将下面空白单元格填充的和上面值一样

热门文章

  1. 华为AI再进化,CANN 3.0释放「算力狂魔」
  2. 6个你应该用用看的用于文本分类的最新开源预训练模型 忆臻
  3. 神经进化:一种不一样的深度学习
  4. Multi-task Learning(Review)多任务学习概述
  5. 为什么医学影像AI已进入「后深度学习时代」?
  6. 《用Python进行自然语言处理》第 11 章 语言数据管理
  7. Python 之 matplotlib (八)Bar
  8. torch.bmm()函数的使用
  9. 【前沿科技】云计算军事运用有啥特点
  10. IBM Watson大裁70% 员工,撕掉了国内大批伪AI企业最后一块遮羞布!