SAT问题展开说(2)[下]
首先,我们看使用何种适应度函数。事实上,对于染色体组,我们好坏的评价由score来记录,如下的过程自然刻画了我们对适应度函数的设计:
private int GetScore(int i)
{
int score=0;
for(int j=0;j<maxNum;j++)
for(int k=0;k<valNum;k++)
if(int.Parse(satGenes[i].ToString().Substring(k,1))+bSentence[j,k]==2//
||int.Parse(satGenes[i].ToString().Substring(k,1))+bSentence[j,k]==-1)
{
score++;
break;
}
return score;
}
public int Sort()
{
int iBest=0,iWorst=0;
int bestScore=GetScore(0);//
int worstScore=GetScore(0);
for(int i=0;i<maxNum;i++)
{
score[i]=GetScore(i);
if(score[i]>bestScore)
{
iBest=i;
bestScore=score[i];
}
else if(score[i]<worstScore)
{
iWorst=i;
worstScore=score[i];
}
}
best=new StringBuilder(satGenes[iBest].ToString());
worst=new StringBuilder(satGenes[iWorst].ToString());
return bestScore;
}
您注意到,演化策略我们采取的又不同于一般的遗传算法。当然,使用best和worst,使得演化的压力异常的大,但我们对于算子的设计是多样的,以避免早熟现象的出现。
现在是最为核心的部分,遗传算子的设计。
我们的算法中,杂交算子和变异算子都是非常多样化的。其中变异算子以Pm的概率值使一个染色体进行如下的变异:
1/3的情况进行某一位的取反变异,或以2/3的情况进行某连续两位的取反变异。
而杂交算子以Pc的概率值使两个染色体进行杂交,杂交过程如下:
从某位开始,以某个随机长度,2/3的情况进行互换子串进行杂交,1/3的情况进行异或杂交。
过程分别如下:
public void Mutation()
{
int mutType,start;
string subGene;
int i=randNum.Next(maxNum-1);
if(randNum.NextDouble()<=Pm)
{
mutType=randNum.Next(0,2);
switch(mutType)
{
case 0:
start=randNum.Next(0,valNum-1);
subGene=satGenes[i].ToString().Substring(start,1);
if(subGene=="0")
satGenes[i].Replace("0","1",start,1);
else
satGenes[i].Replace("1","0",start,1);
break;
case 1:case 2:
start=randNum.Next(0,valNum-2);
subGene=satGenes[i].ToString().Substring(start,2);
switch(subGene)
{
case "00":
satGenes[i].Replace("00","11",start,2);
break;
case "01":
satGenes[i].Replace("01","10",start,2);
break;
case "10":
satGenes[i].Replace("10","01",start,2);
break;
case "11":
satGenes[i].Replace("11","00",start,2);
break;
}
break;
default:
break;
}
}
}
public void Crossover()
{
int croType,start,length,i,j;
string i_subGene,j_subGene;
length=randNum.Next(5)+1;
start=randNum.Next(0,valNum-length);
i=randNum.Next(maxNum-1);
j=randNum.Next(maxNum-1);
i_subGene=satGenes[i].ToString().Substring(start,length);
j_subGene=satGenes[j].ToString().Substring(start,length);
croType=randNum.Next(2);
switch(croType)
{
case 0:case 2:
satGenes[i].Replace(i_subGene,j_subGene,start,length);
satGenes[j].Replace(j_subGene,i_subGene,start,length);
break;
case 1:
for(int k=0;k<length;k++)
{
switch(int.Parse(i_subGene.Substring(k,1))
+int.Parse(j_subGene.Substring(k,1)))
{
case 0:case 2:
satGenes[i].Replace(i_subGene.Substring(k,1),"1",start+k,1);
satGenes[j].Replace(j_subGene.Substring(k,1),"0",start+k,1);
break;
case 1:
satGenes[i].Replace(i_subGene.Substring(k,1),"0",start+k,1);
satGenes[j].Replace(j_subGene.Substring(k,1),"1",start+k,1);
break;
}
}
break;
default:
break;
}
}
以上是SAT类的主要部分。这样算法的细节设计过程已经实现了。
下面是算法过程,以及初始化过程和其他界面过程,在类Form1中实现,另外修改演化参数的类frmSet我们就不在此介绍了(因为它不关系算法实现)。
这里是算法本身:
private void geneCompute()
{
int answer=0,oldAnswer=0;
hh=DateTime.Now.Hour;
mm=DateTime.Now.Minute;
ss=DateTime.Now.Second;
ms=DateTime.Now.Millisecond;
SetTime();
oneSAT.InitGenes();
oldAnswer=answer=oneSAT.Sort();
while(answer<maxNum-nilNum)
{
oneSAT.Mutation();
oneSAT.Crossover();
answer=oneSAT.Sort();
spScore.Text=strScore+answer.ToString();
if(answer<oldAnswer)
{
oldAnswer=answer;
chListBoxString.Items.Add(oneSAT.Best,true);
}
SetTime();
}
chListBoxString.ClearSelected();
chListBoxString.Items.Add(oneSAT.Best,true);
spScore.Text=strScore+answer.ToString();
}
其中oneSAT是SAT类的一个对象,初始化如下:
oneSAT=new SAT(maxNum,valNum,0.8,0.02);
SetTime()是计算算法执行时间的跟踪,详细过程可见附录。而整个算法的执行,我们新创建一个线程computeGene,其过程如下:
private void threadGeneCompute()
{
computeGene=new Thread(new ThreadStart(geneCompute));
computeGene.Start();
}
这样,在类Form1中,我们可以控制这个线程computeGene来进行我们算法的数值实验了。
算法的参数设置如下:
l 逻辑变量规模:200 和 300
l 种群规模:30
l 子句数量:30
l 杂交概率:0.8
l 变异概率:0.02
l 算法的出口为最好的染色体的分数等于非空的子句数量
我们选择200和300个的逻辑变量分别做了若干次实验,除去一开始就寻到结果的瞬时解答外,我们各随机选择了两个子句集与其计算结果、时间。分别为:
逻辑变量数目为200的:
其一,计算结果为:
00000000000000010000000000000000000011100000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
0000011000000000000000000000000000000000
时间为: 13s 328ms
其二,计算结果为:
00000111100100000000011110000011000000000000000110101110000000000001100000011111
00000000000100000000000000000000000000000010000000000000001100000000100000110000
0000000001000000011110000010000001111100
时间为: 57s 563ms
逻辑变量数目为300的:
其一,计算结果为:
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000111100000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
时间为: 2s 625ms
其二,计算结果为:
00001000000000000000000000000000000000000000111000000000000000000000000000000000
00000000000000000000000100000000000000000000000000001100000000000000000000000000
00000000000000000000010000000000000011110000000000000001111100000000000000000000
000000000000000000000000000000010000000000000000000000000000
时间为: 28s 641ms
子句集合都是随机生成的,我们附录在后面供读者参考。由答案的结果来看,复杂的子句需要较多的时间,但相应于2^200 和2^300的问题规模,计算速度还是可观的。
存在的问题
算法是我们上学期实现,后来由于忙于其他事情,再没有做细致的优化工作。所以现在看来,算法的可改进尝试点还是很多的。简单分析如下,而更多的提高,我们寄希望于读者。如果您对整个程序的源码有兴趣,可联系笔者,免费赠送。
首先,演化策略还比较单调。我们实际上采取的是,对一个种群,择其一按概率要求进行变异和杂交,然后排序去除最差者。没有其他的任何措施,比如好基因可以保留为一个库等。这里可以尝试改进。
算法在执行的过程中,有少量情况是,分数降低1分,但很快又还原。这表明计算不是一直向前的。而且,在计算结果趋于较好时,计算也放慢,应该想办法进行加速而有所补救。
最大的缺憾是,在最终结果出现之前,我们不能判定该集合是否是可满足的。我们只能告诉您,目前的计算结果表明,有多少子句已经可以满足,而我们希望情况越来越好。这是由演化计算的特点所决定的,改进难于实现。
算子还可以试图采取其他方案。但我们认为我们的算子设计还是较好的。
参考资料:
(1)康立山,智能计算及其应用讲义,中国地质大学内部讲义,2002年9月
(2)Harry R.Lewis/Christos H.Papadimitriou, ELEMENTS OF THE THEORY OF COMPUTATION ,清华大学出版社(影印),1999年9月
(2)