人工智能实验:遗传算法求解TSP(旅行商问题)问题

本文通过遗传算法求解旅行商问题,对比了三种不同的变异算子(0、1、3)对求解结果的影响。实验结果显示,变异算子1和3的表现优于算子0,能更接近最优解。经过40次实验,变异算子1的平均解为441.536,而变异算子0的平均解为507.407。这表明变异策略对算法的收敛性和解决方案质量有显著影响。
摘要由CSDN通过智能技术生成

实验四 TSP问题的遗传算法实现

1. 实验内容

以N个节点的TSP(旅行商问题)问题为例,应用遗传算法进行求解,求出问题的最优解。
1 旅行商问题
旅行商问题(Traveling Salesman Problem, TSP),又译为旅行推销员问题、货担郎问题,简称为TSP问题,是最基本的路线问题。假设有n个可直达的城市,一销售商从其中的某一城市出发,不重复地走完其余n-1个城市并回到原出发点,在所有可能的路径中求出路径长度最短的一条。
TSP问题是组合数学中一个古老而又困难的问题,也是一个典型的组合优化问题,现已归入NP完备问题类。NP问题用穷举法不能在有效时间内求解,所以只能使用启发式搜索。遗传算法是求解此类问题比较实用、有效的方法之一。

下面给出30个城市的位置信息:
表1 Oliver TSP问题的30个城市位置坐标
城市编号 坐标 城市编号 坐标 城市编号 坐标
1 (87,7) 11 (58,69) 21 (4,50)
2 (91,38) 12 (54,62) 22 (13,40)
3 (83,46) 13 (51,67) 23 (18,40)
4 (71,44) 14 (37,84) 24 (24,42)
5 (64,60) 15 (41,94) 25 (25,38)
6 (68,58) 16 (2,99) 26 (41,26)
7 (83,69) 17 (7,64) 27 (45,21)
8 (87,76) 18 (22,60) 28 (44,35)
9 (74,78) 19 (25,62) 29 (58,35)
10 (71,71) 20 (18,54) 30 (62,32)
最优路径为:1 2 3 4 6 5 7 8 9 10 11 12 13 14 15 16 17 19 18 20 21 22 23 24 25 28 26 27 29 30
其路径长度为:424.869292
也可取前10个城市的坐标进行测试:

2. 程序清单

- 定义参数
//定义参数
#define MAX_CITIES 30               //城市数量
#define MAX_INDIVIDUALITY 300       //种群数量
#define Mutated_Rate 30             //变异率 30%
#define Generate_Rate 80            //繁衍率 80%
#define G_TIMES 500                 //迭代次数
int Position[MAX_CITIES][2];        //记录城市坐标
int candidate = 3;                  //候选个数,用于锦标赛选择算子
- 定义种群个体
class Individualty {
public:
    double Cost;                    //当前个体的路径长度
    vector<int> Path;               //路径数组
}Individualties[MAX_INDIVIDUALITY], StartIndividualties[MAX_INDIVIDUALITY];
//在统计时因为需要使用同一个初始种群,所以定义了Individualties数组和StartIndividualties数组
//Individualties数组用于计算过程的迭代。
//StartIndividualties数组用于保存初始种群的信息。
- 随机产生种群:
void RandPath() {
    for (int i = 0; i < MAX_INDIVIDUALITY; i++) {
        StartIndividualties[i].Path.resize(MAX_CITIES);
        bool status[MAX_CITIES] = { false };            //标记数组
        for (int j = 0; j < MAX_CITIES; j++) {
           int index = rand() % MAX_CITIES;            //随机产生城市下标
            while (status[index]) {                     //判断是否存在冲突
                index = rand() % MAX_CITIES;
            }
            status[index] = true;                       //设置标记
            StartIndividualties[i].Path[j] = index;
        }
    }
}
- 计算种群个体路径代价:
void ComCost(Individualty& a) {
    a.Cost = 0;
    for (int j = 0; j < MAX_CITIES; j++) {
        int dx = Position[a.Path[j]][0] - Position[a.Path[((j + 1) % MAX_CITIES)]][0];
        int dy = Position[a.Path[j]][1] - Position[a.Path[((j + 1) % MAX_CITIES)]][1];
        double direct = pow(dx, 2) + pow(dy, 2);
        a.Cost += pow(direct, 0.5);
    }
}

//计算种群中所有个体的路径代价,并按小到大排序
void ComPathCost() {
    for (int i = 0; i < MAX_INDIVIDUALITY; i++) {
        ComCost(Individualties[i]);
    }
    qsort(Individualties, MAX_INDIVIDUALITY, sizeof(Individualty), > (_CoreCrtNonSecureSearchSortCompareFunction)cmp);
}
- 输出最优个体(i=0)的路径及代价:
void ShowPath(int i=0) {
    cout << "cost:" << Individualties[i].Cost << endl;
    for (int j = 0; j < MAX_CITIES; j++) {
        cout << Individualties[i].Path[j] << ",";
    }
    cout << endl;
}
- 选择算子算法:将被选中的个体在种群中的下标放入vector中返回
//最佳选择算子,选择代价最小的前num个个体。
vector<int> BSel(int num) {
    if (num > MAX_INDIVIDUALITY)return vector<int>(0);
    vector<int> parents(num);
    for (int i = 0; i < num; i++) {
        parents[i] = i;
    }
	return parents;
}

