禁忌算法、遗传算法,HEAD算法求解图染色问题(K-Colori

2018-04-25  本文已影响0人  缱绻顾

具体可参考本人博客GitHub
HEAD算法论文中得到了K=47的结果,但是运算量太大,本人并没有得到。
算法主要参考了 hujinglovekmg《禁忌搜索算法解决图着色问题》和华科吕志鹏老师的教学ppt《Chapter 4 Hybrid Evolutionary Algorithm: A Case Study on Graph Coloring》

核心代码部分

01 判断条件优化

在ppt中,关于禁忌算法的描述如下图:


禁忌搜索算法

步骤3.3,会计算出delt相同的moves,而相同的moves中,tabu & non-tabu moves可能同时存在。而在步骤3.4中,在上述情况存在时,若满足aspiration,则会优先选择tabu moves。而我们希望的,应该是在此种情况时,tabu & non-tabu moves一起随机选择。因此,可以把判断条件写为

if (tmp_delt <= delt && ( iter> h_tabu[j]|| (tmp_delt + f)<best_f)) { // 1&&(2||3)
    if (tmp_delt < delt) {//当前解小于本次迭代最优解,则重新开始保存解
    equ_count = 0;
    delt = tmp_delt;
    }
    equ_delt[equ_count][0] = i;
    equ_delt[equ_count][1] = j;
    equ_count++;
}

在计算完所有的critical one-moves后,再从equ_delt保存的moves中,随机选一个。

    int tmp = rand() % equ_count;//有多个解时,随机选择
    sel_vertex = equ_delt[tmp][0];
    sel_color = equ_delt[tmp][1];

其中,变量delt表示本次迭代的最好解,变量tmp_delt表示当次计算出的delt,变量best_f表示历史最好解。i和 j 分别表示顶点和颜色。
若满足条件(1&&2),则是non-tabu move;若满足条件(1&&3),则是tabu move。

02 随机选解的另一种方法

另有一种并不需要每次都保存下相同delt的move的方法。

if (tmp_delt <= delt && ( iter> h_tabu[j]|| (tmp_delt + f)<best_f)) { // 1&&(2||3){
    if (tmp_delt < delt) {
    equ_count = 1;
    delt = tmp_delt;
    sel_vertex = i;
    sel_color = j;
    }
    if (rand() % equ_count == 0) {
    sel_vertex = i;
    sel_color = j;
  }
  equ_count++;
}                           
if (rand() % equ_count == 0) {
    sel_vertex = i;
    sel_color = j;
}// 耗时约为3/10000毫秒
equ_delt[equ_count][0] = i;
equ_delt[equ_count][1] = j;
//耗时约为7/10000000毫秒

所以在equ_count已知范围,并且范围不大(避免数组出现问题)的情况下,用前节的方法会更好。
在本例中,equ_count的最大值为N*(K-1),N为总顶点数,K为总颜色数。即在一次迭代中,可以得到相同delt的moves,最多有N*(K-1)个。

运行结果

迭代次数 时间(ms) 迭代频率(次/s)
1 6637998 20931 317137
2 23955577 75391 317751
3 38922336 123361 315516
4 96602832 302124 319746
5 12942501 40556 319127
6 2570572 8239 312000
7 139941246 439614 318328
8 218616457 690711 316509
9 52939800 168495 314192
10 7037286 21899 321352
11 9168899 28408 322758
12 13672252 41232 331593
平均值 51917313 163413.4 318834

非核心部分

01 初始化读取文件

算例DSJC500.5.col的格式为

DSJC500.5.col的部分截图
#include<string>
#include<fstream>
using namespace std;
void readData(string fileName)
{
    ifstream ifs;
    ifs.open(fileName);
    string str;
    ifs>>str;
    while(!ifs.eof())
    {
        if(str=="edge")
        {
            ifs>>N; //int N,顶点数目
            //其他代码
        }
        if(str=="e")
        {
            int m,n;
            ifs>>m>>n;
            //其他代码
        }
        ifs>>str;
    }
    ifs.close();
}

ifstream ifs;即可实现把文件内容读入不同类型的变量。

02 随机生成初始解

#include<stdlib.h>
    sol = new int[N];//每个顶点的颜色
    srand(time(NULL));
    for (int i = 0; i < N; i++)
    {   
        sol[i] = rand() % K;  //给每个点随机着色    
    }

注意,rand()生成的随机数,实际上是伪随机数,srand()则决定了选择的随机数序列是哪一个。srand(time(NULL))则是按照系统实际,选择随机数序列,若不加上这一句,则每次都会得到相同的初始解。

小结

本文中的禁忌搜索算法的迭代次数与初始解的关系很大,怎样才能迅速收敛到最优解,还需思考。
算例DSJC500.5.col的历史最优解为48,用纯禁忌算法已经很难算出来。
越简单的数据类型,效率越高。最开始我用了vector,后来用了链表,再后来全改成了数组,速度有了很大的提升。

上一篇 下一篇

猜你喜欢

热点阅读