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是,表示八皇后不能互相攻擊,為最優解。