python 使用递归回溯完美解决八皇后的问题


Posted in Python onFebruary 26, 2020

八皇后问题描述:在一个8✖️8的棋盘上,任意摆放8个棋子,要求任意两个棋子不能在同一行,同一列,同一斜线上,问有多少种解法。

规则分析:

任意两个棋子不能在同一行比较好办,设置一个队列,队列里的每个元素代表一行,就能达到要求

任意两个棋子不能在同一列也比较好处理,设置的队列里每个元素的数值代表着每行棋子的列号,比如(0,7,3),表示第一行的棋子放在第一列,第二行的棋子放在第8列,第3行的棋子放在第4列(从0开始计算列号)

任意两个棋子不能在同一斜线上,可以把整个棋盘当作是一个XOY平面,原点在棋盘的左上角,斜线的斜率为1或者-1,X为列号,Y为行号,推出斜线的表达式为Y=X+n或者Y=-X+n(n为常数,斜线确定下来之后n就确定了),进而可以推导出Y-X=n或者Y+X=n。也就是说在同一斜线上的两个棋子行号与列号之和或者之差相等。X1+Y1=X2+Y2或者X1-Y1=X2-Y2。再进行变换能够得到X1-X2=Y2-Y1或者X1-X2=Y1-Y2,也就是说|X1-Y1|=Y1-Y2。即判断两个棋子是否在同一斜线上,只要判断出两个棋子的列号之差是否等于两个棋子的行号之差的绝对值就行了。

如下图:

python 使用递归回溯完美解决八皇后的问题

将上述文字分析转化为代码,就可以判断棋子之间是否符合规则了(abs(num)表示取num的绝对值)

def is_rule(queen_tup, new_queen):
 """
 :param queen_tup: 棋子队列,用于保存已经放置好的棋子,数值代表相应棋子列号
 :param new_queen: 被检测棋子,数值代表列号
 :return: True表示符合规则,False表示不符合规则
 """
 num = len(queen_tup)
 for index, queen in enumerate(queen_tup):
 
  if new_queen == queen: # 判断列号是否相等
   return False
  if abs(new_queen-queen) == num-index: # 判断列号之差绝对值是否与行号之差相等
   return False
 
 return True

事实上,这段代买还可以简写,判断列号之差也可以写作是列号之差是否为0,这样就可以使用一个in来完成整个判断。修改后如下

def is_rule(queen_tup, new_queen):
 """判断棋子是否符合规则"""
 for index, queen in enumerate(queen_tup):
  if abs(new_queen-queen) in (len(queen_tup)-index, 0): # 判断表达式
   return False
 return True

接下来写一下摆放棋子的函数

摆放棋子其实有两种方法,第一种,求出8✖️8棋盘上每行放置一个棋子的所有方法,也就相当于全排列。然后再用冲突函数逐个判断是否符合规则,如符合就放入队列

第二种,在一行放入棋子,然后判断是否符合规则,符合的情况下再去放下一行,下一行如果所有位置都不符合,退回到上一行,上一行的棋子再放置一个新的位置,然后再进去下一行判断有没有符合规则的棋子的位置。这种方法叫做递归回溯,每一行就相当于是一个回溯点

这里我使用第二种方法写个函数,先上代码,然后再解释

def arrange_queen(num, queen_tup=list()):
 """
 :param num:棋盘的的行数,当然数值也等于棋盘的列数
 :param queen_tup: 设置一个空队列,用于保存符合规则的棋子的信息
 """
 
 for new_queen in range(num): # 遍历一行棋子的每一列
 
  if is_rule(queen_tup, new_queen): # 判断是否冲突
 
   if len(queen_tup) == num-1:  # 判断是否是最后一行
    yield [new_queen] # yield关键字
 
   else:
    # 若果不是最后一行,递归函数接着放置棋子
    for result in arrange_queen(num, queen_tup+[new_queen]):
     yield [new_queen] + result

如果能够理解上边函数的可以不用看下面的分析了,如果不明白,接下来我将举几个代码例子来说明上面的函数

首先是yield,这个是python里的关键字,带有yield的函数被称作为生成器函数。函数在执行的时候,遇到yield关键字会暂停函数的执行,同时返回yield右边的对象到函数被调用的地方,直到函数下次被执行,将回到yield所在的地方继续执行,如果函数执行完毕还没有遇到yield,就会抛出一个异常StopIteration。而生成器函数需要使用next方法来执行。下面的代码将解释生成器函数的执行:

def demo():
 
 yield 1
 yield 2
 print('end')
 
