1. 程式人生 > 實用技巧 >Genetic Algorithms with python 學習筆記ch4

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 就表示每個皇后所在的行是不同的

但是隻滿足上列得條件不一定會得到問題的解,例如:

因此應當將相同對角線也編上相同的編號,這樣是對角線的編號不同就可以了。對角線分為東南對角線(如上圖),和東北對角線(如下圖):

對他們的編號情況如下:

上面是東南對角線的編號,對角線對應的編號可以由(8-1-行號)+列號計算得出。

上面這個是東北對角線的編號,編號可以由行號+列號計算得出。
因此total由(size-行號個數)+(size-列號個數)+(size-東南對角線號的個數)+(size-東北對角線號的個數)得到,當total=0是,表示八皇后不能互相攻擊,為最優解。