----用教授的方法学习
目录
1.八皇后问题
2.状态表示(抽象)
3.检测冲突
4.基线条件
5.递归条件
6.结尾
1.八皇后问题
深受大家喜爱的计算机科学谜题:你需要将8个皇后放在棋盘上,条件是任何一个皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。怎样才能做到这一点呢?应将这些皇后放在什么地方呢?
这是一个典型的回溯问题:在棋盘的第一行尝试为第一个皇后选择一个位置,再在第二行尝试为第二个皇后选择一个位置,依次类推。在发现无法为一个皇后选择合适的位置后,回溯到前一个皇后,并尝试为它选择另一个位置。最后,要么尝试完所有的可能性,要么找到了答案。
在前面描述的问题中,只有8个皇后,但这里假设可以有任意数量的皇后,从而更像现实世界的回溯问题。如何解决这个问题呢?
2.状态表示(抽象)
可简单地使用元组(或列表)来表示可能的解(或其一部分),其中每个元素表示相应行中皇后所在的位置(即列)。因此,如果state[0] == 3,就说明第1行的皇后放在第4列(还记得吧,我们从0开始计数)。在特定的递归层级(特定的行),你只知道上面各皇后的位置,因此状态元组的长度小于8(即皇后总数)。
3.检测冲突
先来做些简单的抽象。要找出没有冲突(即任何一个皇后都吃不到其他皇后)的位置组合,首先必须定义冲突是什么。为何不使用一个函数来定义呢?
函数conflict接受(用状态元组表示的)既有皇后的位置,并确定下一个皇后的位置是否会导致冲突。
def conflict(state, nextX): nextY = len(state) for i in range(nextY): if abs(state[i] - nextX) in (0, nextY - i): return True return False
参数nextX表示下一个皇后的水平位置(x坐标,即列),而nextY为下一个皇后的垂直位置(y坐标,即行)。这个函数对既有的每个皇后执行简单的检查:如果下一个皇后与当前皇后的x坐标相同或在同一条对角线上,将发生冲突,因此返回True;如果没有发生冲突,就返回False。比较难理解的是下面的表达式:
abs(state[i] - nextX) in (0, nextY - i)
如果下一个皇后和当前皇后的水平距离为0(在同一列)或与它们的垂直距离相等(位于一条对角线上),这个表达式就为真;否则为假。
4.基线条件
基线条件:最后一个皇后。对于这个皇后,你想如何处理呢?假设你想找出所有可能的解——给定其他皇后的位置,可将这个皇后放在什么位置(可能什么位置都不行)?可以这样编写代码。
def queens(num, state): if len(state) == num-1: for pos in range(num): if not conflict(state, pos): yield pos
这段代码的意思是,如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那些不会引发冲突的位置。参数num为皇后总数,而参数state是一个元组,包含已放好的皇后的位置。例如,假设总共有4个皇后,而前3个皇后的位置分别为1、3和0.
从该图可知,每个皇后都占据一行,而皇后的位置是从0开始编号的
>>> list(queens(4, (1, 3, 0))) [2] |
代码的效果很好。这里使用list旨在让生成器生成所有的值。在这个示例中,只有一个位置符合条件。在图9-1中,在这个位置放置了一个白色皇后。(请注意,颜色没有什么特殊含义,不是程序的一部分。)
5.递归条件
现在来看看这个解决方案的递归部分。处理好基线条件后,可在递归条件中假设来自更低层级(编号更大的皇后)的结果都是正确的。因此,只需在函数queens的前述实现中给if语句添加一个else子句。
递归调用,向它提供的是由当前行上面的皇后位置组成的元组。对于当前皇后的每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。为了让这个过程不断进行下去,只需将当前皇后的位置插入返回的结果开头,如下所示:
...
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=()): for 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
如果你觉得这些代码难以理解,用自己的话来描述其作用可能会有所帮助。另外,你可能还记得(pos,)中的逗号必不可少(不能仅用圆括号将pos括起),这样得到的才是元组。
生成器queens提供了所有的解(即所有合法的皇后位置组合)。
>>> list(queens(3)) [] >>> list(queens(4)) [(1, 3, 0, 2), (2, 0, 3, 1)] >>> for solution in queens(8): ... print solution ... (0, 4, 7, 5, 2, 6, 1, 3) (0, 5, 7, 2, 6, 3, 1, 4) ... (7, 2, 0, 5, 1, 4, 6, 3) (7, 3, 0, 2, 5, 1, 6, 4) >>> |
如果运行queens时将参数num设置为8,将快速显示大量的解。下面看看有多少个解。
>>> len(list(queens(8))) |
6.结尾
可以让输出更容易理解些。在任何情况下,清晰的输出都是好事,因为这让查找bug等工作更容易。
def prettyprint(solution): def line(pos, length=len(solution)): return '. ' * (pos) + 'X ' + '. ' * (length-pos-1) for pos in solution: print(line(pos))
请注意,我在prettyprint中创建了一个简单的辅助函数。之所以将它放在prettyprint中,
是因为我认为在其他地方都用不到它。下面随机地选择一个解,并将其打印出来,以确定它是正
确的。
>>> import random >>> prettyprint(random.choice(list(queens(8)))) . . . . . X . . . X . . . . . . . . . . . . X . X . . . . . . . . . . X . . . . . . . . . . . X . . . . X . . . . . X . . . . . |
----end