小白教学式,八皇后问题,Python生成器解法

来源:互联网 发布:经济学是什么 知乎 编辑:程序博客网 时间:2024/05/01 17:04

问题描述:

  • 八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。

问题转换:

  • 1、在8*8矩阵上,摆放元素
  • 2、元素不在同一行,同一列、同一对角线

数据结构:

  • 数组state存储棋盘上皇后位置,eg:state[0]=1,表示:(1,2),state[2]=3,表示(3,4)
    (tips:根据问题,一行只能摆放一个元素,所以可以用下标值表示行,下标对应数组的值表示列)

算法:

  • 问题归类:在数组中搜索,满足要求的,存放元素的位置->搜索问题->蛮力回溯搜索->用递归实现回溯
  • 寻找冲突:声明并定义摆放元素的规则,(conflict函数)
  • 递归出口:当不冲突,且为最后一个要摆放的皇后时,返回此时位置。
  • 递归表达式主题:利用生成器返回一个即时生成的序列。

tips:为了理解算法、生成器在其中的应用,先理解四皇后问题)


一、 回溯法 vs DPS vs 递归

  • 先看一段stackoverflow上的回答:

$Backtracking is a more general purpose algorithm.

$Depth-First search is a specific form of backtracking related to searching tree structures. From Wikipedia:

One starts at the root (selecting some node as the root in the graph case) and explores as far as possible along each branch before backtracking.
It uses backtracking as part of its means of working with a tree, but is limited to a tree structure.

Backtracking, though, can be used on any type of structure where portions of the domain can beeliminated - whether or not it is a logical tree. The Wiki example uses a chessboard and a specific problem - you can look at a specific move, and eliminate it, then backtrack to the next possible move, eliminate it, etc.

  • 综上所述,DPS是回溯法中特例,递归是解决回溯一种方式(也就是说你还可以用非递归方式解决回溯),当然,通过把递归结束的条件设置到搜索的最后一步,再通过借用递归的特性来回溯,是比较简单的方法。

二、 可迭代 vs 迭代器 vs 生成器

https://python-ning.github.io/2015/11/30/python_generators_and_iterators/
我觉得这篇博客讲的比较好,下边第二小节内容主要是这篇博客的内容

1、为什么要用生产器: 如果你不知道到要创建多大的列表,而事先创建好列表,过大或者过小都不好。这时候,你使用生成器可以动态生成列表,从而不但可以满足生成列表要求的某种性能,或者算法,还节省了大量的空间。

2、怎么生成一个生成器:

  • 第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator
>>> L = [x * x for x in range(10)]>>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> g = (x * x for x in range(10))>>> g <generator object <genexpr> at 0x1022ef630>
  • 创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
    如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值。
>>> next(g)0>>> next(g)1>>> next(g)4>>> next(g)9>>> next(g)16>>> next(g)25>>> next(g)36>>> next(g)49>>> next(g)64>>> next(g)81>>> next(g)Traceback (most recent call last):  File "<stdin>", line 1, in <module>StopIteration
  • 我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
    当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10))>>> for n in g:...     print(n)... 0149162536496481
  • 第二种方法一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
>>>def fib(max):    n, a, b = 0, 0, 1    while n < max:        yield b        a, b = b, a + b        n = n + 1    return 'done'>>> f = fib(6)>>> f<generator object fib at 0x104feaaa0>
  • 这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
    再举个简单的例子,定义一个generator,依次返回数字1,3,5:
def odd():    print('step 1')    yield 1    print('step 2')    yield(3)    print('step 3')    yield(5)
  • 调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
>>> o = odd()>>> next(o)step 11>>> next(o)step 23>>> next(o)step 35>>> next(o)Traceback (most recent call last):  File "<stdin>", line 1, in <module>StopIteration
  • 可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。
    回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
    同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
>>> for n in fib(6):...     print(n)...112358
  • generator怎么拿到返回值???:但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
>>> g = fib(6)>>> while True:...     try:...         x = next(g)...         print('g:', x)...     except StopIteration as e:...         print('Generator return value:', e.value)...         break...g: 1g: 1g: 2g: 3g: 5g: 8Generator return value: done
#例子:实现杨辉三角def trag(n):    a = [1]    b = [1]    for m in range(1, n + 1):        a = [0] + a        b = b + [0]        c = [a[i] + b[i] for i in range(m)]        a = b = c        yield(c)for i in trag(8):    print(i)    [1][1, 1][1, 2, 1][1, 3, 3, 1][1, 4, 6, 4, 1][1, 5, 10, 10, 5, 1][1, 6, 15, 20, 15, 6, 1][1, 7, 21, 35, 35, 21, 7, 1][Finished in 0.1s]

