利用生成器yield 递归解决八王后问题

Posted stonenox

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用生成器yield 递归解决八王后问题相关的知识,希望对你有一定的参考价值。

八王后问题:

  要在一个8*8的国际象棋棋盘里,放下8个王后,请问如何放置。

  技术图片

 

  由于在国际象棋中,王后的杀戮区域是直线,和斜线,不论距离:如下图

    技术图片

  所以要放置下8个王后,就必须把她们放在各自的杀戮区域之外。显然,每一行只能有且必须有一个王后。

构思代码:

  1.棋盘

  我们可以考虑,把棋盘用一个元组,

  state = (pos1,pos2,pos3....pos8)

  state中,元素的序号代表了棋盘的行 ; 元素的值(posn),则代表了王后在该行的位置.

  state 的初始状态是(none,none,none....)

  我们从第1行state[0]开始放置皇后

  2.位置合法判断

  判断某一行王后的位置是否正确:

    如果在新的一行,例如第四行state[3] = pos3

    那么我们可以用state[3] ,和之前3行 state[i]    i = 0,1,2 依次对比。

    1. 是否在同一列(直线杀戮区域)

      如果state[3] 与 state[i] 相等,那么她们就在同一列,该位置不正确。

    2. 是否在斜角上(45度杀戮区域)

      技术图片

      如图,红色圆圈的位置,就是在斜线杀戮区域。 很容易看到,其实该点满足|X2-X1| = |Y2-Y1|

      即:  abs(state[3]  - state[i] ) == abs(3-i)

 

    有了这两个条件,我们可以写出判断位置是否合法的函数:  

#判断在该棋盘(state)上,新的皇后如果放在nextX列,是否于老皇后冲突.
def conflic(state,nextX):
    nextY = len(state)                        
    for i in range(nextY):
        if abs(nextX- state[i])  in (0,nextY-i): #如果与之前的皇后同列,或者ΔX=ΔY(45°角),则不可
            return True
    return False

 

  3. 递归查找王后位置

     函数的返回条件:

      当函数找到第8行的王后位置时,返回(yield) 该位置。

      当找完了第八行所有位置,都没有新的合适的位置时,返回none空

    函数的递归条件:

      如果函数当前不是在第8行,并且找到了新的合适的位置,则进入新函数

    函数的输入输出:

      输入: 函数需要直到当前的棋盘信息,所以,需要输入 

        1. 棋盘的大小。

         2.棋盘上已经存在的皇后 state()  + 本函数找到的新皇后的合法位置 pos

          注意pos是以元素的新式加入到state()的。 

      返回:

        向上级函数返回的,是 当皇后位置,和其后的皇后递归得到的位置

        即返回的是  pos_i ~~ pos_8 组成的元组。

      可见,输入的state,并不用于结果的输出,结果的输出是由pos一层一层往回迭代的。

  代码如下:

  

#num: 棋盘的尺寸(行,列都为num),也是皇后的个数,
#state: 元组,序号是行,值为该行中皇后的列位置
def queen(num=8,state=()):
    row = len(state)    #获得现在的行
    for i in range(num): #对所有列位置做:
        if not conflic(state,i):   #找出可以放的位置
            if (row == num-1) : #是最后一行
                yield (i,)      #暂停并返回 位置(next也是从此处恢复) 
            else :  #不是最后一行
                for pos in queen(num,state+(i,)):      #进入递归
                    yield  ((i,) + pos)   #从递归返回了值

  注意,当函数挂起后(遇到yield),重新next()时,是从最初挂起的位置,也就是说第7层queen的 地方开始恢复执行的。

  if (row == num-1) : #是最后一行
    yield (i,) #暂停并返回 位置(next也是从此处恢复) 

  当第七层没有找到合适的点时,返回none,进入上一层queen()的如下代码

   for pos in queen(num,state+(i,)):      #进入递归
           yield  ((i,) + pos)   #从递归返回了值
  

  由于该层的pos得到了none, 所以并不执行yield,而是跳到最外层for循环,执行下一个点的判断和递归。

    for i in range(num): #对所有列位置做:

  这样,就是先了所有点的遍历。

 

  该函数的执行结果如下:

  

>>> g = queen()
>>> next(g)
(0, 4, 7, 5, 2, 6, 1, 3)
>>> next(g)
(0, 5, 7, 2, 6, 3, 1, 4)
>>> next(g)
(0, 6, 3, 5, 7, 1, 4, 2)
>>> 

我们可以执行很多次next(g),因为这个程序其实是以固定的方式,遍历了所有的可能。 

总共有多少种可能呢?

>>> len(list(queen()))
92

92种

以上是关于利用生成器yield 递归解决八王后问题的主要内容,如果未能解决你的问题,请参考以下文章

Python 生成器 匿名函数 递归 模块及包的导入 正则re

python_递归_协程函数(yield关键字)_匿名函数_模块

python yield浅析

python协程函数递归匿名函数与内置函数使用模块与包

利用递归和回溯解决八皇后问题

利用递归和回溯解决八皇后问题