b = demo()  # 将生成器函数的引用传递给变量b
print(next(b)) # 第一次执行生成器函数,返回 1 同时函数暂停,打印结果
print(next(b)) # 第二次执行生成器函数,返回 2 同时函数暂停,打印结果
print(next(b)) # 第三次执行生成器函数,因为没有再遇到yield,函数执行完毕,抛出异常StopIteration

但是上述放置棋子的代码中并没用调用next方法来执行生成器函数,而是使用了for循环遍历,并且在函数执行完毕之后也没有抛出StopIteration的错误。那是因为for循环在执行的时候,会不断的自动调用next方法,并且在遇到StopIteration的时候会捕捉异常并终止循环,以下代码我将模拟一下for循环来执行生成器函数

def demo():
 
 yield 1
 yield 2
 print('end')
 
 
# 模拟的for循环
b = demo()
while True:
 try:
  next(b)
  """
  此段区域写for下的代码块
  """
 except StopIteration:
  break
 
# 实际的for循环
for i in demo():
 """
 for 下的代码块
 """
 pass

通过这个可以知道,当使用for循环驱动生成器函数的时候,如果函数执行完毕还没有遇到yield关键字,就会直接退出for循环而不会执行for循环下的代码块。值得注意的是,上边两个循环分别是调用了两次生成器函数。生成器函数在一次执行完毕之后再继续调用是不会得到结果的

了解了生成器函数与for循环是怎么驱动生成器函数之后,关于棋子的递归函数里面还有一个就是递归函数了。以前上课的时候老师将递归函数使用的例子是数值的阶乘,这里我也使用阶乘来解释一下递归函数的执行。先介绍一下阶乘:给定一个正整数n,规定n的阶乘n!=n(n-1)(n-2).....1。也就是从1到n的累乘。(0!=1,这是规定,别问我为什么......)

def a(num):
 result = num*b(num-1)
 return result
 
 
def b(num):
 
 result = num*c(num-1)
 return result
 
 
def c(num):
 if num == 1:
  result = 1
 return result
 
 
result = a(3)
print(result)

上述代码是函数嵌套,只能用作计算3的阶乘,我使用它来理解递归函数

a函数被调用执行的时候,传参3,然后调用函数b,同时传参3-1=2,函数b执行在调用函数c同时传参2-1=1,函数c执行,判断传参结果符合,返回数值result到函数c被调用的地方,然后与b的参数2相乘,得到新的结果赋值给b里面的result,然后再将result返回到b被调用的地方,再乘a的参数3赋值给a里面的result,再将a里的result返回到函数a被调用的地方,然后打印结果。

这就是利用函数的嵌套来执行出3!,那么如果想算10000的函数呢?难道写10000个函数?

这里发现a函数和b函数除了变量名字不一样,其余的形式都一摸一样,那么直接在a里面调用a函数,写成如下形式

def a(num):
 result = num*a(num-1)
 return result

但是这样的话,函数将不断的被调用。所以加一个函数终止的条件,变成了

def a(num):
 if num == 1:
  return 1
 else:
  return num*a(num-1)
 
 
result = a(3)
print(result)

这就是一个最简单的递归函数

分析函数的运行,函数第一次被调用,传递参数3,判断不满足终止条件。继续执行,接下来再调用函数a,传递参数3-1=2,判断不满足终止条件。继续执行,接下来再调用函数a,传递参数2-1=1,判断满足终止条件,第三次被调用的函数结束,返回1到被调用的地方,与2相乘,第二次被调用的函数结束,结果再返回到第二次函数被调用的地方,与3相乘,第一次被调用的函数结束,结果返回

这就是这个最简单的递归函数的执行过程。总结就是递归函数不断的调用自身,直至满足函数终止的条件

搞定了含有yield的生成器函数,for循环驱动生成器函数的实质,递归函数的调用,我们再来看八皇后的棋子摆放的函数,为了方便观察,将‘八皇后'改为‘四皇后',就是只算4✖️4棋盘上放置4个棋子

def arrange_queen(num, queen_tup=list()):
 """
 :param num:棋盘的的行数,当然数值也等于棋盘的列数
 :param queen_tup: 设置一个空队列,用于保存符合规则的棋子的信息
 """
 
 for new_queen in range(num): # 遍历一行棋子的每一列
 
  if is_rule(queen_tup, new_queen): # 判断是否冲突
 
   if len(queen_tup) == num-1:  # 判断是否是最后一行
    yield [new_queen] # yield关键字
 
   else:
    # 若果不是最后一行,递归函数接着放置棋子
    for result in arrange_queen(num, queen_tup+[new_queen]):
     yield [new_queen] + result
 
 
for i in arrange_queen(4):
 print(i)

执行结果是

[1,3,0,2]