3、迭代器:

可迭代对象(Iterable):可直接用于for循环中的数据类型都是可迭代对象(Iterable)。可用isinstance()函数判断
第一类:集合数据类型,list、tuple、dict、set、str
第二类:generator,包括生成器、generator function(带yield的函数)

>>> from collections import Iterable>>> isinstance([], Iterable)  #注意参数True>>> isinstance({}, Iterable)True>>> isinstance('abc', Iterable)True>>> isinstance((x for x in range(10)), Iterable)True>>> isinstance(100, Iterable)False

迭代器(Iterator):可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)。
可以使用isinstance()判断一个对象是否是Iterator对象。

>>> from collections import Iterator>>> isinstance((x for x in range(10)), Iterator)True>>> isinstance([], Iterator)  # 注意参数False>>> isinstance({}, Iterator)False>>> isinstance('abc', Iterator)False
  • 生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
    把list、dict、str等Iterable变成Iterator可以使用iter()函数:
>>> isinstance(iter([]), Iterator)True>>> isinstance(iter('abc'), Iterator)True
  • 你可能会问,为什么list、dict、str等数据类型不是Iterator?
    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算
  • Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

  • 小结
    1、凡是可作用于for循环的对象都是Iterable类型;
    2、凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
    3、集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

    4、Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:    pass#实际上完全等价于:# 首先获得Iterator对象:it = iter([1, 2, 3, 4, 5])# 循环:while True:    try:        # 获得下一个值:        x = next(it)    except StopIteration:        # 遇到StopIteration就退出循环        break

4、生成器、迭代器详解:

  • 迭代器优势和劣势:
  • 劣势
    对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。
  • 优势
    1、但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。
    2、另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
    3、迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了iter()方法对象,就可以使用迭代器访问。

  • 迭代器的两个基本方法

    next()方法:返回迭代器的下一个元素
    __iter__()方法:返回迭代器对象本身

  • 下面用生成斐波那契数列为例子,说明为何用迭代器

#示例代码1def fab(max):  n, a, b = 0, 0, 1  while n < max:    print b    a, b = b, a + b    n = n + 1#直接在函数fab(max)中用print打印会导致函数的可复用性变差,因为fab返回None。其他函数无法获得fab函数返回的数列。#示例代码2def fab(max):  L = [] n, a, b = 0, 0, 1  while n < max:    L.append(b)    a, b = b, a + b    n = n + 1 return L#代码2满足了可复用性的需求,但是占用了内存空间,最好不要。#示例代码3#对比:for i in range(1000): passfor i in xrange(1000): pass#前一个返回1000个元素的列表,而后一个在每次迭代中返回一个元素,因此可以使用迭代器来解决复用可占空间的问题class Fab(object):  def __init__(self, max):    self.max = max    self.n, self.a, self.b = 0, 0, 1  def __iter__(self):    return self  def next(self):    if self.n < self.max:      r = self.b      self.a, self.b = self.b, self.a + self.b      self.n = self.n + 1      return r    raise StopIteration()执行>>> for key in Fabs(5):  print key

Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数

4.2 使用迭代器

  • 使用内建的工厂函数iter(iterable)可以获取迭代器对象:
>>> lst = range(5)>>> it = iter(lst)>>> it<listiterator object at 0x01A63110>
  • 使用next()方法可以访问下一个元素:
>>> it.next()>>> it.next()>>> it.next()
  • python 处理迭代器*=越界 是抛出StopIteration异常
>>> it.next()>>> it.next<method-wrapper 'next' of listiterator object at 0x01A63110>>>> it.next()>>> it.next()Traceback (most recent call last): File "<pyshell#27>", line 1, in <module>  it.next()StopIteration
  • 了解了StopIteration,可以使用迭代器进行遍历
lst = range(5)it = iter(lst)try:  while True:    val = it.next()    print valexcept StopIteration:    pass
  • 事实上,因为迭代器如此普遍,python专门为for关键字做了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。如下
>>> a = (1, 2, 3, 4)>>> for key in a:  print key#首先python对关键字in后的对象调用iter函数迭代器,然后调用迭代器的next方法获得元素,直到抛出StopIteration异常。

4.3 定义迭代器

  • 下面一个例子——斐波那契数列
class Fabs(object):  def __init__(self,max):    self.max = max    self.n, self.a, self.b = 0, 0, 1 #特别指出:第0项是0,第1项是第一个1.整个数列从1开始  def __iter__(self):    return self  def next(self):    if self.n < self.max:      r = self.b      self.a, self.b = self.b, self.a + self.b      self.n = self.n + 1      return r    raise StopIteration()print Fabs(5)#结果<__main__.Fabs object at 0x01A63090>

