像我这种对奇特的语言特性很热衷,对算法本身并不热衷也并不擅长的人,似乎很难去写一篇关于算法题的文章。好在我还有一个特点就是不管写了多么烂的代码都会拿出来得瑟一下,不怕被拍砖,于是便来分享一下关于使用Python中的yield
来解决八皇后问题的心得。
Python生成器
PEP 255中详细介绍了Python Generator。简单来说它可以将一个函数当作可以迭代的对象来使用,此举真是将高阶函数的特性发挥得淋漓尽致(Java什么的一边玩去吧)。一个很简单的例子:
def fib():
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
这能够产生一个无穷尽的菲波纳契数列,可以使用
for
死循环般地遍历它:
for i in fib():
print i
yield
关键字的要诀就是替换
return
,但是它只是中途返回一个值,而不是将整个函数返回,第二次调用这个函数的时候,会从
yield
之后的地方继续运行。听上去跟闭包(Closures)颇类似呢。
八皇后问题
这其实是个讨论得很滥的问题了,所以人们一般称它为『经典问题』。大家知道国际象棋是8×8的棋盘,其中的“皇后”可以横竖斜四通八达地游走和吃子。八皇后问题就是怎么将八个皇后放在棋盘中,让它们互相不能吃掉。这个问题有92个互不相同的解,如果把旋转和对称的解算做同一种,那便是12个独立解。
暴力解决法
很容易看出来,在每一行和每一列上有且只能有一个皇后,因此较为复杂的判断就是对角线了。维基百科的页面上有一个非常暴力但是写起来非常简单的解法:
from itertools import permutations
n = 8
cols = range(n)
for vec in permutations(cols):
if (n == len(set(vec[i] + i for i in cols))
== len(set(vec[i] - i for i in cols))):
print vec
cols
是一个长度为8的数组,其索引代表第几行,值代表第几列,于是便可以用这个列表来表示8个皇后的坐标了。这个暴力解法将所有可能进行全排列后一个一个地判断是否有对角线共线,所以是非常慢的。
稍微不那么暴力的方法
终于到了本文的主题了。稍微不那么暴力的方法就是,一个一个计算列的值,每次的可用值都是依赖于前面已经计算好了的位置的,如果一直运行到最后都有可选择的位置,那么自然就成功了。这样的方法用Generator来实现真是天作之合。
def next_col(current, n=8):
length = len(current)
if length == n:
return
dangerous = current + [item for l in [(val + length - i, val - length + i)
for i, val in enumerate(current)]
for item in l if item >= 0 and item <= n]
for i in range(n):
if i not in dangerous:
yield i
def queens(n=8, columns=[]):
if len(columns) == n:
yield columns
for i in next_col(columns, n):
appended = columns + [i]
for c in queens(n, appended):
yield c
def prettyprint(solution):
def line(pos,lengh=len(solution)):
return '.'*(pos)+'X'+'.'*(lengh-pos-1)
for pos in solution:
print(line(pos))
if __name__ == '__main__':
i = 0
for solution in queens(8):
# print(i, solution)
i+=1
if i > 1: print '\n'
print '第' + str(i) + '解'
prettyprint(solution)
"""
.......X
...X....
X.......
..X.....
.....X..
.X......
......X.
....X...
"""
原文: http://blog.dayanjia.com/2012/10/solve-n-queen-puzzle-using-python-generator/