[2,0,3,1]

下面描述一下函数的执行过程:

1.放置第一行棋子。函数第一次被调用,传递参数4,空列表。放置棋子在第一行第一列,判断棋子放置符合规则,判断不是最后一行,将棋子位置信息放入列表,同时生成新的列表[0]

2.放置第二行棋子。函数第二次被调用,传递参数4,列表[0]。放置棋子在第二行第一列,判断棋子不符合规则,接着放置棋子在第二行第二列,判断棋子不符合规则,再放置棋子在第二行第三列,判断符合规则,将棋子位置信息放入列表,同时生成新的列表[0,2]

3.放置第三行棋子。函数第三次被调用,传递参数4,列表[0,2]。放置棋子在第三行第一列,判断棋子不符合规则,接着放置棋子在第三行第二列,判断不符合规则,再放置棋子到第三行第三列,判断不符合规则,再放置棋子到第三行第四列,判断还是不符合规则。第三次函数调用结束

4.回到函数第二次被调用的地方,第二次被调用的函数接着放置棋子,上一次放置到了第三列,这次放到第四列,判断符合规则,将棋子位置信息放入列表,同时生成新的列表[0,3]

5.函数被调用,用于放置第三行,从第一列再依次判断到最后一列,如果符合规则,放入棋子信息,同时生成新的列表[0,3,1]

6.函数被调用,用于放置第四行,从第一列判断到最后一列,都不符合规则,函数执行完毕,回到上一级

.......

N.当前三行的棋子放入都符合规则,而且第四行也符合规则了,此时第一次遇到yield关键字,第四级函数暂停,将棋子信息放入列表[2],返回到第三级,第三级函数也将第三级符合规则的棋子信息放入列表,同时与第四级返回的列表相加,得到一个新的列表,然后遇到第三级函数的关键字函数yield,第三级函数暂停,返回了[0,2]到第二级函数.......直到第一级函数暂停,返回结果[1,3,0,2],打印结果

然后第一级函数接着执行,驱动二级函数执行,二级驱动三级执行,三级驱动四级执行....

直到所有结果打印完毕,整个函数执行完毕

整个代码为

def is_rule(queen_tup, new_queen):
 """判断棋子是否符合规则"""
 for index, queen in enumerate(queen_tup):
  if abs(new_queen-queen) in (len(queen_tup)-index, 0): # 判断表达式
   return False
 return True
 
 
def arrange_queen(num, queen_tup=list()):
 """
 :param num:棋盘的的行数,当然数值也等于棋盘的列数
 :param queen_tup: 设置一个空队列,用于保存符合规则的棋子的信息
 """
 
 for new_queen in range(num): # 遍历一行棋子的每一列
 
  if is_rule(queen_tup, new_queen): # 判断是否冲突
 
   if len(queen_tup) == num-1:  # 判断是否是最后一行
    yield [new_queen] # yield关键字
 
   else:
    # 若果不是最后一行,递归函数接着放置棋子
    for result in arrange_queen(num, queen_tup+[new_queen]):
     yield [new_queen] + result
 
 
for i in arrange_queen(8):
 print(i)

整个代码最终要的就是递归回溯的思想,如果能真正的明白,不用用什么语法或者写什么样的函数,都能轻松解决这个八皇后的问题

接下来我贴出一个八皇后的的终极版(下面的代码来源百度百科),不使用yield关键字的。可以自行理解一下

def queen(A, cur=0):
 if cur == len(A):
  print(A)
  return 0
 for col in range(len(A)):
  A[cur], flag = col, True
  for row in range(cur):
   if A[row] == col or abs(col - A[row]) == cur - row:
    flag = False
    break
  if flag:
   queen(A, cur+1)
queen([None]*8)

八皇后的所有解

