转载请带源地址,谢谢:http://hi.baidu.com/2427/blog/item/aa24643813d6622c97ddd8d1.html
用遗传算法做数独求解器
I 闲话在前
我不知道有多少人曾经试过这种方法,但我在网上没有找到过类似的内容。
刚开始接触遗传算法的时候,根本不知道他到底有什么用,只知道是个很好玩的算法。于是乎,第一时间想到了拿这东西解数独,但经过N次尝试失败后,放弃了。
前几天群里有人偶尔又提到遗传算法,于是便又想起了之前的那个数独问题,并再次尝试用新方法解决问题。
II 解决方案
首先提一下之前失败的例子。
在我想到的第一种解决方案中首先想到了是把要填的数提出来,做成一个随机序列。拿下面这个数独为例:
在这里例子中,从左到右、自上到下依次缺少的数字为:
然后把这些数字随机生成一个字串,位置不做控制。
第二种方式我才去了二进制方式,首先查看数独中有多少个空位,然后生成N(N为空位数量)组八位二进制数据,对数据进行拼接,所以在我第二种方法中看到的是下面这样的数据。
每组二进制数据转换为整形后得到的是0-255之间的数字,然后乘以系数0.03515625(9/256得到)加1就会得到1-9之间任意数。
这两种效果都不是很理想,第一种方式也许有万分之一的几率能算出正确结果(因为我在运行的时候确实算出过一次),正常情况下很看出结果。第二种出结果的几率更小,因为在第二种结构中,存在的一个致命弊端就是错误数据。
所以我放弃了这两种方式,第三种方式中,我采用了和第一种方式类似的结构形式,以块为单位,计算出每块中缺失数字,然后以组的方式拼串,得到如下数据(块从上到下、从左到右的方式排列,因为在我计算块的时候方向搞错了,也懒得改,就这么些了,不影响计算):
下面我要介绍的就是第三种排列方式,所以正文从这里开始!
1.数独
在这里,还是有必要介绍一下数独,不过本节可以跳过,因为我也是复制百度百科的,哈哈!
九宫格数独,是一种源自18世纪末的瑞士,后在美国发展、并在日本得以发扬光大的数字谜题。数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次。这种游戏全面考验做题者观察能力和推理能力,虽然玩法简单,但数字排列方式却千变万化,所以不少教育者认为数独是训练头脑的绝佳方式。
数独游戏我玩过很多,最嚣张的一次用了不到三分钟解决简单级别的游戏,不过我所知道最牛的人只用一份二十秒就搞定的。如果不考虑遗传算法,用暴力破解法(也就是遍历),最困看的数独计算都是不需要话费时间的,我以为朋友为了证明效率,专门写了一个遍历的方法,非常简单,但是非常有效,我想网上的求解器大多也是用这种方式吧。
2.问题转化
数独横向、纵向、小块中只允许填写1-9之间不重复的数字,首先我们只需要确定出一种数据来即可(横向、纵向或是小块),这是非常简单的,然后我们通过调整小块中的数据,来得到其他两种数据。
3.编码方式
上面已经提到过我这次用到的编码方式,以小块为单位(就是我先计算出来的那种数据)进行数据随机排列,这种数据格式的要求就是首先要保证组与组的顺序不能乱,再次要保证每组中的数据只能是位置条换,不能改成其他数字。这就有别于第一种编码方式了,因为在第一种编码方式中数据是不分组的,也就意味着可能有九个9同时出现在第一块中,效率太低。
4.群体
我习惯叫这个东西部落,可能是因为玩《孢子》太多的缘故吧。一个部落中存在多个个体,数量上我记得有个前辈说过,部落的大小取决于基因的长短,一般部落大小是基因的4-5倍合适,但是我经试验,发现我的算法一般群体小的话收敛太慢,但太大的话计算起来又太慢,所以做到10-20倍就差不多了,在看神经网络的时候看到过这样一句话:你只能把任何建议当作不可全信的东西,主要还要靠自己的不断尝试和失败中获得经验。
群体初始化完全是随机的,但是要遵循编码方式的两个原则:1.分组顺序不能乱,2.组中单个数据不能改。
5.选择与交叉
选择上我用的比较多的还是轮盘赌,这个我就不细说了,不知道的可以百度百科看一下什么叫轮盘赌,或者重新学习一下遗传算法。
首先在染色体中随机选择两个位置,然后把父代染色体进行按位置交叉运算,如下图:
红色位要交叉位(开始位为3,结束位为13,这里由于空间原因只写部分基因)
交叉后得到
我们看到,在交叉后第一组数据和最后一组数据中产生了错误信息,就是重复位,所以,我们下一步要做的就是把错误的基因更正过来,做法如下:
我们首先查看未交叉部分的基因与交叉部分那些基因产生了重复,在我们这个例子中,个体C中的2产生了重复,而交叉部分中,2对应的基因是5,那么我们就把未交叉部分的2改成5,于是得到:
(我尽量把这部分说细写,因为肯能会有些绕)
更正后又发现,5产生了重复,而交叉部分的5对应的基因是9,那么我们再次把5(第二位)换成9,于是得到:
更正有发现9仍然重复,交叉部分的9对应的基因是6,那么再次把9换成6,得到如下个体
到此为止,第一组基因的错误被更正,但是在最后一组中仍然有错误基因,在个体D中也存在错误基因,我们再依次更正,得到修正后的个体如下:
中间所有基因都参与交换的组我们不用管,因为其中不会产生错误数据。
一般交叉率会被设置在70%左右,还是那句话,试验才是正道,自己做不同的交叉率试试看,哪种适合自己用哪种。
(这部分讲的有些啰嗦,如果看不懂的可以Mail我[email protected],或是去我的博客提问,http://hi.baidu.com/2427)
6.变异
变异操作十分简单,只要随机选中一组数字,然后再这个组中随机选择两位进行交换就行了,以下是操作方式
选中变异组为第一组,变异位分别是第二位和第五位,变异后得到
一般变异率在1%到0.1%之间,自己做试验看吧。
变异是为了方式部落陷入局部最优中,这样到死都算不出结果了。群里有位朋友说过,有一种机制可以防止局部最优,就是在每次繁殖后都加入一些新的随机个体,但是我感觉这样会违反遗传算法的一些理论,没有采纳,不过在局部最优产生的时候(或是个体同质化严重的时候)采用这个方法还是不错的。
7.计算
这个我放到逻辑的最后来讲了,其实他和选择是息息相关的,计算方法的忧虑决定了算法的收敛速度,我的算法可能不是最好的,但是我可以为初学者提供一种不错的解决方式。
在轮盘赌中,被选中的几率取决于个体所占区域的大小,而区域的大小是根据个体得分决定的,所以计算得分成了关键。在本例中,我用了减分法进行计算,总分为144分(一会介绍为什么是144分),然后计算每行和每列中出现过多少重复,每出现一次重复减一分。所以这里就看出了我的144分是怎么计算的,每行或每列最多出现重复的数量是8,所以单、列减分就是8,8分*18组=144分。当然,在我提出这个算法的时候有个朋友告诉我说64分就可以了,因为不可能出现所有空格中都出现一个数字的情况。不过这种方法我没细考虑呢,或许他是对的,各位看官验证吧。考虑到这种比分在论盘中体现出来的差习性会很小,所以可以把得分做平方处理,这样会出现指数型变化,让小的越小,大的越大,也许这种方式是不科学的,但是值得去尝试。另外啰嗦一句,我再刚开始做的时候想到了把得分乘3,但后来改正了,因为这中计算方式并不能让区域产生变化。
8.进化
一切准备就绪了,开始进化吧,下面是我这种方法的进化图,分别体现出了最优个体、最劣个体和平均得分。
图中的进化在495代(这个代不准确,懒得改了)算出真确结果,一般情况下在一千代之内才能算出接过来,效率非常低,因为在最开始的时候我忘了一件事,把上一代的最优个体带入,这是很关键的,通过下面这个图你就能比较出来了。
看到所有线几乎都是直线飙升上去的,一般在一百代之内就能算出结果来,这个截图线飚的还不是太好,我一次用了32代就做出来了,那条线才叫漂亮。
我最优个体选择了5%,这个比率自己做试验看一下,带的太多了容易让结果跑到局部最优,或者是收敛太慢,太少了达不到效果。
结束语
在我这个算法中也存在一些没有解决的问题,就是效率,有时在运算的时候线可能会飚直了,这就是局部最优产生了,有一次我计算的时候竟然三个线飚到了一起了,这是非常致命的,期待高手对算法进行优化。
就啰嗦到这吧,文采太差劲了,不适合写教程,有不明白的或是想讨论一下的mail我吧([email protected])。
我的源程序放在了CSDN的下载频道,叫《用遗传算法做数独求解器》,需要的可以下载。
http://download.csdn.net/source/2805016
到此为止,期待高手改正算法和文章中的错字,谢谢您的耐心!