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迭代用法实例教程
Sep 08 Python
Python之ReportLab绘制条形码和二维码的实例
Jan 15 Python
pyqt5中QThread在使用时出现重复emit的实例
Jun 21 Python
python对象销毁实例(垃圾回收)
Jan 16 Python
双向RNN:bidirectional_dynamic_rnn()函数的使用详解
Jan 20 Python
python中pandas库中DataFrame对行和列的操作使用方法示例
Jun 14 Python
django 实现后台从富文本提取纯文本
Jul 02 Python
详解用python -m http.server搭一个简易的本地局域网
Sep 24 Python
python Matplotlib数据可视化(2):详解三大容器对象与常用设置
Sep 30 Python
Python tempfile模块生成临时文件和临时目录
Sep 30 Python
matplotlib运行时配置(Runtime Configuration,rc)参数rcParams解析
Jan 05 Python
浅谈Python3中datetime不同时区转换介绍与踩坑
Aug 02 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中header的用法详解
2013/06/07 PHP
帝国cms目录结构分享
2015/07/06 PHP
laravel5.6框架操作数据curd写法(查询构建器)实例分析
2020/01/26 PHP
jQuery动态添加的元素绑定事件处理函数代码
2011/08/02 Javascript
JavaScript把数组作为堆栈使用的方法
2015/03/20 Javascript
详解JavaScript逻辑Not运算符
2015/12/04 Javascript
jQuery插件Validate实现自定义表单验证
2016/01/18 Javascript
VueJs单页应用实现微信网页授权及微信分享功能示例
2017/07/26 Javascript
angularJS的radio实现单项二选一的使用方法
2018/02/28 Javascript
webpack的pitching loader详解
2019/09/23 Javascript
微信小程序点击滚动到指定位置的实现
2020/05/22 Javascript
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
2020/10/09 Javascript
使用基于Python的Tornado框架的HTTP客户端的教程
2015/04/24 Python
python图片验证码生成代码
2016/07/02 Python
Pycharm学习教程(4) Python解释器的相关配置
2017/05/03 Python
浅谈python3.6的tkinter运行问题
2019/02/22 Python
python中多个装饰器的调用顺序详解
2019/07/16 Python
Python字符编码转码之GBK,UTF8互转
2020/02/09 Python
Python写出新冠状病毒确诊人数地图的方法
2020/02/12 Python
解决python 找不到module的问题
2020/02/12 Python
DataFrame.groupby()所见的各种用法详解
2020/06/14 Python
Django中Q查询及Q()对象 F查询及F()对象用法
2020/07/09 Python
Python3使用tesserocr识别字母数字验证码的实现
2021/01/29 Python
Foot Locker意大利官网:全球领先的运动鞋和服装零售商
2017/05/30 全球购物
英国最大的香水商店:The Fragrance Shop
2018/07/06 全球购物
俄罗斯奢侈品牌衣服、鞋子和配饰的在线商店:INTERMODA
2020/07/17 全球购物
个人简历中自我评价
2014/02/11 职场文书
四年大学自我鉴定
2014/02/17 职场文书
销售经理竞聘书
2014/03/31 职场文书
新闻稿件写作范文
2015/07/18 职场文书
小学语文教师竞聘演讲稿范文
2019/08/09 职场文书
MySQL令人咋舌的隐式转换
2021/04/05 MySQL
go:垃圾回收GC触发条件详解
2021/04/24 Golang
Nginx静态压缩和代码压缩提高访问速度详解
2022/05/30 Servers
python数字图像处理之对比度与亮度调整示例
2022/06/28 Python
新的CSS 伪类函数 :is() 和 :where()示例详解
2022/08/05 HTML / CSS