[0, 4, 7, 5, 2, 6, 1, 3]
[0, 5, 7, 2, 6, 3, 1, 4]
[0, 6, 3, 5, 7, 1, 4, 2]
[0, 6, 4, 7, 1, 3, 5, 2]
[1, 3, 5, 7, 2, 0, 6, 4]
[1, 4, 6, 0, 2, 7, 5, 3]
[1, 4, 6, 3, 0, 7, 5, 2]
[1, 5, 0, 6, 3, 7, 2, 4]
[1, 5, 7, 2, 0, 3, 6, 4]
[1, 6, 2, 5, 7, 4, 0, 3]
[1, 6, 4, 7, 0, 3, 5, 2]
[1, 7, 5, 0, 2, 4, 6, 3]
[2, 0, 6, 4, 7, 1, 3, 5]
[2, 4, 1, 7, 0, 6, 3, 5]
[2, 4, 1, 7, 5, 3, 6, 0]
[2, 4, 6, 0, 3, 1, 7, 5]
[2, 4, 7, 3, 0, 6, 1, 5]
[2, 5, 1, 4, 7, 0, 6, 3]
[2, 5, 1, 6, 0, 3, 7, 4]
[2, 5, 1, 6, 4, 0, 7, 3]
[2, 5, 3, 0, 7, 4, 6, 1]
[2, 5, 3, 1, 7, 4, 6, 0]
[2, 5, 7, 0, 3, 6, 4, 1]
[2, 5, 7, 0, 4, 6, 1, 3]
[2, 5, 7, 1, 3, 0, 6, 4]
[2, 6, 1, 7, 4, 0, 3, 5]
[2, 6, 1, 7, 5, 3, 0, 4]
[2, 7, 3, 6, 0, 5, 1, 4]
[3, 0, 4, 7, 1, 6, 2, 5]
[3, 0, 4, 7, 5, 2, 6, 1]
[3, 1, 4, 7, 5, 0, 2, 6]
[3, 1, 6, 2, 5, 7, 0, 4]
[3, 1, 6, 2, 5, 7, 4, 0]
[3, 1, 6, 4, 0, 7, 5, 2]
[3, 1, 7, 4, 6, 0, 2, 5]
[3, 1, 7, 5, 0, 2, 4, 6]
[3, 5, 0, 4, 1, 7, 2, 6]
[3, 5, 7, 1, 6, 0, 2, 4]
[3, 5, 7, 2, 0, 6, 4, 1]
[3, 6, 0, 7, 4, 1, 5, 2]
[3, 6, 2, 7, 1, 4, 0, 5]
[3, 6, 4, 1, 5, 0, 2, 7]
[3, 6, 4, 2, 0, 5, 7, 1]
[3, 7, 0, 2, 5, 1, 6, 4]
[3, 7, 0, 4, 6, 1, 5, 2]
[3, 7, 4, 2, 0, 6, 1, 5]
[4, 0, 3, 5, 7, 1, 6, 2]
[4, 0, 7, 3, 1, 6, 2, 5]
[4, 0, 7, 5, 2, 6, 1, 3]
[4, 1, 3, 5, 7, 2, 0, 6]
[4, 1, 3, 6, 2, 7, 5, 0]
[4, 1, 5, 0, 6, 3, 7, 2]
[4, 1, 7, 0, 3, 6, 2, 5]
[4, 2, 0, 5, 7, 1, 3, 6]
[4, 2, 0, 6, 1, 7, 5, 3]
[4, 2, 7, 3, 6, 0, 5, 1]
[4, 6, 0, 2, 7, 5, 3, 1]
[4, 6, 0, 3, 1, 7, 5, 2]
[4, 6, 1, 3, 7, 0, 2, 5]
[4, 6, 1, 5, 2, 0, 3, 7]
[4, 6, 1, 5, 2, 0, 7, 3]
[4, 6, 3, 0, 2, 7, 5, 1]
[4, 7, 3, 0, 2, 5, 1, 6]
[4, 7, 3, 0, 6, 1, 5, 2]
[5, 0, 4, 1, 7, 2, 6, 3]
[5, 1, 6, 0, 2, 4, 7, 3]
[5, 1, 6, 0, 3, 7, 4, 2]
[5, 2, 0, 6, 4, 7, 1, 3]
[5, 2, 0, 7, 3, 1, 6, 4]
[5, 2, 0, 7, 4, 1, 3, 6]
[5, 2, 4, 6, 0, 3, 1, 7]
[5, 2, 4, 7, 0, 3, 1, 6]
[5, 2, 6, 1, 3, 7, 0, 4]
[5, 2, 6, 1, 7, 4, 0, 3]
[5, 2, 6, 3, 0, 7, 1, 4]
[5, 3, 0, 4, 7, 1, 6, 2]
[5, 3, 1, 7, 4, 6, 0, 2]
[5, 3, 6, 0, 2, 4, 1, 7]
[5, 3, 6, 0, 7, 1, 4, 2]
[5, 7, 1, 3, 0, 6, 4, 2]
[6, 0, 2, 7, 5, 3, 1, 4]
[6, 1, 3, 0, 7, 4, 2, 5]
[6, 1, 5, 2, 0, 3, 7, 4]
[6, 2, 0, 5, 7, 4, 1, 3]
[6, 2, 7, 1, 4, 0, 5, 3]
[6, 3, 1, 4, 7, 0, 2, 5]
[6, 3, 1, 7, 5, 0, 2, 4]
[6, 4, 2, 0, 5, 7, 1, 3]
[7, 1, 3, 0, 6, 4, 2, 5]
[7, 1, 4, 2, 0, 6, 3, 5]
[7, 2, 0, 5, 1, 4, 6, 3]
[7, 3, 0, 2, 5, 1, 6, 4]

