本文主要介绍遗传算法的一些基本思想,主要是代码思想方面的,并不用于考试....在我的资源中可以找到一份课件(并不是我们学校的,是老师给的,我们貌似并不开这门课)
另外会在下一篇附上用遗传算法解决tsp问题的代码。
遗传算法的思想其实和生物学有密切的联系(话说我高中选的是生物,已经忘光了哈),遗传算法相比与动态规划,贪心之类的算法,区别主要在于,贪心之类的一开始给出一种计算方式,而我们从已知条件,通过这种计算方式最终得到解,并且解一定是正确的(如果贪心策略经过验证)。而遗传算法一开始会给出一个种群(其实质就是解的集合),当然这些解不一定是正确的,所以我们通过一步步演变,就像是优胜劣汰,最终得到尽可能正确的解。
先介绍一些遗传算法的名词(后面会举例子方便理解)。
基因:染色体内部的某个表现,如何将问题表示成基因需要一定的技巧。
染色体:其实质就是一个解,也就是一个个体,遗传算法优胜劣汰直接作用的对象
适应性:就是一个评估函数,给出一条染色体,看看他是不是我们需要的,如果不需要,那么被淘汰的可能性好高(注意并不是一定被淘汰,就算适应性很低,也有可能存活)
选择:即染色体的选择
交叉:两个染色体的某一处断裂,各自交换断裂的部分(这个地方交换的方法其实也很多,但大概意思都差不多,按照程序书写方便与最终效果选择)
变异:某一个染色体的某一个基因(或多个,也是根据情况选择)发生改变。
简单来说现在有一个汉堡店,他有三个选择,汉堡包价格50美分还是1美元,餐馆大还是小,服务快还是慢,最终考虑汉堡包为了尽可能的盈利,应该怎样选择。
对于这个问题,汉堡店就是一个染色体,它里面有三个基因,分别是汉堡包价格,餐馆大小,服务快慢。
适应性就是我们设计函数,比如当汉堡包价格低,餐馆服务快,大餐馆会怎样(以此类推,应该共有8种情况吧)
那么如何表现基因呢,常用的方法就是二进制,比如50美分就是1,1美元就是0,大餐馆就是1,小餐馆就是0,服务快就是1,服务慢就是0,
那么低价格,服务快,大餐馆就是111,这就是一个解
下面附一个
生成初始群,这个你随机生成即可,适应度这里不太好表示(对于大多数情况适应性函数也只能取个大概)
选择常用的方法是轮盘赌选择法。
终止就是你设计一下最多迭代次数,超过这个次数停止。
步骤: 1、求群体中所有个体的适应值的总和 S ;
2、产生一个在 0 与 S 之间的随机数 m ;
3、从群体中编号为 1 的个体开始,将其适应
值与后继个体的适应值相加,直到累加和等
于或大于 m , 则停止. 其中那个最后加进去
的个体即为选择的个体 .
这样能达到可能存活性为80%它被选择的可能性就为80%
交叉 比如111,001那么我们交叉第二位(含之后的)就能得到101,011)
交叉方式的有很多,你可以自己选择在哪个点断开,也可以选择几个点断裂,比如两个点断裂,就两边交换,中间保留(tsp的代码采用的是这种方法)
变异 这个比较好理解,就是你随机一个位置这个位置上的基因发生变化,有时是引入新的基因,有时是随机两个位置,这两个位置交换(比如tsp问题,因为那个要求染色体是全排列,你肯定不能随便修改里面的基因)
以上就是一个轮回。
下面讨论tsp问题,对于tsp问题,显然用二进制来表示是不合适的,一般对于区间在[a,b]内的书如果用二进制表示,则他的位数n应满足
(b-a)/(2^n)<1,一般城市数量都相当大,所以无法用二进制表示,这里采用的是全排列的方式,每一个城市就是基因,他们访问的顺序就是染色体。
而初始种群设置为10个染色体,而适应函数肯定与路程有关,且路程越大,适应性越低,所以算出每一个路程/总路程后,有两种方式,一种是取倒数,一种是1-这个数
最后不要将概率之和统一为1(每一个操作完的适应性值/总的适应性值).
交叉算法如下:
注意每一次修正后,还要考虑修正的值是否与之前两个断点之间的基因重复(刚才认为不重复是因为与原来的值重复,而现在换了,比如图中第二种情况)
下面附代码,参考http://blog.csdn.net/mylovestart/article/details/8977005(讲得非常详细,但注意交配有问题)
http://blog.csdn.net/yeruby/article/details/13161853(交配进行了修正)
#include <stdio.h> #include <string.h> #include <math.h> #include <stdlib.h> #include <time.h> #define cities 10//城市的个数 #define num 10//种群的大小 #define MAX 100//最大迭代次数 #define pm 0.05//变异的概率 #define pc 0.8//交配的概率 int distance[cities+1][cities+1]; typedef struct node//染色体的结构,也就是一组解 { int city[cities];//每一个基因,也就是10座城市 int adapt;//该组解的适应性 double p;//在种群中幸存的概率 }node; node group[num],grouptemp[num];//grouptemp是为了在选择是临时存储group内的数据 void init() { int i,j; memset(distance,0,sizeof(distance)); srand(time(NULL)); for (i=0;i<cities;i++) for (j=i+1;j<cities;j++) { distance[i][j]=rand()%100; distance[j][i]=distance[i][j]; } printf("城市的距离矩阵如下:\n"); for (i=0;i<cities;i++) { for (j=0;j<cities;j++) printf("%4d",distance[i][j]); printf("\n"); } } void groupproduce()//该函数是随机生成10个n=10的全排列,作为初始种群 { int i,j,k,t,flag; for (i=0;i<num;i++) for (j=0;j<cities;j++) group[i].city[j]=-1; srand(time(NULL)); for (i=0;i<num;i++)//以下方法是求全排列 for (j=0;j<cities;j++) { t=rand()%cities; flag=1; for (k=0;k<j;k++) if (group[i].city[k]==t) { flag=0; break; } if (flag==0) j--; else group[i].city[j]=t; } printf("初始种群\n"); for (i=0;i<num;i++) { for (j=0;j<cities;j++) printf("%4d",group[i].city[j]); printf("\n"); } } void pingjia() { int i,j; int n1,n2; int sumdistance1,sumdistance2;//1是计算某个染色体内的路径值和(10个城市加起来),是计算某个种群内所有染色体的路径值和(即是1的和) double sump=0; for (i=0;i<num;i++) { sumdistance1=0; for (j=1;j<cities;j++) { n1=group[i].city[j-1]; n2=group[i].city[j]; sumdistance1+=distance[n1][n2]; } group[i].adapt=sumdistance1; sumdistance2+=sumdistance1; } //注意上面group[i].adapt保存的是每一条染色体上的路径长度,但是路径越长,生存概率越小 //解决办法是计算每一条染色体 1-group[i].adapt/总的路径长度 //这样确实满足路径越短,生存概率高,但是这样算出来所有染色体出现概率之和不为1 //所以再把出现概率求和,用每一个概率除以总和(这个是不是归一化?) sump=0; for (i=0;i<num;i++) { group[i].p=1-(double)group[i].adapt/(double)sumdistance2; sump+=group[i].p; } for (i=0;i<num;i++) group[i].p=group[i].p/sump; for (i=0;i<num;i++) printf("染色体%d的路径之和与生存概率分别为%4d %.4f\n",i,group[i].adapt,group[i].p); } //这里还是轮盘赌选择,即对于随机生成一个概率,从0开始,依次累加每一个染色体,一旦发现和大于这个概率就认为最后一个加进去是这次选择的胜利者 //由于要保证种群大小不变,所以选择num次,同时为了避免每一次都累加,则事先将累加结果保存到gradient中. void xuanze() { int i,j,temp; double gradient[num]; double t; int xuan[num]; for (i=0;i<num;i++) gradient[i]=0.0; gradient[0]=group[0].p;//先开始累加 for (i=1;i<num;i++) gradient[i]=gradient[i-1]+group[i].p; srand(time(NULL)); for (i=0;i<num;i++)//为保证种群大小不变 { t=(rand()%100);//每一次随机生成1个概率 t=t/100; for (j=0;j<num;j++) if (gradient[j]>t)//最后添加进去的存活 { xuan[i]=j;//第i次选择的是第j条染色体 break; } } for (i=0;i<num;i++) { grouptemp[i].adapt=group[i].adapt; grouptemp[i].p=group[i].p; for (j=0;j<cities;j++) grouptemp[i].city[j]=group[i].city[j]; } for (i=0;i<num;i++) { temp=xuan[i]; group[i].adapt=grouptemp[temp].adapt; group[i].p=grouptemp[temp].p; for (j=0;j<cities;j++) group[i].city[j]=grouptemp[temp].city[j]; } } void jiaopei() { int i,j,k,kk,kkk,flag; int t;//参与交配的染色体的个数 int point1,point2,temp,c,d;//交配断点 int pointnum; int temp1,temp2; int map1[cities]; int map2[cities]; double jiaopeip[num];//染色体的交配的概率 int jiaopeiflag[num];//染色体的可交配的情况 for (i=0;i<num;i++) jiaopeiflag[i]=-1; srand(time(NULL)); for (i=0;i<num;i++) { jiaopeip[i]=rand()%100; jiaopeip[i]/=100; } t=0;//统计能交配的个数 for (i=0;i<num;i++) if (jiaopeip[i]<pc) { jiaopeiflag[t]=i; t++; } t=t/2*2;//保证t为偶数,这里最理想的自然是8了,因为pc是0.8 srand(time(NULL)); temp1=0; c=0; d=1; for (i=0;i<t/2;i++) { point1=rand()%cities; point2=rand()%cities; if (point1>point2) { temp=point1; point1=point2; point2=temp; } temp1=jiaopeiflag[c]; temp2=jiaopeiflag[d]; c=c+2; d=d+2; memset(map1,-1,sizeof(map1)); memset(map2,-1,sizeof(map2)); for (k=point1;k<=point2;k++) { map1[group[temp1].city[k]]=group[temp2].city[k];//对于map1记录第一个染色体的某个基因对应第二个染色体的某个基因(关键!) map2[group[temp2].city[k]]=group[temp1].city[k];//对于map2记录第二个染色体的某个基因对应第二个染色体的某个基因 } for (k=0;k<point1;k++) { temp=group[temp1].city[k]; group[temp1].city[k]=group[temp2].city[k]; group[temp2].city[k]=temp; } for (k=point2+1;k<cities;k++) { temp=group[temp1].city[k]; group[temp1].city[k]=group[temp2].city[k]; group[temp2].city[k]=temp; } printf("处理冲突.........\n");//首先交换分割点两边的基因片段,然后判断新的左边,新的右边是否与中间片段有重复(因为要求是全排列) //如果有重复则左边的重复元素变为中间的元素所对应的另一条染色体上的基因 //最后因为担心这次操作完后左边的重复元素可能又与中间的另外元素重复,所以还得从头扫一遍 for (k=0;k<point1;k++)//先处理第一条染色体 for (kk=point1;kk<=point2;kk++) if (group[temp1].city[k]==group[temp1].city[kk]) { group[temp1].city[k]=map1[group[temp1].city[k]]; kk=point1-1; } for (k=point2+1;k<cities;k++) for (kk=point1;kk<=point2;kk++) if (group[temp1].city[k]==group[temp1].city[kk]) { group[temp1].city[k]=map1[group[temp1].city[k]]; kk=point1-1; } for (i=0;i<num;i++) { for (j=0;j<cities;j++) printf("%4d",group[i].city[j]);//输出10条幸存的路径 printf("\n"); } //处理第二条染色体 for (k=0;k<point1;k++) for (kk=point1;kk<=point2;kk++) if (group[temp2].city[k]==group[temp2].city[kk]) { group[temp2].city[k]=map2[group[temp2].city[k]]; kk=point1-1; } for (k=point2+1;k<cities;k++) for (kk=point1;kk<=point2;kk++) if (group[temp2].city[k]==group[temp2].city[kk]) { group[temp2].city[k]=map2[group[temp2].city[k]]; kk=point1-1; } } } void bianyi()//变异(正常是某一个基因突变,考虑到这里必须要全排列)就是交换两个节点。 { int i,j; int temp1,temp2,temp; double bianyip[num]; int bianyiflag[num]; for (i=0;i<num;i++) bianyiflag[i]=0; srand(time(NULL)); for (i=0;i<num;i++) { bianyip[i]=rand()%100; bianyip[i]/=100; } for (i=0;i<num;i++) { if (bianyip[i]<pm) { bianyiflag[i]=1;//标记需要变异的基因 } } srand(time(NULL)); //开始变异,找到需要变异的染色体,交换染色体的两个节点 for (i=0;i<num;i++) if (bianyiflag[i]==1) { temp1=rand()%10; temp2=rand()%10; temp=group[i].city[temp1]; group[i].city[temp1]=group[i].city[temp2]; group[i].city[temp2]=temp; } } void shuaixuan() { int i,j; double max; max=group[0].p; j=0; for (i=1;i<num;i++) if (max<group[i].p) { max=group[i].p;//更新最大概率 j=i; } printf("最佳路径为:\n"); for (i=0;i<cities;i++) printf("%d ",group[j].city[i]); printf("\n"); printf("最短tsp路径为:%d\n",group[j].adapt); } int main() { int i,j,t,m; init(); groupproduce(); t=0; while(t<MAX) { pingjia(); xuanze(); jiaopei(); bianyi(); t++; } printf("当前幸存路径为:\n"); for (i=0;i<num;i++) { for (j=0;j<cities;j++) printf("%4d",group[i].city[j]);//输出10条幸存的路径 printf("\n"); } shuaixuan();//在当前10组数据中找出最优解 return 0; }