改进遗传算法求解数独谜题模型详解(Go语言)

改进遗传算法求解数独谜题模型详解(Go语言)

image-20221014123818937

遗传算法简介

基本定义

核心思路 : 优胜劣汰

在计算机科学和运筹学中,遗传算法( GA ) 是一种受自然选择过程启发的元启发式算法智能优化算法),属于较大的进化算法(EA) 类别。遗传算法通常用于通过依赖诸如变异、交叉和选择等受生物学启发的算子来生成优化和搜索问题的高质量解决方案。GA 应用的一些示例包括优化决策树为了获得更好的性能,解决数独难题,超参数优化等。

遗传算法基本流程图

在这里插入图片描述

问题考虑事项

对于常规的遗传算法模型搭建我们需要考虑以下几点问题:

  1. 粗略估算我们的搜索空间大小。
  2. 我们是否可以确定问题存在全局最优解。
  3. 如何对问题解进行染色体编码。
  4. 如何高效地设计适应度函数。
  5. 如何避免局部优解进行选择操作。
  6. 染色体交叉操作的算法设计。
  7. 染色体变异操作的算法设计。

专有名词介绍

为了从多角度理解遗传算法的概念,经常同一个概念会有各种不同的表述方式:口语化表述、数学表述、生物学表述等。

一个解决方案 = 一个解 = 一个个体 ≈ 一个染色体(对于不太复杂的问题,即一个个体只有一个染色体时)

image-20221014115016703

一堆解决方案 = 一个解集 = 一个种群

image-20221014115321928

适应度 = 目标函数 : 用来衡量一个个体的优劣程度。

染色体 : 对问题解抽象化染色体编码后的序列化数据(或者数据结构)。

image-20221014115931273

基因 : 对于较为简单的问题模型,当一个染色体上只有一个基因时约等于染色体的概念,为染色体数据的进一步划分子集。

交叉 : 父代的一个染色体与母代的一个染色体进行染色体片段(一个或多个基因)交配互换产生新染色体。(生物学表述)

image-20221014120627998

变异 : 一个染色体的内部一个或多个基因发生增删改的行为。(生物学表述)

image-20221014120926602

选择 : 模拟优胜劣汰逐渐逼近最优解的过程。

模型参数

种群大小 :每一轮进化(迭代)后或者初始化后得到的固定个体数目(解的数量)。

最大进化轮次 :最大迭代次数。

交叉率 : 每对染色体发生交叉互换的概率。

变异率 : 每个基因发生突变的概率。

以上表述并不是学术界统一的定义,仅为笔者个人的理解与总结,大家可以灵活理解。


解数独模型

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

对于计算机解数独而言,最直接的方法就是剪枝+回溯算法。即按顺序一个空一个空的用所有可行的数字进行填充枚举,最终找到正确答案。但是这种较为暴力的算法在解决困难级别的数独时,由于它是全局搜索,要耗费大量的算力和时间成本。

在这里插入图片描述

染色体编码

数独谜题本身就是由数字组成的问题,我们不需要做特殊的数字化映射,直接用9*9的数组保存即可。

  1. 声明一个9x9的二维数组。

image-20221014173636637

  1. 填上原题数据。

image-20221014174402619

  1. 对每个3x3的格子进行1~9全排列随机填充。

image-20221014174840574

image-20221014175018210

适应度计算

我们设定适应度为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=19Count(RowNumi>1)+i=19Count(ColNumi>1)]

image-20221014181033385

image-20221014180924734

例如,这个解的适应度计算为:

image-20221014183554767

扫描行

image-20221014184113427

扫描列

image-20221014184544989
F i t n e s s = 100 − 8 − 12 = 80 Fitness = 100 - 8 - 12 = 80 Fitness=100812=80

种群选择操作

选择操作就是模拟优胜劣汰,固然可以直接排序,但现实中并不是这样的。为了保证种群基因的多样性,更准确的说,为了避免模型陷入局部最优解,我们会使用其他选择方式。本模型使用的是锦标赛选择法,具体操作为打乱种群内的个体顺序,一对一对地选拔。适应度更好的留下,不好的淘汰(从种群中删去)。

image-20221014192248120

染色体交叉互换

  • 遍历当前种群选出父代母代染色体(本模型中个体与染色体一样)。

image-20221014193225233

  • 遍历父代母代的每个基因(设每个3x3的全排列子数组为一个基因,一个染色体共9个基因)。

image-20221014193313054

  • 随机选中父代母代相同位置的基因按照一定的交叉率进行交叉(实际操作为互换对应的值),得到两个子代染色体。

image-20221014193426899

基因变异操作

  • 对种群遍历每个个体并随机进行变异。
  • 对一个染色体的9个基因进行遍历。
  • 随机对某个基因按照变异率进行变异(实际操作为重新按照全排列初始化)。

image-20221014194909289

模型训练

则按照前面的流程图进行,具体实现参考下面的代码。


代码实现

Go语言实现

image-20221014195526553

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

维基百科遗传算法

遗传算法原理及其matlab程序实现

【数之道14】六分钟时间,带你走近遗传算法

【算法】遗传算法解决旅行商(TSP)问题

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值