「C++」遗传算法求解最佳路径问题——“一日游”行程规划程序
「C++」遗传算法求解最佳路径问题——“一日游”行程规划程序
1 题目描述
为游客提供行程规划服务,在满足时间、资金约束下,使得行程的总体质量较高、路程较短。
- 某地有一组旅游景点,景点具有名称、营业时间、门票价格、评分、游玩时长、**地理位置(经纬度坐标)**等信息。
- 该程序要根据游客的资金预算,自动生成一条合理的旅游路线。
2 题目要求
-
“一日游”的时间从早上八点到晚上七点;
-
生成的线路应尽量用完以上的游玩时间,但不能超时;
-
不需要考虑出发点和结束点,即可以从任意景点出发、在任意景点结束;
-
以线路中所有景点的评分的均值作为该线路的评价得分;
-
以所有景点的门票价格之和作为该线路的价格;
-
以各景点间的距离之和作为线路的路程长度;
-
线路的质量优劣,按以下公式计算:
Q = 0.4 ∗ n o r m a l ( 评价得分 ) + 0.4 ∗ n o r m a l ( 价格 ) + 0.2 ∗ n o r m a l ( 路程长度 ) Q=0.4 * normal(评价得分) + 0.4 * normal(价格)+0.2 * normal(路程长度) Q=0.4∗normal(评价得分)+0.4∗normal(价格)+0.2∗normal(路程长度)
其中, n o r m a l ( 评价得分 ) = 评价得分 景点数量 ; 0 ≤ 评价得分 ≤ 5 normal(评价得分) = \frac{评价得分}{景点数量}; 0\le评价得分\le5 normal(评价得分)=景点数量评价得分;0≤评价得分≤5
n o r m a l ( 价格 ) = 预算 − 价格 预算 ; 0 ≤ 价格 ≤ 预算 normal(价格)=\frac{预算-价格}{预算}; 0≤价格\le预算 normal(价格)=预算预算−价格;0≤价格≤预算
n o r m a l ( 路程长度 ) = 最大长度 − 路程长度 最大路程 ; 0 ≤ 路程长度 ≤ 最大长度 normal(路程长度)=\frac{最大长度-路程长度}{最大路程}; 0\le路程长度\le最大长度 normal(路程长度)=最大路程最大长度−路程长度;0≤路程长度≤最大长度 -
输入:
200,50//说明:200为游客的预算,则生成的线路的价格不能超过该预算;50为游客一天最远的行程,则线路的路程长度不能超过该最远行程。
输出:
//如果可行,则输出一条较优的线路: 景点1、景点3、景点8、景点4 (0.82、4.2、180、50)//说明:先按顺序输出景点名称,括号内分别为线路的质量得分、评价得分、价格、路程长度。 //如果不可行,如输入的预算为0且无免费景点,则输出: error
3 解决方案
3.1 遗传算法
遗传算法(Genetic Algorithm,简称GA)起源于对生物系统所进行的计算机模拟研究,是一种随机全局搜索优化方法,它模拟了自然选择和遗传中发生的复制、交叉和变异等现象,从任一初始种群出发,通过随机选择、交叉和变异操作,产生一群更适合环境的个体,使群体进化到搜索空间中越来越好的区域,这样一代一代不断繁衍进化,最后收敛到一群最适应环境的个体(Individual),从而求得问题的优质解[1]。
3.2 构思
3.2.1 核心定义的类
在C++中,内置的数据类型无法很好地满足我们的特殊需求,虽然STL容器拥有很强大且完备的功能,但是有些信息还是定义为类比较好处理,比如记录景点信息、路线信息、遗传信息等。需要定义几个主要的类:
- scenicSpot类用于记录一个景点的信息(景点名、开放时间、关闭时间、价格、坐标等);
- Chrom类用于记录条路线的信息(路线、路线评价、路线价格、路线路程等);
- Evolve类用于记录种群,并实现种群的进化方法。
3.2.2 常用类和方法
除此之外,还有一些数据类型和功能被反复用到,处理成单独的类和函数比较方便管理和计算:
- coordinate类,记录坐标,并实现坐标的计算功能;
- mytime类,记录时间,只记录时、分、秒,方便时间的运算;
- selector_sort函数,选择排序。
- find_vector函数,在vector中查找元素并返回下标。
- vector容器存储路线节点,方便变化长度,作为辅助作用
3.2.3 遗传算法的策略
- 精英遗传,当前一代的最优解之间相互杂交;
- 劣等淘汰,每一代最差的结果被淘汰;
- 优质选择,淘汰掉本代最差的结果后,从子代中选择最好的一批结果补充,以保持种群规模;
4 代码一览
4.1 mytime类
class mytime
{
private:
int hour;
int min;
int sec;
public:
mytime() { this->hour = this->min = this->sec = 0; }
mytime(int h, int m = 0, int s = 0);
mytime(string t);
mytime(double t);
~mytime(){};
int H() const { return this->hour; }//返回小时
int M() const { return this->min; }//返回分钟
int S() const { return this->sec; }//返回秒
void H(int h) { this->hour = h; }//修改小时
void M(int m) { this->min = m; }//修改分钟
void S(int s) { this->sec = s; }//修改秒
int to_sec() const;//时间转为整数
mytime &operator=(const mytime &m);//重载赋值运算符
mytime &operator+=(const mytime &y);//重载自增运算符
friend mytime toMytime(int sec);//整型转为时间
friend ostream &operator<<(ostream &out, const mytime m);//重载输出运算符
//重载四则运算
friend mytime operator+(const mytime &m, const mytime &y);
friend mytime operator-(const mytime &m, const mytime &y);
friend double operator/(const mytime &m, const mytime &y);
friend double operator*(const int &m, const mytime &y);
//重载逻辑运算
friend bool operator>(const mytime &m, const mytime &y);
friend bool operator<(const mytime &m, const mytime &y);
friend bool operator>=(const mytime &m, const mytime &y);
friend bool operator<=(const mytime &m, const mytime &y);
friend bool operator==(const mytime &m, const mytime &y);
friend bool operator!=(const mytime &m, const mytime &y);
};
4.2 coordinate类
class coordinate
{
private:
float abscissa; // 横坐标
float ordinate; // 纵坐标
public:
coordinate() {}
coordinate(float x, float y);
coordinate(int x, int y);
coordinate(double x, double y);
float x() const { return abscissa; }//返回横坐标
float y() const { return ordinate; }//返回纵坐标
// 赋值
coordinate &operator=(const coordinate &coord);
// 计算距离
float distance(coordinate c);
friend ostream &operator<<(ostream &out, const coordinate c);
};
4.3 选择排序
template <typename T>
void selection_sort(std::vector<T> &arr)
{
for (int i = 0; i < arr.size() - 1; i++)
{
int min = i;
for (int j = i + 1; j < arr.size(); j++)
if (arr[j] < arr[min])
min = j;
std::swap(arr[i], arr[min]);
}
}
4.4 查找vector元素
// 查找
template <typename T>
int find_vector(vector<T> tlist, T t)
{
for (int i = 0; i < tlist.size(); i++)
{
if (tlist[i] == t)
{
return i;
}
}
return -1;
}
4.5 scenicSpot类
class scenicSpot
{
private:
string scenicname; // 景点名称
mytime busi_hour; // 营业时间
mytime clos_hour; // 歇业时间
float scenicprice; // 门票价格
float scenicscore; // 评分
mytime scenicduration; // 游玩时长
coordinate sceniccoord; // 景点坐标
public:
scenicSpot() {}
scenicSpot(string name, mytime busi = mytime(8), mytime clos = mytime(19), float price = 100, float score = 0, mytime duration = mytime(1), coordinate coord = coordinate(0, 0));
~scenicSpot(){};
// 返回各项成员
string name() const { return this->scenicname; }
mytime businesshour() const { return this->busi_hour; }
mytime closerhour() const { return this->clos_hour; }
float price() const { return this->scenicprice; }
float score() const { return this->scenicscore; }
mytime duration() const { return this->scenicduration; }
coordinate coord() const { return this->sceniccoord; }
// 重载运算符
scenicSpot &operator=(const scenicSpot s);
bool operator==(const scenicSpot &s);
friend ostream &operator<<(ostream &out, const scenicSpot &s);
};
4.6 Chrom类
// 染色体
class Chrom
{
private:
vector<scenicSpot> geneorder; // 基因序列,路线
double chromstar; // 路线评价
double chromdistance; // 路线路程
double chromprice; // 路线票价
mytime chromendtime; // 路线游玩时间
double chromscore; // 这条染色体的适应度
public:
Chrom();
Chrom(scenicSpot *gene);
Chrom(vector<scenicSpot> gene);
~Chrom(){};
// 计算参数‘
void calculate();
// 输出信息
vector<scenicSpot> gene() const { return this->geneorder; }
int size() const { return this->geneorder.size(); }
double score() const { return this->chromscore; } // 返回得分
double star() const { return this->chromstar; } // 返回路线评价
double distance() const { return this->chromdistance; } // 返回路线路程
double price() const { return this->chromprice; } // 返回路线票价
mytime endtime() const { return this->chromendtime; } // 返回路线游玩时间
coordinate end() const { return this->geneorder.back().coord(); } // 返回最后一站的坐标
string route() const; // 输出路线
// 其他操作
void append(scenicSpot s); // 增加路线长度
vector<scenicSpot> getblock(int start, int end); // 获取片段
vector<scenicSpot> swapnodeAbarr(); // 交换节点变异
vector<scenicSpot> swapEpisodeAbarr(); // 片段易位变异
vector<scenicSpot> inverseEpisodeAbarr(); // 片段逆序变异
void adjust(double budget, double maxtrip); // 调整(如果超预算,删除尾部节点,以保证子代的有效性)
Chrom onesideCross(vector<scenicSpot> cross, int start); // 杂交时的一边变换
double calcscore(double budget, double maxtrip); // 计算线路得分
// 重载算符
friend ostream &operator<<(ostream &out, const Chrom &c);
Chrom &operator=(const Chrom s);
bool operator<(const Chrom s);
bool operator<=(const Chrom s);
bool operator>(const Chrom s);
bool operator>=(const Chrom s);
bool operator!=(const Chrom s);
bool operator==(const Chrom s);
};
4.7 Evolve类
// 遗传算法
class Evolve
{
private:
vector<Chrom> parents; // 父代
vector<Chrom> children; // 子代
vector<scenicSpot> scenicmap; // 景点地图(坐标)
vector<vector<double>> distance_map; // 记录景点间的距离
double maxtrip; // 路线的最大路程
double budget; // 预算
int maxgen; // 最大迭代次数
int populat_scale; // 群体规模
double cross_prob; // 交叉概率
double abarr_prob; // 变异概率
double select_prob; // 选择概率
int children_scale; // 子代的选取个数
public:
Evolve(int maxgen = 100, int pop_scale = 200, double crossp = 0.8, double abarrp = 0.0125, double slectp = 0.5, string path = "./map.csv");
~Evolve(){};
bool importMap(string path); // 从文件导入景点地图
bool calcdistMap(); // 计算路径
void randomParents(); // 随机生成第一父代
void Cross(); // 杂交
void Aberrant(); // 变异(基因点交换、基因片段倒序、基因增长)
void select(); // 选择子代插入父代
bool evolveNow(); // 开始演化
};
4.8 main函数
int main()
{
Evolve evolution;
evolution.evolveNow();
return 0;
}