昨天失败了,看了一晚上还是没搞定,但至少有头目了。
讲八皇后之前,我还是先来一个简单的递归生成器:
我给自己标记下分析的思路,怕到时候又忘记了,这是一个多层列表的去除[]剥皮器。
def flatten(nested): try: for sublist in nested: for element in flatten(sublist): # print(element) yield element # 主输出,用for循环接受。 except TypeError: yield nested # 子返回
这个看似乎非常简单的递归生成器,本来我以为我看懂了,没仔细分析,后面仔细分析了一下,其实我没懂,再大把力气花下去,又懂了
这种递归生成器用了双yield,其实这里面会让人陷进去,我以前也写过一些简单的递归,一般都是用return,这次的双yield,把我还苦了
因为后面的八皇后递归生成器也是双yield的格式,如果用递归生成器可能也只能用yield,
记住一点yield的返回值需要用next, send(None), 或者案例里面通过for循环取值,其实案例用next取值,可能我分析的时候可以少走很多弯路。
经过测试next取值失败,next取值无法对列表套列表里面的元素全部返回值,只会返回列表套列表的元素里面第一个值。
这个可能跟for 循环取值的原理不一样导致,for循环取值是一次会把一个列表内列表元素内的yield的取值逐一全部取出。
下面上错误案例:
def flatten(nested): try: for sublist in nested: element = next(flatten(sublist)) # print(element) yield element # for element in flatten(sublist): # print(element) # yield element except TypeError: yield nested a = list(flatten([1, [[[[2]]], 5, [3,[4]]], [4]])) print(a) b = flatten([[2,3]]) print(list(b))
这个是失败的案例,输出:
[1, 2, 4] [2]
了解列表脱壳器,我对递归生成器又了一个初步了了解与定义,
一般又两个yield,一个子yield是到了基线条件,触发后,返回给父yield,父yield通过for 循环取值,层层传递给上层的出口,知道返回最表层,最外层父级yelid返回数据。
所以看这个递归生成器,首先找出基线条件,然后找出子yield,找出父yield,虽然我能看懂,但让我写,还真的写不出来。
有了这个基础,八皇后的代码,我自己分析就没啥大问题了。
递归很多时候我看的都已经头痛的,外加生成器一起,痛上加痛,晚上再花两个小时痛好,睡觉。()失败完结,今天再来
问题概述:
在N×N格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,输出所有摆法。
'''定义一个列表,如果皇后在第1行第三个元素,那么state[0] = 3可以简单称呼下标为行,内容为列,下标肯定不会重复,列表元素也不能重复(必要条件)如果定义二维平面x,y,则y轴不可能重叠,重点在x轴'''定义一个规则,什么样子的条件才是满足下一个皇后的位置:
def conflict(state, nextX): # state可以是前面定义的元祖,nextX定义为x坐标 nextY = len(state) '''定义了nextX后,它将与前面所有的皇后对比,如果与前面任意一个皇后条件成立,直接break,返回True''' for i in range(nextY): # i表示前面皇后的y轴参数 '''这个是判断是否列相等,还有第二个nextY-i是判断是否在同一个对角线''' if abs(state[i] - nextX) in (0, nextY - i): # nextY表示下一个皇后的Y轴参数 return True return False # 返回错误说明这下一个皇后位置符合标准
后续的使用测试都要调用这个函数,再上一个已经全部知道前面坐标,只剩下最后坐标的情况
def queens_t(num, state): if len(state) == num-1: # 查询是不是最后一个皇后了 for pos in range(num): # 让最后一个皇后尝试去每一列取值 if not conflict(state, pos): # 如果有符合标准的列参数就返回。 yield pos print(list(queens_t(4,(1, 3, 0))))
这个代码逻辑比较简单。
这个是完整代码,整个逻辑里面注释非常丰富了,其实递归再一次一次的尝试,太多的失败了,只有激活了基线条件,才层层再返回数据,退出
count_b = 0 # 初始化中间的试错次数 count_s = 0 # 初始化最后结尾的试错次数 def queens(num, state): if len(state) == num-1: # 查询是不是最后一个皇后了,变成了递归返回的基准条件。 for pos in range(num): # 让最后一个皇后尝试去每一列取值 if not conflict(state, pos): # 如果有符合标准的列参数就返回。 yield (pos,) # 这是一个达标的数据的返回数据口,如果这部完成,说明这是一条合格的皇后链条 else: global count_s count_s += 1 # 易筋经尝试到最后一步失败的次数 else: for pos in range(num): # 如果不是最后一个,循环pos if not conflict(state, pos): # 确实该pos是否有效,有效就跑,无效就放弃 # result = next(queens(num, state + (pos,))) # 通过next取值又失败,这次我自己分析,应该是next取值无法定位 # 深度,因为这次失败是取值报错,不想用try,except再测试了 # print(pos) # yield (pos,) + result for result in queens(num, state + (pos,)): # 通过for循环取值,层层返回数据,定义N皇后,返回N-1层 # print((pos,) + result) yield (pos,) + result else: # 其实中间还有很多半途而废的测试,因为测试没有通过直接放弃了,进入下一轮循环 global count_b # 中间的测试过程种失败的次数 count_b += 1 num = 8 print(list(queens(num,()))) # print(len(list(queens(num,())))) print(count_b) print(count_s)
后面再上个代码优化版本的:
count_ss = 0 def queens_simple(num=8, state=()): for pos in range(num): # 主循环开始,该循环刚好可以定位每个元素的任意列坐标 if not conflict(state, pos): # 任意进来的元素必须符合规则 if len(state) == num-1: # 基线条件返回,达到基线条件,(该输出为最后一个元素) yield (pos,) else: '''进入递归,看能否测试达到基本条件,没达标就放弃,达标就通过for循环取值,层层返回''' for result in queens_simple(num, state+(pos,)): yield (pos,) + result else: global count_ss # 定义失败次数,与非简化版对比次数是否一致。 count_ss += 1 print(list(queens_simple()))
两个代码输出的结果都是一致的,包括试错的次数。
最后再次总结:
个人总结,在递归生成式中,比较重要的几个条件,第一想好基线条件,因为基线条件是返回正确数据的出口
第二,要想好正确路径返回的数据接受,这里用for循环接收子yield还是非常好的,数据的接受如果需要合并,想好格式(必须要有返回值的)
另外暂时没有啥了,两个yiled也不说了,一个返回基线条件数据,一个返回主数据。
很开森,总算马马虎虎算搞定了,至少能看懂。
下面上两个链接人家的说明,我的看不懂,看人家的试试,我是看人家的写法,我看不懂。
https://blog.csdn.net/qq_33085753/article/details/86592361
https://blog.csdn.net/wang903039690/article/details/79706311