1. 概述
早在几个周以前就有同学在群里面说使用启发式算法来解决来解决这个问题,当然也有同学使用这个方法得到很不错的效果。在本篇文章中将遗传算法作为一个例子,列出来与大家交流。
首先对遗传算法中的几个概念做一下简短的介绍。
遗传算法是根据生物进化演变的规律总结来的,因而其中的一些概念也是跟生物息息相关的。
交叉:
有性生殖生物在繁殖下一代时,两个同源染色体之间通过交叉而重组,亦即在两个染色体的某一相同位置处DNA被切断,其前后两串分别交叉组合而形成新的染色体,这个新的染色体继承了两个父代染色体的基因。
变异:
虽然概率很小,但有可能产生某些基因复制差错,从而使DNA发生某种变异,产生出新的染色体,这些新的染色体表现出新的生物性状。
适应性:
根据达尔文的自然选择学说,地球上的生物具有很强的繁殖能力。在繁殖过程中,大多数生物通过遗传,使物种保持相似的后代;部分生物由于变异,后代具有明显差别,甚至形成新物种。正是由于生物的不断繁殖后代,生物数目大量增加,而自然界中生物赖以生存的资源却是有限的。因此,为了生存,生物就需要竞争。生物在生存竞争中,根据对环境的适应能力,适者生存,不适者消亡。自然界中的生物,就是根据这种优胜劣汰的原则,不断地进行进化。每一个个体对其生存环境都有不同的适应能力,这种适应能力称为个体的适应度。适应性强的个体有更高多机会生存并遗传下一代。
群体:
生物的进化是以集团的形式共同进行的,这样的一个团体称为群体或称为种群;组成群体的单个生物称为个体。
针对比赛中提出的问题,这里采用0-1规划与遗传算法结合,因而下面的概念可以解释为:
编码:染色体是生物个体的唯一表示,通常用编码来表示染色体;在比赛中使用0-1来表示该节点是否是服务器节点,也就是二进制编码。
群体:个体的集合;在比赛中种群的大小也意味着计算量的大小,因而在大图的时候可以适当减小种群的数量。
适应度:个体的适应性、生命力的度量;在比赛中使用最小费用最大流计算得到的代价来作为它的代价函数,也可以采用拉格朗日松弛来简化数学模型。
选择(复制):适应度高的个体,被大自然选择并生存的机率高;采用随机类似赌轮的方式进行。
交叉:个体间的基因交叉,生物遗传的主要行为;
变异:个体继承父代基因时的基因变异,生物进化的主要行为。
群体:个体的集合;在比赛中种群的大小也意味着计算量的大小,因而在大图的时候可以适当减小种群的数量。
适应度:个体的适应性、生命力的度量;在比赛中使用最小费用最大流计算得到的代价来作为它的代价函数,也可以采用拉格朗日松弛来简化数学模型。
选择(复制):适应度高的个体,被大自然选择并生存的机率高;采用随机类似赌轮的方式进行。
交叉:个体间的基因交叉,生物遗传的主要行为;
变异:个体继承父代基因时的基因变异,生物进化的主要行为。
2. 编码
///////////////////////////////////////*遗传算法 start*//////////////////////////////////////////////////// # define POPSIZE 10 //种群内个体数量 # define MAXGENS 100 //最大的迭代次数 static int NVARS = 3; //变量个数,即用以表示基因型的bit数 # define PXOVER 0.85 //交换率 # define PMUTATION 0.3 //突变率 struct genotype { double gene[MAX_NODE]; double fitness; double upper[MAX_NODE]; double lower[MAX_NODE]; double rfitness; double cfitness; }; struct genotype population[POPSIZE + 1]; struct genotype newpopulation[POPSIZE + 1]; ///////////////////////////////////////*遗传算法 end*////////////////////////////////////////////////////// ///////////////////////////////////////*遗传算法 start*//////////////////////////////////////////////////// //遗传算法搜索 void my_GAsearch() { int seed = 772002; srand((unsigned)time(NULL)); GA_initialize(seed); evaluate(); keep_the_best(); for (int generation = 0; generation < MAXGENS; generation++) { selector(seed); crossover(seed); mutate(seed); evaluate(); elitist(); } } //选择两个个体进行交叉变异 void crossover(int &seed) { const double a = 0.0; const double b = 1.0; int mem; int one; int first = 0; double x; for (mem = 0; mem < POPSIZE; ++mem ) { //x = r8_uniform_ab ( a, b, seed ); x = Dou_uniform_ab( a, b); if ( x < PXOVER ) { ++first; if ( first % 2 == 0 ) { Xover ( one, mem, seed ); } else { one = mem; } } } return; } //选出本轮中最优和最差的个体,将本轮中最好的个体赋值给最后一个个体,将上轮中最坏的个体赋值给当前最坏的个体 void elitist() { int i; double best; int best_mem; double worst; int worst_mem; best = population[0].fitness; worst = population[0].fitness; for (i = 0; i < POPSIZE - 1; ++i ) { if ( population[i+1].fitness < population[i].fitness ) { if ( best <= population[i].fitness ) { best = population[i].fitness; best_mem = i; } if ( population[i+1].fitness <= worst ) { worst = population[i+1].fitness; worst_mem = i + 1; } } else { if ( population[i].fitness <= worst ) { worst = population[i].fitness; worst_mem = i; } if ( best <= population[i+1].fitness ) { best = population[i+1].fitness; best_mem = i + 1; } } } // // If the best individual from the new population is better than // the best individual from the previous population, then // copy the best from the new population; else replace the // worst individual from the current population with the // best one from the previous generation // if ( population[POPSIZE].fitness <= best ) { for ( i = 0; i < NVARS; i++ ) { population[POPSIZE].gene[i] = population[best_mem].gene[i]; } population[POPSIZE].fitness = population[best_mem].fitness; } else { for ( i = 0; i < NVARS; i++ ) { population[worst_mem].gene[i] = population[POPSIZE].gene[i]; } population[worst_mem].fitness = population[POPSIZE].fitness; } return; } //对遗传算法的效果进行评价 void evaluate() { int member; int i; double x[800+1]; int temp_flow(0); //流量标记 std::vector<int> m_server; //取到的时候对应的server for ( member = 0; member < POPSIZE; member++ ) { int flow(0), cost(0); double sum=0; for ( i = 0; i < NVARS; i++ ) { x[i+1] = population[member].gene[i]; } std::vector<int> temp = GA_get_server_pos(x); population[member].fitness = get_fitness(temp, cost, flow); if (flow >= needed_flow) { if (min_cost > cost) { min_cost = cost; m_server = temp; } } //满足了流量要求,则要求其最小费用 else { if (temp_flow < flow) { temp_flow = flow; m_server = temp; } } //不满足流量需求,则满足最大流量需求 } xjbs_search(m_server); //局部搜索最优 GA_update(m_server); return; } //使用搜索出来的局部最优解去更新遗传的参数 void GA_update(std::vector<int> m_server) { for (int i = 0; i < NVARS; i++) { for (int j = 0; j < POPSIZE; j++) { if (CheckIsServer(i, m_server)) { population[j].gene[i] += Dou_uniform_ab(0.0, 0.2);; } //else // population[j].gene[i] = 0.2; } } } //计算自定义的度量函数 double get_fitness(std::vector<int>& server, int& cost, int& flow) { if(server.size()<=0) { cout << "get 0 server pos in GA, ERROR" << endl; return (-MAXINT); } Dij_init(server); flow = 0; cost = Dij_MinCostFlow(net_node_num, (net_node_num+1), flow); cost = cost+server.size()*server_cost; cout << "server_num:" << server.size() <<" ,loop: min cost: " << cost << " , max flow: " << flow << " / " << needed_flow << endl; //return flow; if(flow >= needed_flow) { if(min_cost > cost) { //cout << "find better: " << cost << endl; min_server = server; min_cost = cost; min_path = path; } return cost; } else { return (needed_flow-flow)*10*cost; } } //根据遗传算法得到的参数的解,得到服务器的位置 std::vector<int> GA_get_server_pos(double* data) { std::vector<int> result; if(nullptr == data) return result; for(int i=0; i<NVARS-1; i++) { if(data[i] > 0.88) result.push_back(i); } return result; } //my new design function of the distribution int Int_uniform_ab(int a, int b) { int tmp; tmp = (rand() % (b-a+1))+ a; return tmp; } //遗传算法初始化 void GA_initialize(int& seed) { // Initialize variables within the bounds std::vector<int> server;// = min_server; for (int i=0; i<client_node_num; i++) { server.push_back(client_node[i].innode[0]); } NVARS = net_node_num;//+1; //设置GA算法的参数变量的个数为网络中节点的个数 for (int i = 0; i < NVARS; i++) { for (int j = 0; j < POPSIZE; j++) { population[j].fitness = 0; population[j].rfitness = 0; population[j].cfitness = 0; population[j].lower[i] = 0; //代表不选中该点作为服务器 population[j].upper[i] = 1; //代表选中该点作为服务器 if (CheckIsServer(i, must_server)) { population[j].gene[i] = 1.0; } else if (CheckIsServer(i, forbid_server)) { population[j].gene[i] = 0.0; } else population[j].gene[i] = r8_uniform_ab(0.2, 0.9, seed); //population[j].gene[i] = Dou_uniform_ab(0.0, 1.0); } } } //保存最好的个体到最后一个中去 void keep_the_best( ) { int cur_best; int mem; int i; cur_best = 0; for ( mem = 0; mem < POPSIZE; mem++ ) { if ( population[POPSIZE].fitness < population[mem].fitness ) { cur_best = mem; population[POPSIZE].fitness = population[mem].fitness; } } // // Once the best member in the population is found, copy the genes. // for ( i = 0; i < NVARS; i++ ) { population[POPSIZE].gene[i] = population[cur_best].gene[i]; } return; } //变异 void mutate(int &seed ) { const double a = 0.0; const double b = 1.0; int i; int j; double lbound; double ubound; double x; for ( i = 0; i < POPSIZE; i++ ) { for ( j = 0; j < NVARS; j++ ) { x = r8_uniform_ab ( a, b, seed ); //x = Dou_uniform_ab( a, b); if ( x < PMUTATION ) { lbound = population[i].lower[j]; ubound = population[i].upper[j]; population[i].gene[j] = r8_uniform_ab ( lbound, ubound, seed ); //population[i].gene[j] = Dou_uniform_ab( lbound, ubound ); } } } return; } //通过随机种子选取a,b之间的随机数 double r8_uniform_ab( double a, double b, int &seed ) { int i4_huge = 2147483647; int k; double value; if ( seed == 0 ) { std::cerr << "\n"; std::cerr << "R8_UNIFORM_AB - Fatal error!\n"; std::cerr << " Input value of SEED = 0.\n"; exit ( 1 ); } k = seed / 127773; seed = 16807 * ( seed - k * 127773 ) - k * 2836; if ( seed < 0 ) { seed = seed + i4_huge; } value = ( double ) ( seed ) * 4.656612875E-10; value = a + ( b - a ) * value; return value; } //随机选择a,b之间的随机数 double Dou_uniform_ab(double a, double b) { double tmp; //rand() / double(RAND_MAX)可以生成0~1之间的浮点数 tmp = a + static_cast<double>(rand())/RAND_MAX*(b-a); return tmp; } //计算相对适应度和累积适应度,然后根据适应度进行选择和赋值 void selector(int &seed) { const double a = 0.0; const double b = 1.0; int i; int j; int mem; double p; double sum; double max_value(0); // Find the total fitness of the population. sum = 0.0; for (mem = 0; mem < POPSIZE; mem++) { max_value = max_value<population[mem].fitness?population[mem].fitness:max_value; } //for ( mem = 0; mem < POPSIZE; mem++ ) //{ // sum += max_value - population[mem].fitness; //} // Calculate the relative fitness of each member. for ( mem = 0; mem < POPSIZE; mem++ ) { population[mem].rfitness = (max_value-population[mem].fitness) / max_value; } // Calculate the cumulative fitness. population[0].cfitness = population[0].rfitness; for ( mem = 1; mem < POPSIZE; mem++ ) { population[mem].cfitness = population[mem-1].cfitness + population[mem].rfitness; } // Select survivors using cumulative fitness. for ( i = 0; i < POPSIZE; i++ ) { p = r8_uniform_ab ( a, b, seed ); //p = Dou_uniform_ab(a,b); if ( p < population[0].cfitness ) { newpopulation[i] = population[0]; } else { for ( j = 0; j < POPSIZE; j++ ) { if ( population[j].cfitness <= p && p < population[j+1].cfitness ) { newpopulation[i] = population[j+1]; } } } } // Overwrite the old population with the new one. for ( i = 0; i < POPSIZE; i++ ) { population[i] = newpopulation[i]; } return; } //基因的交叉操作 void Xover( int one, int two, int &seed ) { int i; int point; double t; // Select the crossover point. //point = i4_uniform_ab ( 0, NVARS - 1, seed ); point = Int_uniform_ab(0, NVARS-1); // Swap genes in positions 0 through POINT-1. for ( i = 0; i < point; i++ ) { t = population[one].gene[i]; population[one].gene[i] = population[two].gene[i]; population[two].gene[i] = t; } return; } ///////////////////////////////////////*遗传算法 end*//////////////////////////////////////////////////////
3. 总结
3.1 遗传算法深化
遗传算法的全局搜索能力强,但局部搜索能力就不行了。解释:比如对于一条染色体,遗传算法并不会去看看这条染色体周围局部的染色体适应度怎么样,是否比这条染色体好。遗传算法会通过变异和交叉产生新的染色体,但新产生的染色体可能和旧染色差的很远。因此遗传算法的局部搜索能力差。
对于一些局部搜索算法如:梯度法、爬山法和贪心法等算法的局部搜索能力强,运算效率也高。若能够将问题相关的启发知识引入这些算法,这些算法的威力不可小觑。受此启发,人们提出了混合遗传算法,将遗传算法和这些算法结合起来。混合遗传算法的框架是遗传算法的,只是生成新一代种群之后,对每个个体使用局部搜索算法寻找个体周围的局部最优点。
混合遗传算法是很常见的策略。比如遗传算法应用于排序问题,生成新一代种群之后,将个体中相邻两个元素交换次序,如果新的个体适应度更高则保留。这种贪心的变种往往能大幅度提高遗传算法的收敛速率。
3.2 个人观点
无论是上一讲中的禁忌搜索还是这篇文章中将的遗传算法,都是启发式算法,都有计算速度慢的问题,但是对于解决大规模问题是一种很好的方法。对这些概念的理解也很简单,可以用一下的一段话来理解:
曾经有过一个「有志气的兔子」的故事,相信很多朋友都听过:为了找出地球上最高的山,一群有志气的兔子们开始想办法:
兔子朝着比现在高的地方跳去。他们找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是局部搜索,它不能保证局部最优值就是全局最优值。
兔子喝醉了。他随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,他渐渐清醒了并朝最高方向跳去。这就是模拟退火。
兔子们吃了失忆药片,并被发射到太空,然后随机落到了地球上的某些地方。他们不知道自己的使命是什么。但是,如果你过几年就杀死一部分海拔低的兔子,多产的兔子们自己就会找到珠穆朗玛峰。这就是遗传算法。
兔子们知道一个兔的力量是渺小的。他们互相转告着,哪里的山已经找过,并且找过的每一座山他们都留下一只兔子做记号。他们制定了下一步去哪里寻找的策略。这就是禁忌搜索。
兔子朝着比现在高的地方跳去。他们找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是局部搜索,它不能保证局部最优值就是全局最优值。
兔子喝醉了。他随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,他渐渐清醒了并朝最高方向跳去。这就是模拟退火。
兔子们吃了失忆药片,并被发射到太空,然后随机落到了地球上的某些地方。他们不知道自己的使命是什么。但是,如果你过几年就杀死一部分海拔低的兔子,多产的兔子们自己就会找到珠穆朗玛峰。这就是遗传算法。
兔子们知道一个兔的力量是渺小的。他们互相转告着,哪里的山已经找过,并且找过的每一座山他们都留下一只兔子做记号。他们制定了下一步去哪里寻找的策略。这就是禁忌搜索。