• 生成器递归解决八皇后问题(勉强理解)


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

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

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

    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取值无法对列表套列表里面的元素全部返回值,只会返回列表套列表的元素里面第一个值。

    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

  • 相关阅读:
    oh forever love~
    PostgreSQL数据库忘记密码的解决方案
    vue
    无法启动postgresql的错误
    Goaccess解析nginx日志备忘
    pg备份恢复与设置编码
    django+uwsgi+nginx+postgresql备忘
    Git 直接推送到生产服务器
    为了性能, 数据操作尽量在数据库层面进行
    简单理解call_user_func和call_user_func_array两个函数
  • 原文地址:https://www.cnblogs.com/sidianok/p/11802800.html
Copyright © 2020-2023  润新知