「C++」蚁群算法求解最佳路径问题(一日游规划)

「C++」蚁群算法求解最佳路径问题(一日游规划)

    • 1 题目描述
    • 2 题目要求
    • 3 解决方案
      • 3.1 蚁群算法
        • 3.1.1 算法简介
        • 3.1.2 算法逻辑
        • 3.1.3 蚂蚁的策略
        • 3.1.4 蚁群算法在本例中的应用
      • 3.2 构思
        • 3.2.1 核心定义的类
        • 3.2.2 常用类和方法
    • 4 代码概览
      • 4.1 mytime类
      • 4.2 coordinate类
      • 4.3 选择排序
      • 4.4 查找vector元素
      • 4.5 scenicSpot类
      • 4.6 class Ant核心函数
      • 4.7 class antColony核心函数
      • 4.8 一些参数
      • 4.9 main
    • 5 参考

1 题目描述

为游客提供行程规划服务,在满足时间、资金约束下,使得行程的总体质量较高、路程较短。

  • 某地有一组旅游景点,景点具有名称营业时间门票价格评分游玩时长、**地理位置(经纬度坐标)**等信息。
  • 该程序要根据游客的资金预算,自动生成一条合理的旅游路线。

上次采用遗传算法实现了这个功能,这次采用蚁群算法。


2 题目要求

  1. “一日游”的时间从早上八点到晚上七点;

  2. 生成的线路应尽量用完以上的游玩时间,但不能超时;

  3. 不需要考虑出发点和结束点,即可以从任意景点出发、在任意景点结束;

  4. 以线路中所有景点的评分的均值作为该线路的评价得分

  5. 所有景点的门票价格之和作为该线路的价格

  6. 以各景点间的距离之和作为线路的路程长度

  7. 线路的质量优劣,按以下公式计算:
    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.4normal(评价得分)+0.4normal(价格)+0.2normal(路程长度)
    其中, 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路程长度最大长度

  8. 输入:

    20050//说明:200为游客的预算,则生成的线路的价格不能超过该预算;50为游客一天最远的行程,则线路的路程长度不能超过该最远行程。
    

    输出:

    //如果可行,则输出一条较优的线路:
    景点1、景点3、景点8、景点4 (0.824.218050)//说明:先按顺序输出景点名称,括号内分别为线路的质量得分、评价得分、价格、路程长度。
    //如果不可行,如输入的预算为0且无免费景点,则输出:
    error
    

3 解决方案

3.1 蚁群算法

3.1.1 算法简介

蚁群算法(Ant Clony Optimization, ACO)是一种群智能算法,它是由一群无智能或有轻微智能的个体(Agent)通过相互协作而表现出智能行为,从而为求解复杂问题提供了一个新的可能性。蚁群算法最早是由意大利学者Colorni A., Dorigo M. 等于1991年提出。经过20多年的发展,蚁群算法在理论以及应用研究上已经得到巨大的进步。

蚁群算法是一种仿生学算法,是由自然界中蚂蚁觅食的行为而启发的。在自然界中,蚂蚁觅食过程中,蚁群总能够按照寻找到一条从蚁巢和食物源的最优路径。[1]

3.1.2 算法逻辑

  1. 放一只蚂蚁在一个起点处,由蚂蚁来决定下一步走哪一个节点;
  2. 蚂蚁经过一条路线,会留下信息素(Flomone),为后来的蚂蚁提供选择的依据;
  3. 信息素随时间衰减;
  4. 每一只蚂蚁经过这条路线都会带来信息素,以减缓信息素的衰减效果;
  5. 每一次迭代中的蚂蚁都会完成走完一条路线,一次迭代结束后根据蚂蚁走过路线的情况更新信息素,再进行下一次迭代;

3.1.3 蚂蚁的策略

  1. 在其他情况相同的情况下,蚂蚁倾向于选择路程更短的节点;
  2. 在路程相同的情况下,蚂蚁倾向于选择信息素浓度更高的路线;
  3. 蚂蚁通过轮盘赌的方式选择下一个节点,选择一个节点的概率为:
    P i j = τ i j α η i j β Σ z τ i z α η i z β P_{ij}=\frac{\tau^\alpha_{ij}\eta^\beta_{ij}}{\Sigma_{z}\tau^\alpha_{iz}\eta^\beta_{iz}} Pij=Σzτizαηizβτijαηijβ

其中,z是所有剩余的可选择的节点, τ i j \tau_{ij} τij为第 i i i个节点到第 j j j个节点的路径上的信息素, η i j \eta_{ij} ηij为第 i i i个节点到第 j j j个节点的“能见度”(路程的倒数), α 和 β \alpha和\beta αβ分别为信息素和“能见度”的重要系数;

α \alpha α β \beta β影响蚁群的策略,需要一个适当的比例;

  1. alpha=0时,无视 flomone,蚂蚁完全根据能见度(上一次路线得分)做判断,容易陷入局部最优解;
  2. beta=0时,无视上次路线得分,蚂蚁完全根据flomone做判断,收敛速度快,但很难达到最优解;

