生成器递归解决八皇后问题(基本属于抄题,勉强理解)

十年热恋 提交于 2019-12-03 15:50:46

昨天失败了,看了一晚上还是没搞定,但至少有头目了。

讲八皇后之前,我还是先来一个简单的递归生成器:

我给自己标记下分析的思路,怕到时候又忘记了,这是一个多层列表的去除[]剥皮器。

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!