嗯哼,时隔半年,再次有时间整理关于组合优化问题——旅行商问题(Traveling Salesman Problem, TSP),这次采用的是经典遗传算法(Genetic Algorithm, GA)进行求解,利用C++语言进行编程实现。关于TSP问题以及GA的简单介绍,可参见我的另一篇文章:Java版GA_TSP(我的第一个Java程序)

  各种启发式算法的整体框架大致都由以下几个操作组成:(1)初始解的产生;(2)解的评价(评价函数);(3)扰动算子;此外,还可以加上程序原始数据的导入等操作。这些操作是多数启发式算法所通用的算子,基为此,此次在采用C++进行实现的时候,采用一个通用的 HeuristicOperator.h 头文件以及对应的 HeuristicOperator.cpp 类文件对这些操作进行集中放置,造好轮子,方便以后取用。

Ps:指针玩不太转,只好绕道过去了~~~~

表1 HeuristicOperator中函数功能清单

编号

功能

记号

函数1

获取客户点坐标

getCoord()

函数2

获取距离矩阵

getDM()

函数3

获取初始解

getInitS()

函数4

解的评价

Eval()

函数5

搜索范围内最优评价值及其相对应的位置

bestS()

函数6

产生Sharking操作位置

RandPosition()

函数7

交换算子(swap)

Swap()

函数8

翻转算子(flip)

Flip()

函数9

插入算子(insert)

Insert()

注:函数5可以直接用 STL 中vector的操作函数max_element、min_element实现类似的功能,写的时候一时没有想起来,就自己造了个轮子。

  之前在采用Matlab以及Java实现GA to solve TSP 时,考虑到程序的运行效率,通常会选用 array 来放置各种数据,众所周知,array的特点就是固定分配的连续物理地址进行数据的存储,然而对于不定长度的数据进行存储时,一般的方式是采用“多次少量”,即预先分配一定内存空间,等不够用了再分配一定的内存空间(多说一句,Matlab 中还可以采用以下两种方式:用 array=[]; 以及用cell实现存储不定长度数组,但效率不高)。而在C++ STL 中有一种神奇的数据类型vector容器,它既有着 array 的连续内存分配方式,又能不用指定数据存储长度,对于一组不同规模的数据集进行测试时,再也不用担心使用array时提醒必须预分配确定的存储空间了~~

  以下为HeuristicOperator.h头文件:

 #pragma once