公式解释: 从当前节点 i i i到节点 j j j的概率为这两个节点间的信息素与能见度的乘积,占当前节点到所有可选择节点的比例。

  1. 信息素的更新由下面的公式决定:
    τ i j ′ = ρ τ i j + Δ τ \tau_{ij}'=\rho\tau_{ij}+\Delta\tau τij=ρτij+Δτ
    其中, ρ \rho ρ为信息素的衰减系数, Δ τ \Delta\tau Δτ为一轮迭代后蚂蚁带来的信息素变化。
    Δ τ = Q L \Delta\tau=\frac{Q}{L} Δτ=LQ
    Q Q Q为一个常量,可取1; L L L为这一只蚂蚁选择的路线的路程;每一只蚂蚁都会带来信息素的变化,则 Δ τ \Delta\tau Δτ应是所有变化的总和。

3.1.4 蚁群算法在本例中的应用

  1. 初始信息素浓度都为1;
  2. 由于本问题不是求解最短路径,而是有评分作为指标找出最优的路线,所以能见度定义为 η = 0.4 ∗ n o r m a l ( 评价得分 ) + 0.4 ∗ n o r m a l ( 价格 ) + 0.2 ∗ n o r m a l ( 路程长度 ) \eta=0.4 * normal(评价得分) + 0.4 * normal(价格)+0.2 * normal(路程长度) η=0.4normal(评价得分)+0.4normal(价格)+0.2normal(路程长度)
  3. 将信息素的变化量也相应改为 Δ τ = η Q \Delta\tau=\eta Q Δτ=ηQ

3.2 构思

本质上需要用到的信息与《「C++」遗传算法求解最佳路径问题——“一日游”行程规划程序》相似,所以大部分内容不需要修改。主要的差异在于实现算法的逻辑。

3.2.1 核心定义的类

需要定义几个主要的类:

  1. scenicSpot类用于记录一个景点的信息(景点名、开放时间、关闭时间、价格、坐标等);
  2. Ant类用于记录条路线的信息(路线、路线评价、路线价格、路线路程等);
  3. antColony类用于记录蚁群和信息素等信息。

3.2.2 常用类和方法

除此之外,还有一些数据类型和功能被反复用到,处理成单独的类和函数比较方便管理和计算:

  1. coordinate类,记录坐标,并实现坐标的计算功能;
  2. mytime类,记录时间,只记录时、分、秒,方便时间的运算;
  3. selector_sort函数,选择排序;
  4. find_vector函数,在vector中查找元素并返回下标;
  5. vector容器存储路线节点,方便变化长度,作为辅助作用;
  6. 除此之外还可重载矩阵的四则运算符,方便做矩阵的计算;

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 class Ant核心函数

class Ant
{
private:
    vector<scenicSpot> nodes; // 基因序列,路线
    double Antstar;           // 路线评价
    double Antdistance;       // 路线路程
    double Antprice;          // 路线票价
    mytime Antendtime;        // 路线游玩时间
    double Antscore;          // 路线得分
public:
	//略去不重要的函数
    // 计算评分、路程等参数
    void calculate();
    // 其他操作
    vector<vector<double>> deltaTau(); // 返回这只蚂蚁带来的flomone变化
    bool isExist(scenicSpot spot) const;// 判断路线是否已经存在该景点
    bool append();// 增加路线长度
    double calcscore(double budget, double maxtrip);// 计算线路得分
    // 重载算符
    friend ostream &operator<<(ostream &out, const Ant &c);
    Ant &operator=(const Ant s);
};

4.7 class antColony核心函数

class antColony
{
private:
    /* data */
    vector<Ant> ants;                // 蚂蚁
    vector<Ant> best_ant;            // 存储迭代中的最佳路径,防止好的结果被刷新掉(取20个)
    vector<vector<double>> flomone;  // 弗洛蒙浓度(信息素)
    vector<scenicSpot> spots;        // 景点信息
    vector<vector<double>> spotsmap; // 景点地图(路程信息)
    double bugdet;                   // 预算
    double maxtrip;                  // 最大路程
    int maxgen;                      // 最大迭代次数
    ofstream fout;                   // 把过程写入文件方便查看
public:
    //略去其他不重要的成员函数
    int roulette();                   // 轮盘赌
    bool import(string path); // 从文件导入景点信息
    bool calcDist();// 计算景点间距离
    bool initFlomone(); // 初始化flomone
    bool initAnt();// 初始化蚂蚁
    bool updateFlomone(); // 更新Flomone
    void Run();// 开始运行
};

4.8 一些参数

#define Q 0.1     // 系统常数(取值任意)
#define rho 0.3   // 挥发系数
#define alpha 0.5 // flomone 重要程度系数
#define beta 1.5  // 能见度重要程度系数(此处能见度定义为路线得分)
#define tau 1     // flomone浓度初始值
#define ants_num 10// 一个起点放置的蚂蚁数量

4.9 main

#include "antColony.hpp"
int main(int argc, char *argv[])
{
    antColony antcolony;
    antcolony.Run();
    return 0;
}

5 参考

[1] 蚁群算法(Ant Colony Optimization)
[2] 【数之道 04】解决最优路径问题的妙招-蚁群ACO算法
[3] 「C++」遗传算法求解最佳路径问题——“一日游”行程规划程序

你可能感兴趣的:(C/C++,算法,c++,人工智能)