Genetic Algorithms with python 学习笔记ch4

八皇后问题

在一个棋盘上放置八个皇后,要求各个皇后不能互相攻击。即各个皇后不能在同一行、同一列、同一对角线。

利用GA求解该问题,其 8queensTests.py 完整代码如下:

import datetime
import unittest

import genetic


def get_fitness(genes, size):
    board = Board(genes, size)
    rowsWithQueens = set()
    colsWithQueens = set()
    northEastDiagonalsWithQueens = set()
    southEastDiagonalsWithQueens = set()
    for row in range(size):
        for col in range(size):
            if board.get(row, col) == 'Q':
                rowsWithQueens.add(row)
                colsWithQueens.add(col)
                northEastDiagonalsWithQueens.add(row + col)
                southEastDiagonalsWithQueens.add(size - 1 - row + col)
    total = size - len(rowsWithQueens) \
            + size - len(colsWithQueens) \
            + size - len(northEastDiagonalsWithQueens) \
            + size - len(southEastDiagonalsWithQueens)
    return Fitness(total)


def display(candidate, startTime, size):
    timeDiff = datetime.datetime.now() - startTime
    board = Board(candidate.Genes, size)
    board.print()
    print("{}\t- {}\t{}".format(
        ' '.join(map(str, candidate.Genes)),
        candidate.Fitness,
        timeDiff))


class EightQueensTests(unittest.TestCase):
    def test(self, size=8):
        geneset = [i for i in range(size)]
        startTime = datetime.datetime.now()

        def fnDisplay(candidate):
            display(candidate, startTime, size)

        def fnGetFitness(genes):
            return get_fitness(genes, size)

        optimalFitness = Fitness(0)
        best = genetic.get_best(fnGetFitness, 2 * size, optimalFitness,
                                geneset, fnDisplay)
        self.assertTrue(not optimalFitness > best.Fitness)

    def test_benchmark(self):
        genetic.Benchmark.run(lambda: self.test(20))


class Board:
    def __init__(self, genes, size):
        board = [['.'] * size for _ in range(size)]
        for index in range(0, len(genes), 2):
            row = genes[index]
            column = genes[index + 1]
            board[column][row] = 'Q'
        self._board = board

    def get(self, row, column):
        return self._board[column][row]

    def print(self):
        # 0,0 prints in bottom left corner
        for i in reversed(range(len(self._board))):
            print(' '.join(self._board[i]))


class Fitness:
    def __init__(self, total):
        self.Total = total

    def __gt__(self, other):
        return self.Total < other.Total

    def __str__(self):
        return "{}".format(self.Total)


if __name__ == '__main__':
    unittest.main()

下面对上述代码逐一解释:首先对于八皇后这样一个问题,如果需要用遗传算法来求解的话,并不需要修改之前一直使用的 engine ,因此 genetic.py 和上一章中使用的文件内容相同。
那么如何利用 engine 来进行计算呢?下面是两个关键的步骤:

首先需要设置基因的类型
其次需要修改适应值

1.制定基因形式表示皇后的位置

class Board:
    def __init__(self, genes, size):
        board = [['.'] * size for _ in range(size)]
        for index in range(0, len(genes), 2):
            row = genes[index]
            column = genes[index + 1]
            board[column][row] = 'Q'
        self._board = board

    def get(self, row, column):
        return self._board[column][row]

    def print(self):
        # 0,0 prints in bottom left corner
        for i in reversed(range(len(self._board))):
            print(' '.join(self._board[i]))

上面表示Board类表示的是整个棋盘,棋盘中 '.' 表示空白,'Q' 表示皇后,初始棋盘如下图所示:

这里设置基因表示皇后的位置,八个皇后共16位,分别表示八个皇后的行列号(取值范围为0~7)。

2.设定适应值函数

class Fitness:
    def __init__(self, total):
        self.Total = total

    def __gt__(self, other):
        return self.Total < other.Total

    def __str__(self):
        return "{}".format(self.Total)

首先适应值由其元素 Total 值的大小决定,并且该值越小越好,那么 Total 是如何计算的呢?

def get_fitness(genes, size):
    board = Board(genes, size)
    rowsWithQueens = set()
    colsWithQueens = set()
    northEastDiagonalsWithQueens = set()
    southEastDiagonalsWithQueens = set()
    for row in range(size):
        for col in range(size):
            if board.get(row, col) == 'Q':
                rowsWithQueens.add(row)
                colsWithQueens.add(col)
                northEastDiagonalsWithQueens.add(row + col)
                southEastDiagonalsWithQueens.add(size - 1 - row + col)
    total = size - len(rowsWithQueens) \
            + size - len(colsWithQueens) \
            + size - len(northEastDiagonalsWithQueens) \
            + size - len(southEastDiagonalsWithQueens)
    return Fitness(total)

上面就是Total的计算过程,有点不好理解,所以先举个简单的例子:

由于八个皇后不能在同一行,也不能在同一列,因此不同皇后的行号应当不同,列号也应当不同
所以设置一个关于行号的集合 rowsWithQueens ,和一个关于列号的集合 colsWithQueens 。
由于集合之中不能有相同的元素,因此两个皇后有相同行号 x的话集合中也只有一个 x 存在。
这样的话如果集合的长度 len(rowsWithQueens) = size 就表示每个皇后所在的行是不同的

但是只满足上列得条件不一定会得到问题的解,例如:
image.png
因此应当将相同对角线也编上相同的编号,这样是对角线的编号不同就可以了。对角线分为东南对角线(如上图),和东北对角线(如下图):
image.png
对他们的编号情况如下:
image.png
上面是东南对角线的编号,对角线对应的编号可以由(8-1-行号)+列号计算得出。
image.png

上面这个是东北对角线的编号,编号可以由行号+列号计算得出。
因此total由(size-行号个数)+(size-列号个数)+(size-东南对角线号的个数)+(size-东北对角线号的个数)得到,当total=0是,表示八皇后不能互相攻击,为最优解。

posted @ 2020-07-31 15:35  idella  阅读(299)  评论(0编辑  收藏  举报