#include<iostream>
#include<vector>
#include <algorithm> // std::shuffle
#include <random> // std::default_random_engine
#include<chrono>
using namespace std; class HeuristicOperator {
public:
vector<vector<double>> getCoord(void); //函数1:获取坐标函数
vector<vector<double>> getDM(vector<vector<double>> Coord); //函数2:获取距离矩阵函数
vector<int> getInitS(int n); //函数3:获取初始解函数
double Eval(vector<int> S, vector<vector<double>> DM, int n); //函数4:评价函数 vector<double> bestS(vector<double> Eval, int Length); //函数5:搜索范围内最优评价值及其相应的位置函数 vector<int> RandPosition(int n); //函数6:产生Sharking操作位置函数
vector<int> Swap(vector<int> S, vector<int> RP); //函数7:交换算子
vector<int> Flip(vector<int> S, vector<int> RP); //函数8:翻转算子
vector<int> Insert(vector<int> S, vector<int> RP); //函数9:插入算子
};

  对应的HeuristicOperator.cpp类文件比较容易实现,在此不再赘述。本文所用算例为31城市的TSP问题,与Java版遗传算法求解TSP求解算例一致,具体数据如下:


  以下为遗传算法的主函数:

 /*
文件名:CppGATSP
作者:Alex Xu
地址:Dalian Maritime University
描述:利用遗传算法求解TSP问题(C++版)
创建时间:2018年12月10日11点27分
*/ #include<iostream>
#include<vector>
#include<numeric> //accumulate
#include<chrono> //time
#include "HeuristicOperator.h"
using namespace std;
using namespace chrono; //设置算法参数
# define POP_SIZE
# define MAX_GEN int main() {
//计时开始
auto start = system_clock::now(); //生成距离矩阵
HeuristicOperator ga_dm;
vector<vector<double>> GA_DM;
GA_DM = ga_dm.getDM(ga_dm.getCoord()); int n = int(GA_DM[].size()); //城市规模 //初始化算法
vector<vector<int>> initPop(POP_SIZE, vector<int>(n)); //初始种群
vector<vector<int>> Pop(POP_SIZE, vector<int>(n)); //当前种群
vector<vector<int>> newPop(POP_SIZE, vector<int>(n)); //新种群
vector<double> popFit(POP_SIZE); //记录种群适应度值
vector<int> bestIndival(n); //最优个体
vector<double> gs(MAX_GEN + ); //记录全局最优解
gs[] = 1e9;
unsigned int seed = (unsigned)std::chrono::system_clock::now().time_since_epoch().count(); //生成初始种群
HeuristicOperator s0;
for (int i = ; i < POP_SIZE; i++) {
initPop[i] = s0.getInitS(n);
}
Pop = initPop; //开始进化
for (int gen = ; gen <= MAX_GEN; gen++) { HeuristicOperator eval; //计算种群的适应度值(这里直接用路径长度表示)
for (int i = ; i < POP_SIZE; i++) {
popFit[i] = eval.Eval(Pop[i], GA_DM, n);
} HeuristicOperator bestEI; //找出种群中个体的最优适应度值并记录相应的个体编号
vector<double> bestEvalIndex();
bestEvalIndex = bestEI.bestS(popFit, POP_SIZE);
double bestEval = bestEvalIndex[]; //最优适应度值
int bestIndex = int(bestEvalIndex[]); //最优适应度值对应的个体编号 //最优解的更新
if (bestEval < gs[gen - ]) { //比上一代优秀则更新
gs[gen] = bestEval;
bestIndival = Pop[bestIndex];
}
else { //不比上一代优秀则不更新
gs[gen] = gs[gen - ];
}
if (gen % == ) {
cout << "第" << gen << "次迭代时全局最优评价值为" << gs[gen] << endl;
} //扰动操作(产生新种群)
for (int p = ; p < POP_SIZE; p++) {
HeuristicOperator shk;
vector<int> randPosition = shk.RandPosition(n);
vector<int> tmpS(n);
double randShk = rand() / double(RAND_MAX);
if (randShk < 0.33) {
tmpS = shk.Swap(Pop[p], randPosition); //交换操作
}
else if (randShk >= 0.67) {
tmpS = shk.Flip(Pop[p], randPosition); //翻转操作
}
else {
tmpS = shk.Insert(Pop[p], randPosition); //插入操作
} HeuristicOperator evl;
if (evl.Eval(tmpS, GA_DM, n) > evl.Eval(Pop[p], GA_DM, n)) {
newPop[p] = Pop[p];
}
else {
newPop[p] = tmpS;
}
}
Pop = newPop; //选择操作(轮盘赌)
vector<double> Cusum(POP_SIZE + , ); //适用于轮盘赌的累加器Cusum(补充了cus[0]=0;
for (int i = ; i < POP_SIZE; i++) {
Cusum[i + ] = Cusum[i] + popFit[i];
} double Sum = accumulate(popFit.begin(), popFit.end(), 0.0); //计算各个体被选择的概率(归一化)
vector<double> cusFit(POP_SIZE + ); //放置种群中个个体被选择的概率值
for (int i = ; i < POP_SIZE + ; i++) {
cusFit[i] = Cusum[i] / Sum;
} for (int p = ; p < POP_SIZE; p++) { //轮盘赌产生新种群
double r = rand() / double(RAND_MAX);
for (int q = ; q < POP_SIZE; q++) {
if (r > cusFit[q] && r <= cusFit[q + ]) {
newPop[p] = Pop[q];
}
}
}
Pop = newPop;
} //计时结束
auto end = system_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout << "花费了"
<< double(duration.count()) * microseconds::period::num / microseconds::period::den
<< "秒" << endl; //输出结果
double gs0 = 15377.711;
cout << "最优解为" << gs[MAX_GEN] << endl;
double e = (gs[MAX_GEN] - gs0) / gs0;
cout << "误差为" << e * 100.0 << '%' << endl;
cout << "最优路径为" << endl;
for (int i = ; i < n; i++) {
cout << bestIndival[i] + << '\t';
}
140
141 while (1)
142     {}
}

  以上即为C++语言所编写的遗传算法求解TSP示例,运行环境为:Windows10 64位操作系统;CPU:i7-8750H; 内存:8G;Microsoft Visual Studio Community 2017 。求解结果如下:

  与已知最优解的误差为1.48%,所用时间约为3.6s. 还可以接受。但值得注意的是:本文实验参数最大迭代次数4000代,而种群规模仅为2,这与一般的遗传算法思想上是没问题的,只是实际参数可能取得不太大众化。当然,对于算法参数这些细节都是可以调节的,不必太过于纠结。

  啊哈,这次的利用C++编程遗传算法求解TSP就这些了~~~

