改进遗传算法求解数独谜题模型详解(Go语言)
遗传算法简介
基本定义
核心思路 : 优胜劣汰
在计算机科学和运筹学中,遗传算法( GA ) 是一种受自然选择过程启发的元启发式算法(智能优化算法),属于较大的进化算法(EA) 类别。遗传算法通常用于通过依赖诸如变异、交叉和选择等受生物学启发的算子来生成优化和搜索问题的高质量解决方案。GA 应用的一些示例包括优化决策树为了获得更好的性能,解决数独难题,超参数优化等。
遗传算法基本流程图
问题考虑事项
对于常规的遗传算法模型搭建我们需要考虑以下几点问题:
- 粗略估算我们的搜索空间大小。
- 我们是否可以确定问题存在全局最优解。
- 如何对问题解进行染色体编码。
- 如何高效地设计适应度函数。
- 如何避免局部优解进行选择操作。
- 染色体交叉操作的算法设计。
- 染色体变异操作的算法设计。
专有名词介绍
为了从多角度理解遗传算法的概念,经常同一个概念会有各种不同的表述方式:口语化表述、数学表述、生物学表述等。
一个解决方案 = 一个解 = 一个个体 ≈ 一个染色体(对于不太复杂的问题,即一个个体只有一个染色体时)
一堆解决方案 = 一个解集 = 一个种群
适应度 = 目标函数 : 用来衡量一个个体的优劣程度。
染色体 : 对问题解抽象化染色体编码后的序列化数据(或者数据结构)。
基因 : 对于较为简单的问题模型,当一个染色体上只有一个基因时约等于染色体的概念,为染色体数据的进一步划分子集。
交叉 : 父代的一个染色体与母代的一个染色体进行染色体片段(一个或多个基因)交配互换产生新染色体。(生物学表述)
变异 : 一个染色体的内部一个或多个基因发生增删改的行为。(生物学表述)
选择 : 模拟优胜劣汰逐渐逼近最优解的过程。
模型参数
种群大小 :每一轮进化(迭代)后或者初始化后得到的固定个体数目(解的数量)。
最大进化轮次 :最大迭代次数。
交叉率 : 每对染色体发生交叉互换的概率。
变异率 : 每个基因发生突变的概率。
以上表述并不是学术界统一的定义,仅为笔者个人的理解与总结,大家可以灵活理解。
解数独模型
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
对于计算机解数独而言,最直接的方法就是剪枝+回溯算法。即按顺序一个空一个空的用所有可行的数字进行填充枚举,最终找到正确答案。但是这种较为暴力的算法在解决困难级别的数独时,由于它是全局搜索,要耗费大量的算力和时间成本。
染色体编码
数独谜题本身就是由数字组成的问题,我们不需要做特殊的数字化映射,直接用9*9的数组保存即可。
- 声明一个
9x9
的二维数组。
- 填上原题数据。
- 对每个
3x3
的格子进行1~9全排列随机填充。
适应度计算
我们设定适应度为100的个体(解)就是该数独的正确答案。对每行每列进行扫描,如果有重复的数字减去其多余的个数。
F
i
t
n
e
s
s
=
100
−
[
∑
i
=
1
9
C
o
u
n
t
(
R
o
w
N
u
m
i
>
1
)
+
∑
i
=
1
9
C
o
u
n
t
(
C
o
l
N
u
m
i
>
1
)
]
Fitness = 100 - \Big[\sum_{i = 1}^9 Count(RowNum_i > 1) + \sum_{i = 1}^9Count(ColNum_i > 1)\Big]
Fitness=100−[i=1∑9Count(RowNumi>1)+i=1∑9Count(ColNumi>1)]
例如,这个解的适应度计算为:
扫描行
扫描列
F
i
t
n
e
s
s
=
100
−
8
−
12
=
80
Fitness = 100 - 8 - 12 = 80
Fitness=100−8−12=80
种群选择操作
选择操作就是模拟优胜劣汰,固然可以直接排序,但现实中并不是这样的。为了保证种群基因的多样性,更准确的说,为了避免模型陷入局部最优解,我们会使用其他选择方式。本模型使用的是锦标赛选择法,具体操作为打乱种群内的个体顺序,一对一对地选拔。适应度更好的留下,不好的淘汰(从种群中删去)。
染色体交叉互换
- 遍历当前种群选出父代母代染色体(本模型中个体与染色体一样)。
- 遍历父代母代的每个基因(设每个
3x3
的全排列子数组为一个基因,一个染色体共9个基因)。
- 随机选中父代母代相同位置的基因按照一定的交叉率进行交叉(实际操作为互换对应的值),得到两个子代染色体。
基因变异操作
- 对种群遍历每个个体并随机进行变异。
- 对一个染色体的9个基因进行遍历。
- 随机对某个基因按照变异率进行变异(实际操作为重新按照全排列初始化)。
模型训练
则按照前面的流程图进行,具体实现参考下面的代码。
代码实现
Go语言实现
Board棋盘数组
给9x9
数组起别名,Board
类型方便添加方法。
// 棋盘数组
type Board [9][9]int
// 显示状态
func (this Board) Show() {
for i := 0; i < 9; i++ {
for j := 0; j < 9; j++ {
fmt.Print(this[i][j])
if j != 8 {
fmt.Print(" ")
}
}
fmt.Println()
}
}
Individual染色体结构体定义
- 一个个体包括的属性有生成解
Board
,原题Board
,适应度score
.
// 全局常量默认参数值
const (
IDV_NUM int = 3000
MAX_NUM int = 100000
CROSS_RATE float64 = 0.4
VARIATION_RATE float64 = 0.1
INTERVAL int = 200
FRESH int = 2000
)
// 初始化函数
func init() {
rand.Seed(time.Now().UnixNano()) // 设定随机种子
}
// 染色体结构体
type Individual struct {
grid Board // 生成解
vis Board // 原题
score int // 适应度
}
- 一个基因全排列生成函数 : 通过
vis
布尔数组记录是否已存在。
// 填充随机解
func RandFill(grid *Board, r, c int, fixed Board) {
vis := [10]bool{}
for i := r; i < r+3; i++ {
for j := c; j < c+3; j++ {
x := fixed[i][j]
if x != 0 {
vis[x] = true
grid[i][j] = x
}
}
}
for i := r; i < r+3; i++ {
for j := c; j < c+3; j++ {
if fixed[i][j] == 0 {
x := rand.Intn(9) + 1
for vis[x] {
x = rand.Intn(9) + 1
}
grid[i][j] = x
vis[x] = true
}
}
}
}
- 染色体构造函数
// Individual构造函数
func NewIndividual(gridInput Board) Individual {
var grid Board
for i := 0; i < 9; i += 3 {
for j := 0; j < 9; j += 3 {
RandFill(&grid, i, j, gridInput)
}
}
res := Individual{grid, gridInput, 0}
res.GetFitness()
return res
}
- 染色体适应度计算函数
// 计算适应度
func (this *Individual) GetFitness() int {
res := 100
checkRow := func(r int) int {
var cnt [10]int
rs := 0
for i := 0; i < 9; i++ {
cnt[this.grid[r][i]]++
}
for i := 1; i <= 9; i++ {
if cnt[i] > 1 {
rs += cnt[i] - 1
}
}
return rs
}
checkCol := func(c int) int {
var cnt [10]int
rs := 0
for i := 0; i < 9; i++ {
cnt[this.grid[i][c]]++
}
for i := 1; i <= 9; i++ {
if cnt[i] > 1 {
rs += cnt[i] - 1
}
}
return rs
}
for i := 0; i < 9; i++ {
res -= checkRow(i)
res -= checkCol(i)
}
this.score = res
return res
}
Individual
结构体辅助函数
// 深拷贝
func (this *Individual) Copy(other Individual) {
this.score = other.score
for i := 0; i < 9; i++ {
for j := 0; j < 9; j++ {
this.grid[i][j] = other.grid[i][j]
this.vis[i][j] = other.vis[i][j]
}
}
}
// 显示解状态
func (this Individual) Show() {
this.grid.Show()
}
遗传算法模型
GA
模型结构体定义
// GA遗传模型
type GA struct {
idvNum int // 种群个体数目
maxNum int // 最大迭代次数
crossRate float64 // 交叉率
variationRate float64 // 变异率
bestIdv Individual // 最优个体
population []Individual // 种群
vis Board // 原题
}
GA
模型构造方法
// GA结构体构造函数:(种群大小,迭代次数,交叉率,变异率)
func NewGAModel(par ...interface{}) GA {
n := len(par)
idvNum := IDV_NUM
maxNum := MAX_NUM
crossRate := CROSS_RATE
variationRate := VARIATION_RATE
if n > 0 {
idvNum = par[0].(int)
if idvNum&1 == 1 {
idvNum--
}
}
if n > 1 {
maxNum = par[1].(int)
}
if n > 2 {
crossRate = par[2].(float64)
}
if n > 3 {
variationRate = par[3].(float64)
}
return GA{idvNum: idvNum, maxNum: maxNum, crossRate: crossRate, variationRate: variationRate}
}
- 模型初始化
// 导入数独习题
func (this *GA) ModelInit(gridInput Board) {
this.vis = gridInput
maxFit := 0
this.population = make([]Individual, this.idvNum)
for i := range this.population {
this.population[i] = NewIndividual(gridInput)
this.population[i].GetFitness()
if maxFit < this.population[i].score {
maxFit = this.population[i].score
this.bestIdv = this.population[i]
}
}
}
- 获取当前种群最优解
// 更新最优染色体
func (this *GA) GetBestIdv() Individual {
maxFit := 0
for i := range this.population {
if maxFit < this.population[i].score {
maxFit = this.population[i].score
this.bestIdv = this.population[i]
}
}
return this.bestIdv
}
- 父代母代染色体交叉互换函数
// 交叉互换
func (this *GA) Cross(idv1, idv2 Individual) (Individual, Individual) {
var idv3, idv4 Individual
idv3.Copy(idv1)
idv4.Copy(idv2)
partSwap := func(chd1, chd2 *Individual, r, c int, vis Board) {
for i := r; i < r+3; i++ {
for j := c; j < c+3; j++ {
if vis[i][j] == 0 {
chd1.grid[i][j], chd2.grid[i][j] = chd2.grid[i][j], chd1.grid[i][j]
}
}
}
}
for i := 0; i < 9; i += 3 {
for j := 0; j < 9; j += 3 {
if rand.Float64() > this.crossRate {
partSwap(&idv3, &idv4, i, j, this.vis)
}
}
}
idv3.GetFitness()
idv4.GetFitness()
return idv3, idv4
}
- 种群锦标赛选择操作
// 锦标赛选择
func (this *GA) Select() {
rand.Shuffle(len(this.population), func(i, j int) {
this.population[i], this.population[j] = this.population[j], this.population[i]
})
newPopulation := make([]Individual, this.idvNum)
better := func(a, b Individual) Individual {
if a.score > b.score {
return a
}
return b
}
for i, j := 0, 0; i < len(this.population); i += 2 {
newPopulation[j] = better(this.population[i], this.population[i+1])
j++
}
this.population = newPopulation
}
- 染色体变异操作
// 变异操作
func (this *GA) Variate(Idx *Individual) {
for i := 0; i < 9; i += 3 {
for j := 0; j < 9; j += 3 {
if rand.Float64() < this.variationRate {
RandFill(&Idx.grid, i, j, this.vis)
}
}
}
}
- 模型训练函数
// 模型训练
func (this *GA) Train() {
for t := 0; t < this.maxNum; t++ {
// 父代母代进行染色体交配
for i := 0; i < this.idvNum; i += 2 {
father, mother := this.population[i], this.population[i+1]
newIdv1, newIdv2 := this.Cross(father, mother)
newIdv1.GetFitness()
newIdv2.GetFitness()
this.population = append(this.population, newIdv1, newIdv2)
}
// 种群选择操作优胜劣汰
this.Select()
// 种群变异操作
for i := 0; i < this.idvNum; i++ {
if rand.Float64() < this.variationRate {
this.Variate(&(this.population[i]))
this.population[i].GetFitness()
}
}
// 获取当前种群最优染色体解
this.GetBestIdv()
if this.bestIdv.score == 100 {
fmt.Printf("-------------NO.%d--------------\n", t)
fmt.Println("Result:")
this.bestIdv.Show()
fmt.Println()
return
}
if t%INTERVAL == 0 {
fmt.Printf("-------------%d--------------\n", t)
fmt.Printf("Score:%d\n", this.bestIdv.score)
fmt.Println("Best Individual:")
this.bestIdv.Show()
fmt.Println()
time.Sleep(time.Second)
}
if t > 0 && t%FRESH == 0 {
if this.variationRate < 0.8 {
this.variationRate *= 1.1
}
fmt.Printf("variation rate:%.2f\n", this.variationRate)
for i := 0; i < this.idvNum; i++ {
this.population[i] = NewIndividual(this.vis)
this.population[i].GetFitness()
}
}
}
fmt.Println("failed to find!")
}
GUI使用的是fyne框架。
开源分享与参考资料
项目开源链接:Go_genetic_algorithm_Sudoku_Project
Jana, S., Dey, A., Maji, A.K., Pal, R.K. (2022). Solving Sudoku Using Neighbourhood-Based Mutation Approach of Genetic Algorithm. In: Chaki, R., Chaki, N., Cortesi, A., Saeed, K. (eds) Advanced Computing and Systems for Security: Volume 13. Lecture Notes in Networks and Systems, vol 241. Springer, Singapore. https://doi.org/10.1007/978-981-16-4287-6_11