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的Tornado框架的HTTP客户端的教程
Apr 24 Python
在Python的web框架中编写创建日志的程序的教程
Apr 30 Python
Python+微信接口实现运维报警
Aug 27 Python
django上传图片并生成缩略图方法示例
Dec 11 Python
在Python中居然可以定义两个同名通参数的函数
Jan 31 Python
python多线程调用exit无法退出的解决方法
Feb 18 Python
用Python从0开始实现一个中文拼音输入法的思路详解
Jul 20 Python
python getpass模块用法及实例详解
Oct 07 Python
Tensorflow的常用矩阵生成方式
Jan 04 Python
python将dict中的unicode打印成中文实例
May 11 Python
解决pycharm debug时界面下方不出现step等按钮及变量值的问题
Jun 09 Python
Python时间操作之pytz模块使用详解
Jun 14 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身份证号码检查类实例
2015/06/18 PHP
初识PHP中的Swoole
2016/04/05 PHP
php获取目录下所有文件及目录(多种方法)(推荐)
2019/05/14 PHP
jQuery Ajax之load()方法
2009/10/12 Javascript
JS面向对象编程之对象使用分析
2010/08/19 Javascript
jQuery Tab插件 用于在Tab中显示iframe,附源码和详细说明
2011/06/27 Javascript
深入理解JavaScript系列(15) 函数(Functions)
2012/04/12 Javascript
通过location.replace禁止浏览器后退防止重复提交
2014/09/04 Javascript
jQuery中eq()方法用法实例
2015/01/05 Javascript
Jquery遍历Json数据的方法
2015/04/20 Javascript
jQuery移动web开发中的页面初始化与加载事件
2015/12/03 Javascript
jQuery实现圣诞节礼物动画案例解析
2016/12/25 Javascript
jQuery实现鼠标经过显示动画边框特效
2017/03/24 jQuery
thinkjs微信中控之微信鉴权登陆的实现代码
2019/08/08 Javascript
基于js实现逐步显示文字输出代码实例
2020/04/02 Javascript
基于javascript实现移动端轮播图效果
2020/12/21 Javascript
[01:35]辉夜杯战队访谈宣传片—LGD
2015/12/25 DOTA
python实现k均值算法示例(k均值聚类算法)
2014/03/16 Python
Python开发WebService系列教程之REST,web.py,eurasia,Django
2014/06/30 Python
python实现多线程采集的2个代码例子
2014/07/07 Python
opencv python 基于KNN的手写体识别的实例
2018/08/03 Python
对Python中plt的画图函数详解
2018/11/07 Python
django框架防止XSS注入的方法分析
2019/06/21 Python
使用Python制作表情包实现换脸功能
2019/07/19 Python
对Python3中列表乘以某一个数的示例详解
2019/07/20 Python
python爬虫 线程池创建并获取文件代码实例
2019/09/28 Python
Python使用pdb调试代码的技巧
2020/05/03 Python
Python的collections模块真的很好用
2021/03/01 Python
L’AGENCE官网:加州女装品牌
2018/06/03 全球购物
FragranceNet中文网:北美健康美容线上零售商
2020/08/26 全球购物
党员批评与自我批评思想汇报
2014/10/08 职场文书
2015年中学校长工作总结
2015/05/19 职场文书
太行山上观后感
2015/06/05 职场文书
初中班主任培训心得体会
2016/01/07 职场文书
Linux中Nginx的防盗链和优化的实现代码
2021/06/20 Servers
Python实现双向链表基本操作
2022/05/25 Python