最后最后,对比其他语言解决八皇后的代码量

以上这篇python 使用递归回溯完美解决八皇后的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python+Pika+RabbitMQ环境部署及实现工作队列的实例教程
Jun 29 Python
判断网页编码的方法python版
Aug 12 Python
Python代码块批量添加Tab缩进的方法
Jun 25 Python
Python/ArcPy遍历指定目录中的MDB文件方法
Oct 27 Python
解决在pycharm运行代码,调用CMD窗口的命令运行显示乱码问题
Aug 23 Python
利用Python的sympy包求解一元三次方程示例
Nov 22 Python
Pytorch实现基于CharRNN的文本分类与生成示例
Jan 08 Python
Python开发之pip安装及使用方法详解
Feb 21 Python
Python3.7.0 Shell添加清屏快捷键的实现示例
Mar 23 Python
详解python中groupby函数通俗易懂
May 14 Python
浅谈Python中的生成器和迭代器
Jun 19 Python
Python实现JS解密并爬取某音漫客网站
Oct 23 Python
基于Python数据结构之递归与回溯搜索
Feb 26 #Python
深度学习入门之Pytorch 数据增强的实现
Feb 26 #Python
Python基于Dlib的人脸识别系统的实现
Feb 26 #Python
python 回溯法模板详解
Feb 26 #Python
python实现信号时域统计特征提取代码
Feb 26 #Python
Python 基于FIR实现Hilbert滤波器求信号包络详解
Feb 26 #Python
python实现逆滤波与维纳滤波示例
Feb 26 #Python
You might like
php中过滤非法字符的具体实现
2013/10/29 PHP
smarty模板引擎中内建函数if、elseif和else的使用方法
2015/01/22 PHP
PHP使用ffmpeg给视频增加字幕显示的方法
2015/03/12 PHP
Laravel 5.5基于内置的Auth模块实现前后台登陆详解
2017/12/21 PHP
JavaScript日历实现代码
2010/09/12 Javascript
仅IE支持clearAttributes/mergeAttributes方法使用介绍
2012/05/04 Javascript
浅谈javascript面向对象程序设计
2015/01/21 Javascript
使用jQuery实现返回顶部
2015/01/26 Javascript
js+html5实现canvas绘制镂空字体文本的方法
2015/06/05 Javascript
JS实现仿苹果底部任务栏菜单效果代码
2015/08/28 Javascript
jquery实现网站列表切换效果的2种方法
2016/08/12 Javascript
基于JavaScript实现熔岩灯效果导航菜单
2017/01/04 Javascript
JavaScript轮播图简单制作方法
2017/02/20 Javascript
bootstrap动态添加面包屑(breadcrumb)及其响应事件的方法
2017/05/25 Javascript
原生JS实现Ajax跨域请求flask响应内容
2017/10/24 Javascript
JavaScript new对象的四个过程实例浅析
2018/07/31 Javascript
JavaScript设计模式之享元模式实例详解
2019/01/17 Javascript
微信小程序websocket实现即时聊天功能
2019/05/21 Javascript
微信小程序判断页面是否从其他页面返回的实例代码
2019/07/03 Javascript
Vue 权限控制的两种方法(路由验证)
2019/08/16 Javascript
javascript实现弹幕墙效果
2019/11/28 Javascript
[05:15]DOTA2英雄梦之声_第16期_灰烬之灵
2014/06/21 DOTA
[41:41]TFT vs Secret Supermajor小组赛C组 BO3 第一场 6.3
2018/06/04 DOTA
python3利用tcp实现文件夹远程传输
2018/07/28 Python
tensorflow 限制显存大小的实现
2020/02/03 Python
使用python计算三角形的斜边例子
2020/04/15 Python
python实现经纬度采样的示例代码
2020/12/10 Python
亚历山大·王官网:Alexander Wang
2017/06/23 全球购物
Furla官网:意大利著名的皮革品牌
2019/08/06 全球购物
诚信考试倡议书
2014/04/15 职场文书
合作意向协议书
2015/01/29 职场文书
2015秋季开学演讲稿范文
2015/07/16 职场文书
感恩父母主题班会
2015/08/12 职场文书
2016年大学生实习单位评语
2015/12/01 职场文书
MySQL分库分表与分区的入门指南
2021/04/22 MySQL
python使用PySimpleGUI设置进度条及控件使用
2021/06/10 Python