4.4生成器

  • 用()扩住的[],或者带有 yield 的函数,在 Python 中被称之为 generator(生成器),几个例子说明下(还是用生成斐波那契数列说明)
#可以看出代码3远没有代码1简洁,生成器(yield)既可以保持代码1的简洁性,又可以保持代码3的效果#示例代码4 def fab(max):  n, a, b = 0, 0, 1  while n < max:    yield b    a, b = b, a + b    n = n = 1
  • 简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值

也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

>>> f = fab(3)>>> f.next()1>>> f.next()1>>> f.next()2>>> f.next()Traceback (most recent call last): File "<pyshell#62>", line 1, in <module>  f.next()
  • StopIterationreturn的作用

  • 在一个生成器中,如果没有return,则默认执行到函数完毕;如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。例如

示例代码5 文件读取def read_file(fpath):  BLOCK_SIZE = 1024  with open(fpath, 'rb') as f:    while True:      block = f.read(BLOCK_SIZE)      if block:        yield block      else:        return
  • 如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。

通过python-ning大神的讲解,应该对可迭代对象、迭代器、生成器有了一定了解,下面我们直接放八皇后问题的python代码

https://www.zhihu.com/question/39786326
感谢知乎用户糖QQ提供的思路

# 冲突函数,判断是否满足约定条件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#主函数,为了便于理解执行过程,添加了一些打印信息def queens(num=8, state=()):    print("---------num:",num,"state:",state)  #打印要解决的皇后个数、当前数组    for pos in range(num):        print("pos:",pos)  #打印处理的位置(列)        if not conflict(state, pos):            if len(state) == num - 1:                print("if len(state) == num-1 -----","pos:",pos)  #到了满足条件的最后一个元素时,打印这一行                            yield (pos,)  #yield可换成return             else:                for result in queens(num, state + (pos,)):   #递归回溯                    print("state:",state ,"pos:",pos,"result:",result) # 当找到递归出口时,即有返回迭代器时,打印信息                    yield (pos,) + result  #添加当前位置,并返回到上一分枝    print("-----------------------------------------")  
  • 最后的输出结果
>>> queens(4)<generator object queens at 0x00000207E537F410>>>> print(list(queens(4)))---------num: 4 state: ()pos: 0---------num: 4 state: (0,)pos: 0pos: 1pos: 2---------num: 4 state: (0, 2)pos: 0pos: 1pos: 2pos: 3-----------------------------------------pos: 3---------num: 4 state: (0, 3)pos: 0pos: 1---------num: 4 state: (0, 3, 1)pos: 0pos: 1pos: 2pos: 3-----------------------------------------pos: 2pos: 3----------------------------------------------------------------------------------pos: 1---------num: 4 state: (1,)pos: 0pos: 1pos: 2pos: 3---------num: 4 state: (1, 3)pos: 0---------num: 4 state: (1, 3, 0)pos: 0pos: 1pos: 2  #终于找到第一个,最后一个满足条件的点if len(state) == num-1 ----- pos: 2state: (1, 3) pos: 0 result: (2,)   #这里是生成器的精髓,理解动态生成、暂停state: (1,) pos: 3 result: (0, 2)   #调用一次生成器返回一个结果,然后暂停state: () pos: 1 result: (3, 0, 2)  #而且是在你想要的时候,才结束暂停,所以不会浪费内存空间pos: 3                              #for 内置了next方法,不断调用生成器函数(这里是一个生成器函数,                                    #它可以满足某种算法,生成你想要的结果)                                                                            -----------------------------------------pos: 1pos: 2pos: 3----------------------------------------------------------------------------------pos: 2---------num: 4 state: (2,)pos: 0---------num: 4 state: (2, 0)pos: 0pos: 1pos: 2pos: 3---------num: 4 state: (2, 0, 3)pos: 0pos: 1if len(state) == num-1 ----- pos: 1state: (2, 0) pos: 3 result: (1,)state: (2,) pos: 0 result: (3, 1)state: () pos: 2 result: (0, 3, 1)pos: 2pos: 3----------------------------------------------------------------------------------pos: 1pos: 2pos: 3-----------------------------------------pos: 3---------num: 4 state: (3,)pos: 0---------num: 4 state: (3, 0)pos: 0pos: 1pos: 2---------num: 4 state: (3, 0, 2)pos: 0pos: 1pos: 2pos: 3-----------------------------------------pos: 3-----------------------------------------pos: 1---------num: 4 state: (3, 1)pos: 0pos: 1pos: 2pos: 3-----------------------------------------pos: 2pos: 3----------------------------------------------------------------------------------[(1, 3, 0, 2), (2, 0, 3, 1)]
原创粉丝点击