一、生成器的回溯(Generator Traceback)
对于逐步得到结果的复杂递归算法,非常适用生成器来实现。
要在不使用生成器的情况下实现这些算法,通常必须通过额外的参数来传递部分结果,让递归能够接着往下计算。
通过使用生成器,所有的递归调用都只需生成其负责部分的结果。
二、问题
八皇后:你需要将8个皇后放在棋盘上,条件是任何一个皇后都不能威胁其他皇后,即不能在同一列和对角线。
这是一个典型的回溯问题:在棋盘的第一行尝试为第一个皇后选择一个位置,再在第二行尝试为第二个皇后找一个位置,以此类推。在发现无法为一个皇后选择合适的位置后,回溯到前一个皇后,并尝试为她找另一个位置。最后,要么尝试完所有的可能性,要么找到答案。
当然现实中的问题可能不仅仅是八个,可以是任意多个。
三、状态表示
可简单的使用元组(或列表)来表示可能的解(或其中一部分),其中每个元素表示相应行中皇后所在的位置(列)。
如state[0] == 3,就说明第一行的皇后放在第四列(因为是从0开始计数的,范围[0,7])。
在特定的递归层级(特定的行),你只知道上面各皇后的位置,因此状态元组的长度小于8(即皇后总数)。
四、检测冲突
函数conflict(冲突)接受(用状态元组表示的)既有皇后的位置,并确定下一个皇后的位置是否会导致冲突。
1 def conflict(state,nextX): #conflict 冲突 2 nextY = len(state) # num = [1,2,3] len(num) == 3 3 for i in range(nextY):# range取不到nextY,range(3)取:0,1,2 4 if abs(state[i] - nextX) in (0, nextY - i): # abs()取绝对值 5 return True 6 return False
nextX表示下一个皇后的水平位置(X坐标,即列);
nextY表示下一个皇后的垂直位置(y坐标,即行)
该函数对既有的每个皇后执行简单的检查:如果下一个皇后与当前皇后的x坐标相同或在同一条对角线上,将发生冲突,因此返回True;如果没有冲突就返回False。
abs(state[i] - nextX) in (0, nextY - i)
表示:如果下一个皇后和当前皇后的水平距离为0(在同一列)或与它们的垂直距离(位于一条对角线上),这个表达式为真,否则为假。
五、基线条件
最后一个皇后
def queens(num, state): if len(state) == num - 1: for pos in range(num): # pos:位置 if not conflict(state, pos): yield pos
如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那些不会引发冲突的位置。
num为皇后总数
state是一个元组,包含已放好的皇后的位置。
六、递归条件
你希望递归调用返回什么样的结果?你希望它返回当前行下面所有皇后的位置。
假设这些位置是以元组的方式返回的,因此需要修改基线条件,使其返回一个(长度为1的)元组。
对于递归调用,向它提供的是由当前行上面的额皇后位置组成的元组。对于当前换后的每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。
为了让这个过程不断进行下去,只需将当前皇后的位置插入返回的结果开头。
def queens(num, state): if len(state) == num - 1: for pos in range(num): if not conflict(state, pos): yield pos else: for pos in range(num): if not conflict(state, pos): for result in queens(num, state + (pos, )) yield (pos,) + result
这里的for pos和if not conflict部分与前面相同,所以可以简化代码:
def queens(num = 8, state = ()): if pos in range(num): if not conflict(state, pos): if len(state) == num - 1: yield (pos,) else: for result in queens(num. state + (pos,)): yield (pos,) + result
七、清晰的输出
def pretyprint(solution): def line(pos, length = len(solution)): return '. '*(pos) + 'X '+'. '*(length - pos - 1) for pos in solution: print(line(pos)) pretyprint(random.choice(list(queens(8))))