遗传算法 | C++版GA_TSP的更多相关文章

  1. 遗传算法 | Java版GA_TSP(我的第一个Java程序)

    嗯哼,第一次写博客,准确说是第一次通过文字的方式记录自己的工作,闲话少叙,技术汪的博客就该直奔技术主题(关于排版问题,会在不断写博客的过程中慢慢学习,先将就着用吧,重在技术嘛~~~). 遗传算法(Ge ...

  2. 遗传算法 | Java版GA_TSP (2)

    嗯哼,上一篇博客中用Java实现了遗传算法求解TSP(Java版GA_TSP(我的第一个Java程序)),但明显求解效果不太好,都没太好意思贴出具体的结果,今天捣腾了下,对算法做了一些小改进,求解效果 ...

  3. python 遗传算法精简版

    精简版遗传算法,算法中仅采用变异算子而没有使用交叉算子,但是进化依然很有效 from string import ascii_lowercase from random import choice, ...

  4. 遗传算法之GAUL

    遗传算法之GAUL简介 简介        GAUL(遗传算法工具库的简称) GAUL is an open source programming library, released under th ...

  5. 多目标遗传算法 ------ NSGA-II (部分源码解析)介绍

    NSGA(非支配排序遗传算法).NSGA-II(带精英策略的快速非支配排序遗传算法),都是基于遗传算法的多目标优化算法,是基于pareto最优解讨论的多目标优化. 在官网: http://www.ii ...

  6. 标准遗传算法(实数编码 python实现)模拟二进制交叉SBX 多项式变异

    代码地址: https://github.com/guojun007/real_sga 本部分是采用实数编码的标准遗传算法,整体流程与上一篇二进制编码的基本一致, 主要区别在于本部分的交叉操作为模拟二 ...

  7. Python 遗传算法实现字符串

    Python 遗传算法实现字符串 流程 1. 初始化 2. 适应度函数 3. 选择 4. 交叉 5. 变异 适应度函数计算方法 计算个体间的差:分别计算每个元素与目标元素的差取平方和 种群:计算总体均 ...

  8. 遗传算法(Genetic Algorithm)——基于Java实现

    一.遗传算法原理介绍 遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法.遗传算法是从代表问 ...

  9. 基于GA遗传算法的TSP旅行商问题求解

    import random import math import matplotlib.pyplot as plt import city class no: #该类表示每个点的坐标 def __in ...

随机推荐

  1. 傻瓜式的go modules的讲解和代码,及gomod能不能引入另一个gomod和gomod的use of internal package xxxx not allowed

    一 国内关于gomod的文章,哪怕是使用了百度 -csdn,依然全是理论,虽然golang的使用者大多是大神但是也有像我这样的的弱鸡是不是? 所以,我就写个傻瓜式教程了. github地址:https ...

  2. WPF Virtualization

    WPF虚拟化技术分为UI 虚拟化和数据虚拟化 第一种方法被称为"UI 虚拟化".支持虚拟化用户界面的控件是足够聪明来创建只显示的是实际在屏幕上可见的数据项目所需的 UI 元素.例如 ...

  3. Android客户端与PC服务端、android服务端通过WiFi通信

    前期准备:我的是Linux Mint操作系统(总之折腾的过程中怀疑过是不是系统的问题),首先是要创建wifi热点给android手机使用,这个时候笔记本作为通信的服务器端,android手机作为客户端 ...

  4. textArea 默认添加的空格

    1.会莫名多出一些空格(标签闭合换行了) <textarea class="borderstyle width97b height75" name="SmsTax. ...

  5. ArcGIS for Server新建站点异常,Failed to create the site.Failed to configure the server machine'XXXX',Server machine'XXXX' is not a local server machine.

      系统环境:操作系统Win7 64位,装在虚拟机VM中,ArcGIS for Server 10.2.1 问题描述:ArcGIS for Server 10.2.1安装并授权完成后,站点初始化时显示 ...

  6. Team Foundation 版本控制

    与 Visual Studio 的一流集成. 使用富文件和文件夹差异工具突出显示代码更改. 借助强大的可视化跨分支跟踪代码更改. 集成的代码评审工具有助于在签入代码之前获得反馈. 使用托管版本或本地版 ...

  7. Oracle数据库基础--建表语法+操作

    语法 1.建表 create table 表名( 列名 数据类型, …… ); 2.删除表:drop table 表名; 3.添加列:alter table 表名 add(列名 数据类型); 4.修改 ...

  8. C# linq根据自定义筛选条件和所对应的数值进行筛选

    在软件应用中有时候会出现这样的界面:上面是利用多选框和下拉框组合的筛选条件.下面表格展示筛选后的数据.如下图 上面是筛选条件,表格是根据筛选条件筛选的结果. 如果表格不支持筛选功能.可以利用Linq对 ...

  9. sk-learning(2)

    sk-learning 学习(2) sklearing 训练评估 针对kdd99数据集使用逻辑回归分类训练 然后进行评估 发觉分数有点高的离谱 取出10%数据494021条,并从中选择四分之一作为测试 ...

  10. Android中的Matrix(矩阵)

    写在前面 看这篇笔记之前先看一下参考文章,这篇笔记没有系统的讲述矩阵和代码的东西,参考文章写的也有错误的地方,要辨证的看. 如何计算矩阵乘法 android matrix 最全方法详解与进阶(完整篇) ...