python八皇后问题递归算法_Python----递归------Eight Queens 八皇后问题

递归思想是算法编程中的重要思想。

作为初学者,对递归编程表示很蒙逼,每次遇到需要递归的问题,心里就有一万头草泥马飞过~~~~~~(此处略去一万头草泥马)

在B站看数据结构与算法的视频时,视频中给了两个非常典型的例子——《汉诺塔》和《八皇后问题》,就希望自己用Python实现一下这两个递归程序,其中汉诺塔问题比较简单,还是能够理解,这里就不讲了。

《八皇后问题》:说要在一个棋盘上放置8个皇后,但是不能发生战争,皇后们都小心眼,都爱争风吃醋,如果有人和自己在一条线上(水平、垂直、对角线)就会引发撕13大战,所以我们就是要妥当的安排8位娘娘,以保后宫太平。[1]

与网上普遍搜到的利用yield函数完成递归方法不同,注意时因为对于初学Python的我来说,完全搞不懂yield怎么用,更不要说将其放在递归程序里了, [捂脸]

下面我会对我的代码进行逐一解释,希望更多的像我这样的初学者能够看懂。!!!敲黑板!!!------------不仅要看懂,还要自己会写代码-----------------

首先声明:

代码中位置状况采用list列表的形式, 如,chess[0] = 4代表在棋盘第1行的第5个位置放置皇后。

count = 0                   #定义一个全局变量count, 用于八皇后方案的统计

#conflict 函数为冲突函数,主要用于判断新添加的位置是否与前面的位置冲突#pos 为新添加的位置, chess为添加pos位置前的皇后位置分布

defconflict(pos, chess):

len_chess= len(chess)       #现在已经分配了几行,几有几个皇后在棋盘上,因为初始chess为空,每次判断一个符合规则的位置后再加入到棋盘中

for i in range(len_chess):if abs(chess[i] - pos) in (0, len_chess - i): #这一行为关键,太多了就放后面了

returnTruereturnFalse#Queen函数为递归函数

defQueens(num,chess):global count         #python 中要再函数中使用之前定义的全局变量,必须再函数中用 global 声明一下

if len(chess) == 8:          #判断chess的长度,当chess长度为8的时候,说明8个皇后都已经全部放好位置了,就可以直接输出相应的方案了

count += 1

print(chess)else:                 #chess长度小于8,说明还有没安排好的皇后,所以继续安排剩下的位置

for pos in range(num):      #对接下来的一行的8个位置进行遍历

if not conflict(pos, chess): #对每个位置进行冲突判断

chess1 = []   #关键是这三行,用于构造棋盘的副本,这三句非常重要,具体原因放在后面解释

for i inchess:

chess1.append(i)

chess1.append(pos)#符合规则,则将其附在chess副本的后面

Queens(num,chess1)   #递归调用Queens函数

board= [] #初始化空棋盘

Queens(8,board) #调用函数

print(count) #打印总共有多少中方案

冲突判断

defconflict(pos, chess):

len_chess= len(chess) #现在已经分配了几行,几有几个皇后在棋盘上,因为初始chess为空,每次判断一个符合规则的位置后再加入到棋盘中

for i in range(len_chess):if abs(chess[i] - pos) in (0, len_chess - i): #这一行为关键,太多了就放后面了

returnTruereturn False

根据规则:有三种情况存在冲突,我i们一一来列举分析一下:

在同一列的情况,若两个皇后在同一列,那么这两行的纵坐标相同,反映到我们的棋盘定义上则未:chess[i] = chess[j]即第i行与第j行的两个皇后在同一列了

第二种情况则为在“\” 对角线上,那么各位置的(横坐标 - 纵坐标)的值时一样的,假设chess[i] = a1,chess[j] = a2在对角线"\"上,i - a1 = j - a2。 如(0,1),(1,2),(2,3)横纵坐标之差都为1

第三种情况则为在" / " 对角线上,那么各位置的(横坐标 + 纵坐标)的值时一样的,假设chess[i] = a1,chess[j] = a2在对角线"\"上,i + a1 = j + a2。 如(0,3),(1,2),(2,1)横纵坐标之和都为1

我们再观察,就会发现(这个我自己想时想不出来的,也是对照着代码思考出来的):

对于被判断是否符合要求的元素,其索引恰好为现在chess的长度,因为最后一个元素的索引为len(chess)-1,那么新的元素的索引恰为len(chess)

我们另 len_chess 为当前chess的长度,则三种情况对应的表达式为:

chess[i] - pos = 0

chess[i] - i = pos - len_chess   --------->   chess[i] - pos = i-len_chess

chess[i] + i = pos + len_chess  ---------> chess[i] - pos = len_chess - i

由于 len_chess > i, 所以可以得到当abs(chess[i] - pos)==0 或len_chess - i时则不符合规则,返回True :

棋盘副本建立

for pos in range(num): #对接下来的一行的8个位置进行遍历

if not conflict(pos, chess): #对每个位置进行冲突判断

chess1 = [] #关键是这三行,用于构造棋盘的副本,这三句非常重要,具体原因放在后面解释

for i inchess:

chess1.append(i)

chess1.append(pos)#符合规则,则将其附在chess副本的后面

Queens(num,chess1) #递归调用Queens函数

这里首先提一点的是,python里面所有如果要将一个list拷贝一份,不能直接像C语言里面一样用幅值语句,需要重新初化,

我个人的理解是:因为list的名称为一个地址,当我们将一个地址幅值给一个新的变量时,新的变量指向的还是原来那个数组,所以并没有起到拷贝的作用。 比较典型的例子就是

言归正传,我们为什么要新建立一个副本呢?

因为我们需要使用副本来进行深层次的递归,如果不使用副本chess1而直接使用原始数据chess,那么随着递归的深入,chess所对应的地址内的值在不断变化,当返回到递归点的时候,原始数据已经不存在了,

只会从当前chess对应的数组继续进行递归。

比如,当 chess = [0,2,4,1,3]时,此时我们需要填充第6行皇后的位置,但是通过判断,发现第6行所有位置都不符合要求,那么此时程序应该退回chess = [0,2,4,1,4]再继续判断第6行是否有符合的位置,但是若不用副本

的话,程序会退回,但是chess所对应的值还是[0,2,4,1,3],这样是错误的。

QAQ: 这一段我也没有讲的特别清楚,主要关键就是这个副本的建立使原始数据不受破坏,可以在递归从深层跳出的时候保持为原来的状态继续进行,可以在编译器中单步运行看一看再理解会更好

[1]. 摘自http://www.cnblogs.com/littleseven/p/5362791.html, 斯认为该博主写的还可以,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值