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

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

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

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

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

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


首先声明:

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

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

# conflict 函数为冲突函数,主要用于判断新添加的位置是否与前面的位置冲突
# pos 为新添加的位置, chess为添加pos位置前的皇后位置分布
def conflict(pos, chess):
len_chess = len(chess)       # 现在已经分配了几行,几有几个皇后在棋盘上,因为初始chess为空,每次判断一个符合规则的位置后再加入到棋盘中
for i in range(len_chess):if abs(chess[i] - pos) in (0, len_chess - i): # 这一行为关键,太多了就放后面了
return True
return False # Queen函数为递归函数
def Queens(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 in chess:
chess1.append(i)
chess1.append(pos)   # 符合规则,则将其附在chess副本的后面
Queens(num,chess1)   # 递归调用Queens函数 board = [] # 初始化空棋盘
Queens(8,board) # 调用函数
print(count) # 打印总共有多少中方案

冲突判断

def conflict(pos, chess):
len_chess = len(chess) # 现在已经分配了几行,几有几个皇后在棋盘上,因为初始chess为空,每次判断一个符合规则的位置后再加入到棋盘中
for i in range(len_chess):if abs(chess[i] - pos) in (0, len_chess - i): # 这一行为关键,太多了就放后面了
return True
return False

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

  1. 在同一列的情况,若两个皇后在同一列,那么这两行的纵坐标相同,反映到我们的棋盘定义上则未:chess[i] = chess[j] 即第i行与第j行的两个皇后在同一列了
  2. 第二种情况则为在“\” 对角线上,那么各位置的(横坐标 - 纵坐标)的值时一样的,假设chess[i] = a1,chess[j] = a2在对角线"\"上,i - a1 = j - a2。 如(0,1),(1,2),(2,3)横纵坐标之差都为1
  3. 第三种情况则为在" / " 对角线上,那么各位置的(横坐标 + 纵坐标)的值时一样的,假设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的长度,则三种情况对应的表达式为:

  1. chess[i] - pos = 0
  2. chess[i] - i = pos - len_chess   --------->   chess[i] - pos = i-len_chess
  3. 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 in chess:
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, 斯认为该博主写的还可以,

最新文章

  1. iOS XCode7制作.Framework动态库和.a静态库的总结
  2. .net core 使用
  3. lintcode:Search Insert Position 搜索插入位置
  4. hdu 1800 Flying to the Mars
  5. Node v4.1.1
  6. div+css知识点
  7. IntelliJ IDEA 出现Unable to execute DX 问题
  8. Android系统智能指针的设计思路(轻量级指针、强指针、弱指针)
  9. maven:pom.xml中没有dependency标签错误
  10. Python系列之入门篇——MYSQL
  11. QUIC:基于udp的传输新技术
  12. Keep On Movin (贪心)
  13. Twitter OA prepare: Two Operations
  14. 论文笔记:语音情感识别(二)声谱图+CRNN
  15. Spring AMQP 源码分析 05 - 异常处理
  16. ccfZ字形扫描
  17. css sprite实例
  18. PHP设计模式系列 - 适配器
  19. 42.oracle物化视图
  20. 5.11-笨办法学python-习题13(argv)

热门文章

  1. 加壳学习笔记(三)-简单的脱壳思路&调试思路
  2. 【poj 1724】 ROADS 最短路(dijkstra+优先队列)
  3. [Berkeley]弹性分布式数据集RDD的介绍(RDD: A Fault-Tolerant Abstraction for In-Memory Cluster Computing 论文翻译)
  4. Linux如何使用cURL分割下载大文件
  5. regEx in Groovy
  6. Android中关于横竖屏问题
  7. Quartz.net使用入门(三)
  8. 查看mysql是否安装成功和mysql的版本信息
  9. handbook/CentOS/使用免费SSL证书让网站支持HTTPS访问.md
  10. Vue 柱状图