//赌轮盘选择算子,选择num个个体
vector<int> RWSel(int num) {
    if (num > MAX_INDIVIDUALITY)return vector<int>(0);
    bool flags[MAX_INDIVIDUALITY] = { false };
    vector<int> parents(num);
    double sum = 0;
    for (int i = 0; i < MAX_INDIVIDUALITY; i++) {//计算所有个体的总路径代价sum
        sum += Individualties[i].Cost;
    }
    for (int i = 0; i < num; i++) {
        int r = rand() % ((int)sum);            //随机生产一个小于sum的随机数r
        double temp = 0;
        for (int j = 0; j < MAX_INDIVIDUALITY; j++) {//判断随机数r落在哪个区间
            if (temp + Individualties[j].Cost > r) {
                if (!flags[j]) {                     //判断是否已经被选择过
                    parents[i] = MAX_INDIVIDUALITY - 1 - j;//选择该个体作为父代
                }
                else {
                    j--;
                }
                break;
            }
            else {
                temp += Individualties[j].Cost;
            }
        }
    }
    return parents;
}

//锦标赛选择算子,candidate=3,选择num个个体
vector<int> TSel(int num) {
    if (num > MAX_INDIVIDUALITY || candidate > MAX_INDIVIDUALITY)return vector<int>(0);
    vector<int> parents(num);
    int* temp = new int[candidate];
    for (int i = 0; i < num; i++) {
        int j = 0;
        bool status[MAX_INDIVIDUALITY] = { false };
        for (; j < candidate;) {                    //选出candidate个候选
            temp[j] = rand() % MAX_INDIVIDUALITY;
            if (!status[temp[j]]) {                 //判断是否重复
                status[temp[j++]] = true;
            }
        }
        int min_index = 0;
        for (j = 1; j < candidate; j++) {       //从candidate个候选中选出最优的个体
            if (Individualties[temp[j]].Cost<Individualties[temp[min_index]].Cost) {
                min_index = j;
            }
        }
        parents[i] = temp[min_index];           //加入到parents数组,作为父代
    }
    delete[] temp;
    return parents;
}
- 交叉算子:
//交叉算子,对p1,p2为父代,产生一个子代。
vector<int> PMCros(int p1, int p2) {
    int l = rand() % MAX_CITIES, r = rand() % MAX_CITIES;   //随机选择交叉区间[l, r]
    if (l > r)swap(l, r);
    vector<int> son(Individualties[p2].Path);
    for (int i = l; i <= r; i++) {
        for (int j = 0; j < MAX_CITIES; j++) {
            if (son[j] == Individualties[p1].Path[i]) {
                swap(son[j], son[i]);
            }
        }
    }
    return son;
}

交叉算子流程图:
交叉算子

- 变异算子0:
//变异算子0,将第l,r个数据互换
void Mutated0(int i) {
    if ((rand() % 100) < Mutated_Rate) {//变异概率
        int l = rand() % MAX_CITIES;
        int r = rand() % MAX_CITIES;
        swap(Individualties[i].Path[l], Individualties[i].Path[r]);
    }
}

变异算子0流程图:
变异算子0

- 变异算子1:

变异算子0在实验中效果并不好,收敛到的结果与最优解差异较大
在结合问题思考后,发现了一种可以收敛结果更优的——变异算子1

//变异算子1,将第l-r个数据移动到头部
void Mutated1(int i) {
    if ((rand() % 100) < Mutated_Rate) {//变异概率
        Individualty temp;
        temp.Cost = 0;
        temp.Path.resize(MAX_CITIES);
        int l = rand() % MAX_CITIES, r = rand() % MAX_CITIES;
        if (l > r)swap(l, r);
        for (int j = l; j <= r; j++) {
            temp.Path[j - l] = Individualties[i].Path[j];
        }
        for (int j = 0; j < l; j++) {
            temp.Path[j + r - l + 1] = Individualties[i].Path[j];
        }
        for (int j = r + 1; j < MAX_CITIES; j++) {
            temp.Path[j] = Individualties[i].Path[j];
        }
        Individualties[i].Path = temp.Path;
        ComCost(Individualties[i]);
    }
}

变异算子1流程图:
变异算子1

- 统计结果:

在进行对该案例分别使用变异算子0和变异算子1(除变异算子外,其他参数算子完全相同)进行40次不同初始种群的重复实验,并统计迭代500代后的结果:

40次实验的平均解:
使用变异算子0:最终平均解为:507.407
使用变异算子1:最终平均解为:441.536
可以看出使用变异算子1整体结果比变异算子0要好,得出的解很接近最优解424.869

蓝色线为:变异算子0
橙色线为:变异算子1
绿色线为:变异算子3,与变异算子1差别不大

- 变异算子3

变异算子3,在变异算子1的基础上加入了一点改进,该变异算子加入判优过程,在执行后会判断适应度是否减小。如果适应度减小了,那么r将向后移动一个位置。当r移动到最后一个位置时,执行该变异算子其适应度将不会改变。因为12345和34512适应度是一样的。

//变异算子3,将第l-r个数据移动到头部,并判断是否变优,没有则r++,直到序列末尾
void Mutated3(int i) {
   if ((rand() % 100) < Mutated_Rate) {
       Individualty temp;
       temp.Cost = 0;
       temp.Path.resize(MAX_CITIES);
       int l = rand() % MAX_CITIES, r = rand() % MAX_CITIES;
       if (l > r)swap(l, r);
       do {
           for (int j = l; j <= r; j++) {
               temp.Path[j - l] = Individualties[i].Path[j];
           }
           for (int j = 0; j < l; j++) {
               temp.Path[j + r - l + 1] = Individualties[i].Path[j];
           }
           for (int j = r + 1; j < MAX_CITIES; j++) {
               temp.Path[j] = Individualties[i].Path[j];
           }
           ComCost(temp);
           r++;
       } while (temp.Cost > Individualties[i].Cost && r != MAX_CITIES);//判断是否变优?
       Individualties[i].Path = temp.Path;
       Individualties[i].Cost = temp.Cost;
   }
}

变异算子3流程图:
变异算子3

最优解:
最优解

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值