蚁群算法
概述
蚁群算法(Ant Colony Algorithm)是由意大利学者 Dorigo M等于20世纪90年代初期受到自然界中真实蚂蚁觅食行为启发而提出的一种仿生优化算法。算法采用分布式并行计算机制,具有较强的鲁棒性,且易与其它优化算法相结合。
蚂蚁是一种社会性昆虫,蚂蚁之间可以相互协作完成复杂 的任务。单个蚂蚁的行为较为简单,但是由简单个体所组成的蚂蚁群体却表现出了极为复杂的行为。真实蚁群在觅食时能够在蚁穴和食物之间找到一条最短路径,并且在环境变化时,比如出现新的障碍物时,蚁群可以相互协作找到一条新的最短路径。群在觅食路径上释放信息素,单个蚂蚁通过感知路径上的信息素强度按概率选择下一步的行进方向,而蚂蚁之间则通过感知和释放信息素完成了间接的信息传递。当觅食路径上有了新的障碍物时,信息素轨迹暂时被隔断,此时蚂蚁随机地选择 下一步的行进方向,而恰好选择了障碍物附近新的最短路径的那些蚂蚁将最先重构起连续的信息素轨迹。久而久之,选择短路径的蚂蚁越来越多,使得短路径上的信息素的强度越发大于较长路径上的信息素强度,从而使得后续的蚂蚁以较大的概率选择短路径,这种自身催化过程形成的信息正反馈机制使得蚁群最终可以找到新的最短路径。
蚁群算法最早应用于旅行商问题,并取得了广泛的应用,诸如作业调度、路径规划、数据挖掘等多个领域。现以TSP问题阐述蚁群算法的基本原理:
设将 m m m只蚂蚁放置在 n n n个随机城市上,设所有城市组成的集合为 C C C,城市 i i i与城市 j j j之间的距离为 d i j ( i , j = 1 , 2 , . . . , n ) d_{ij}(i,j=1,2,...,n) dij(i,j=1,2,...,n), t t t时刻城市 i i i与城市 j j j连接路径上的信息素浓度为 τ i j ( t ) \tau _{ij}(t) τij(t)。初始时刻,各个城市间连接路径上的信息素浓度相同,不妨设为 τ i j ( 0 ) = τ 0 \tau _{ij}(0)=\tau _{0} τij(0)=τ0.
Step1:状态转移概率准则。每只蚂蚁根据各条路径上的 信息量独立地选择下一个要转移的城市,并将蚂蚁
k
k
k走过的城市记录在禁忌表
t
a
b
u
k
tabu_k
tabuk中。在
t
t
t时刻蚂蚁
k
k
k从城市
i
i
i移动到城市
j
j
j的状态转移概率
p
i
j
k
(
t
)
p_{ij}^{k}(t)
pijk(t)表达式如下:
p
i
j
k
(
t
)
=
{
[
τ
i
j
(
t
)
]
α
[
η
i
j
(
t
)
]
β
∑
s
∈
a
l
l
o
w
e
d
k
[
τ
i
s
(
t
)
]
α
[
η
i
s
(
t
)
]
β
,
j
∈
a
l
l
o
w
e
d
k
0
,
j
∉
a
l
l
o
w
e
d
k
p_{ij}^{k}(t) =\left\{\begin{matrix} \frac{[\tau _{ij}(t)]^{\alpha }[\eta_{ij}(t)]^{\beta } }{\sum_{s\in allowed_{k} }[\tau _{is}(t)]^{\alpha }[\eta_{is}(t)]^{\beta } },j\in allowed_{k} \\ 0,j\notin allowed_{k} \end{matrix}\right.
pijk(t)={∑s∈allowedk[τis(t)]α[ηis(t)]β[τij(t)]α[ηij(t)]β,j∈allowedk0,j∈/allowedk
其中
η
i
j
(
t
)
\eta_{ij}(t)
ηij(t)为启发函数,一般取
η
i
j
(
t
)
=
1
d
i
j
\eta_{ij}(t)=\frac{1}{d_{ij}}
ηij(t)=dij1,表示蚂蚁从城市
i
i
i转移到城市
j
j
j的期望程度,
a
l
l
o
w
e
d
k
allowed_{k}
allowedk为蚂蚁允许访问的城市的集合,且
a
l
l
o
w
e
d
k
=
C
−
t
a
b
u
k
allowed_{k}=C-tabu_{k}
allowedk=C−tabuk,因为从每个城市仅允许访问一次。
α
\alpha
α表示信息素重要程度因子,其值越大,表示信息素的浓度在转移中起的作用越大;
β
\beta
β为启发函数重要重要程度因子,其值越大,表示启发函数在转移中起的作用越大;
Step2:信息素更新。在所有蚂蚁完成一次遍历后,对路径上的残留信息作更新处理,各路径上的信息素按下式进行更新:
{
τ
i
j
(
t
+
1
)
=
(
1
−
ρ
)
τ
i
j
(
t
)
+
△
τ
i
j
(
t
)
,
0
<
ρ
<
1
△
τ
i
j
(
t
)
=
∑
k
=
1
m
△
τ
i
j
k
(
t
)
\left\{\begin{matrix} \tau_{ij}(t+1)=(1-\rho ) \tau_{ij}(t)+\bigtriangleup\tau_{ij}(t) ,0<\rho <1\\ \bigtriangleup\tau_{ij}(t)=\sum_{k=1}^{m}\bigtriangleup\tau_{ij}^{k}(t) \end{matrix}\right.
{τij(t+1)=(1−ρ)τij(t)+△τij(t),0<ρ<1△τij(t)=∑k=1m△τijk(t)
其中,
ρ
\rho
ρ为挥发系数,反应信息素的持久性,
△
τ
i
j
k
(
t
)
\bigtriangleup\tau_{ij}^{k}(t)
△τijk(t)表示第
k
k
k只蚂蚁
t
t
t时刻城市
i
i
i与城市
j
j
j连接路径上新释放的信息素浓度,
△
τ
i
j
\bigtriangleup\tau_{ij}
△τij表示所有蚂蚁在城市
i
i
i与城市
j
j
j连接路径上新释放的信息素浓度之和。简单理解就是原来在路径上残留的信息素浓度加上所有蚂蚁在该路径上新释放的信息素浓度,即得到当前路径上的信息素浓度。
针对蚂蚁释放信息素
问题,Dorigo M等人曾给出3种不同的模型。
1°ant cycle system模型
△
τ
i
j
k
(
t
)
=
{
Q
/
L
k
,
第
k
只蚂蚁从城市
i
到城市
j
0
,
否则
\bigtriangleup\tau_{ij}^{k}(t)=\left\{\begin{matrix} Q/L_k,第k只蚂蚁从城市i到城市j\\ 0,否则 \end{matrix}\right.
△τijk(t)={Q/Lk,第k只蚂蚁从城市i到城市j0,否则
其中为
Q
Q
Q常数,表示蚂蚁循环一次所释放的信息素总量,
L
k
L_k
Lk为第只蚂蚁经过一次完整路径其路径的总长度。该模型利用蚂蚁经过路径的整体信息计算释放的信息素浓度。
2°ant quantity system模型
△
τ
i
j
k
(
t
)
=
{
Q
/
d
i
j
,
第
k
只蚂蚁从城市
i
到城市
j
0
,
否则
\bigtriangleup\tau_{ij}^{k}(t)=\left\{\begin{matrix} Q/d_{ij},第k只蚂蚁从城市i到城市j\\ 0,否则 \end{matrix}\right.
△τijk(t)={Q/dij,第k只蚂蚁从城市i到城市j0,否则
该模型利用蚂蚁经过路径的局部信息计算释放的信息素浓度。
3°ant density system模型
△
τ
i
j
k
(
t
)
=
{
Q
,
第
k
只蚂蚁从城市
i
到城市
j
0
,
否则
\bigtriangleup\tau_{ij}^{k}(t)=\left\{\begin{matrix} Q,第k只蚂蚁从城市i到城市j\\ 0,否则 \end{matrix}\right.
△τijk(t)={Q,第k只蚂蚁从城市i到城市j0,否则
该模型释放的信息素浓度取为恒定值。
实际应用中,一般选用ant cycle system模型,该模型下,蚂蚁经过的路径越短,释放的信息素浓度越高。
蚁群算法求解TSP问题的流程图如下所示:
蚁群算法求解31个城市的TSP问题
31个城市的位置信息
城市编号 | X坐标 | Y坐标 | 城市编号 | X坐标 | Y坐标 |
---|---|---|---|---|---|
1 | 1304 | 2312 | 17 | 3918 | 2179 |
2 | 3639 | 1315 | 18 | 4061 | 2370 |
3 | 4177 | 2244 | 19 | 3780 | 2212 |
4 | 3712 | 1399 | 20 | 3676 | 2578 |
5 | 3488 | 1535 | 21 | 4029 | 2838 |
6 | 3326 | 1556 | 22 | 4263 | 2931 |
7 | 3238 | 1229 | 23 | 3429 | 1908 |
8 | 4196 | 1004 | 24 | 3507 | 2367 |
9 | 4312 | 790 | 25 | 3394 | 2643 |
10 | 4386 | 570 | 26 | 3439 | 3201 |
11 | 3007 | 1970 | 27 | 2935 | 3240 |
12 | 2562 | 1756 | 28 | 3140 | 3550 |
13 | 2788 | 1491 | 29 | 2545 | 2357 |
14 | 2381 | 1676 | 30 | 2778 | 2826 |
15 | 1332 | 695 | 31 | 2370 | 2975 |
16 | 3715 | 1678 |
求解过程
- 初始化参数:包括蚁群规模(蚂蚁数量) m m m,信息素重要程度因子 α \alpha α,启发函数重要程度因子 β \beta β,信息素挥发因子,信息素释放总量 Q Q Q,最大迭代次数。
- 构建解空间(蚁群访问完成路径):为每个蚂蚁随机选择一个出发点,然后按照状态转移概率准则让蚂蚁选择下一个城市(Step1),直到蚂蚁走完所有城市;对蚁群中的蚂蚁所有执行该操作。
- 更新信息素浓度:当整个蚁群中的成员走完一条完成路径(构建了一个可行解),按照信息素更新的更新公式更新路径上的信息素浓度(Step2),并找出当前的最短路径。
- 算法终止条件:判断是否达到最大迭代次数,如果是则输出最优解,否则重复前面步骤
代码实现
头文件定义
#ifndef ANT_COLONY_TSP_H
#define ANT_COLONY_TSP_H
#include <vector>
#include <deque>
#include <set>
#include <string>
#include <random>
#include <ctime>
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
using uint = unsigned int;
const double EPS = 1e-20;
// 城市位置坐标
struct CityPos{
double x;
double y;
};
// 定义解的结构
struct Solution{
vector<uint> route;
double distance;
};
class Ant_Colony_Tsp
{
private:
// 蚁群,解空间
deque<Solution> ant_colony;
// 蚂蚁数量
uint ant_cnt;
// 城市数量
uint citys_cnt;
// 信息素重要程度因子
double alpha = 1.0;
// 启发函数重要程度因子
double beta = 5.0;
// 信息素挥发因子
double rho = 0.1;
// 常系数,表示信息素总量
double Q = 1.0;
// 所有城市的信息
set<uint> all_city_num_set;
vector<CityPos> city_pos_info;
// 城市两两之间的距离,启发函数默认取两个城市之间距离的倒数
vector<vector<double>> citys_distace_info;
// 信息素矩阵,记录两个城市之间的信息素浓度
vector<vector<double>> pheromone_info;
// 最大迭代次数
uint iter_max;
Solution best_solution;
public:
Ant_Colony_Tsp();
Ant_Colony_Tsp(uint m_ant_cnt, uint m_iter_mat, string filepath);
// 读取文件,初始化城市距离信息
void readCityData(const string file_path);
void calCitysDistance();
// 计算两个城市间的距离
double calDistance(const CityPos pos1, const CityPos pos2){
return sqrt(pow(pos1.x - pos2.x, 2) + pow(pos1.y - pos2.y,2));
}
// 蚁群算法
void aca();
// 计算路径总长度
double getRouteLength(const vector<uint> route);
// 输出最优解
void outPutBestSolution();
};
#endif // ANT_COLONY_TSP_H
函数实现
#include "ant_colony_tsp.h"
Ant_Colony_Tsp::Ant_Colony_Tsp()
{
}
Ant_Colony_Tsp::Ant_Colony_Tsp(uint m_ant_cnt, uint m_iter_mat, string filepath)
{
ant_cnt = m_ant_cnt;
iter_max = m_iter_mat;
// 读取城市的位置信息
readCityData(filepath);
calCitysDistance();
// 蚁群初始化空间
ant_colony.resize(ant_cnt);
// 初始化城市间的信息素浓度,并记录所有城市编号
pheromone_info = vector<vector<double>>(citys_cnt, vector<double>(citys_cnt, 1.0));
for(uint i = 1; i <= citys_cnt; i++)
{
all_city_num_set.insert(i);
}
}
void Ant_Colony_Tsp::readCityData(const string file_path)
{
// 将文件读入输入流
ifstream infile(file_path,ios_base::in); //以输入的方式打开文件
if(infile.peek() == EOF && infile.eof()) //测试是否成功打开
{
cout<<"open error!"<<endl;
exit(1);
}
double x,y;
while(!infile.eof())
{
infile >> x >> y;
city_pos_info.push_back(CityPos{x,y});
if(infile.fail())
{
break;
}
}
infile.close();
citys_cnt = city_pos_info.size();
}
void Ant_Colony_Tsp::calCitysDistance()
{
for(uint i = 0; i < citys_cnt; i++)
{
vector<double> distance_arr;
for(uint j = 0; j < citys_cnt; j++)
{
if(i == j)
{
distance_arr.push_back(0);
}else if(i < j)
{
distance_arr.push_back(calDistance(city_pos_info.at(i), city_pos_info.at(j)));
}else {
distance_arr.push_back(citys_distace_info.at(j).at(i));
}
}
citys_distace_info.push_back(distance_arr);
}
}
void Ant_Colony_Tsp::aca()
{
// 使用系统时间作为种子
std::time_t now = std::time(nullptr);
std::tm* cur_tm = std::localtime(&now);
int seed = cur_tm->tm_hour * 3600 + cur_tm->tm_min * 60 + cur_tm->tm_sec;
// 默认引擎
std::default_random_engine dre(seed);
// 默认生成0到1之间的数
std::uniform_real_distribution<double> ure;
// 随机生成一个城市起点
std::uniform_int_distribution<uint> uid_city_start(1, citys_cnt);
uint iter_start = 0;
while (iter_start++ < iter_max) {
// 为蚂蚁生成一个随机起点
// 所有蚂蚁依据信息素从一个城市移动到下一个位置,并走完一圈
for(uint i = 0; i < ant_cnt; i++)
{
vector<uint> ant_route;
set<uint> tabu; // 禁忌表
dre.discard(i);
uint start_city = uid_city_start(dre);
tabu.insert(start_city);
ant_route.push_back(start_city);
for(uint j = 1; j < citys_cnt; j++)
{
// 找出蚂蚁下一步允许到访的城市的集合
set<uint> allowed_set;
set_difference(all_city_num_set.begin(), all_city_num_set.end(),
tabu.begin(), tabu.end(), inserter(allowed_set, allowed_set.begin()));
vector<uint> allowed_vec(allowed_set.begin(), allowed_set.end());
uint allow_city_num = allowed_vec.size();
// 计算城市的转移概率,这里仅仅计算到下一个城市
vector<double> pick_pro_arr(allow_city_num, 0);
uint pre_city_idx = ant_route.back();
for(uint k = 0; k < allow_city_num; k++)
{
// back方法返回最末元素,但是它不检查是否存在最末元素
uint next_city_idx = allowed_vec.at(k);
pick_pro_arr.at(k) = pow(pheromone_info.at(pre_city_idx -1 ).at(next_city_idx - 1), alpha) *
pow(1.0/citys_distace_info.at(pre_city_idx -1 ).at(next_city_idx - 1), beta);
}
// double sum_pheromone = accumulate(pick_pro_arr.cbegin(), pick_pro_arr.cend(), 0);
double sum_pheromone = 0;
for(uint k = 0; k < allow_city_num; k++)
{
sum_pheromone += pick_pro_arr.at(k);
}
// 这里pick_pro_arr里面元素的值都非常小,因此还是需要做归一化处理
std::transform(pick_pro_arr.begin(), pick_pro_arr.end(), pick_pro_arr.begin(),
std::bind2nd(std::divides<double>(), sum_pheromone));
std::uniform_int_distribution<uint> uid_city(0, allow_city_num - 1);
double full_pro = 1.0;
// 采用轮盘赌的方法选择下一个城市的去向
while(full_pro > EPS)
{
uint idx = uid_city(dre);
full_pro -= pick_pro_arr.at(idx);
if(full_pro <= EPS)
{
uint next_city_num = allowed_vec.at(idx);
ant_route.push_back(next_city_num);
tabu.insert(next_city_num);
break;
}
}
// cout << "once" << endl;
}
// 计算路径长度
double route_length = getRouteLength(ant_route);
// 当前蚂蚁遍历完一次完整路径
ant_colony.at(i) = Solution{ant_route, route_length};
}
// 寻找当前迭代轮次的最优解
auto it = min_element(ant_colony.cbegin(), ant_colony.cend(),
[](const Solution& s1, const Solution& s2){
return s1.distance < s2.distance;
});
Solution cur_solution = *it;
// 更新当前的最优解
if(iter_start == 1 || best_solution.distance >= cur_solution.distance)
{
best_solution = cur_solution;
}
vector<vector<double>> delta_pheromone(citys_cnt, vector<double>(citys_cnt, 0));
// 更新城市i到城市j的信息浓度
for(uint ii = 0; ii < ant_cnt; ii++)
{
vector<uint> cur_route = ant_colony.at(ii).route;
double rl = ant_colony.at(ii).distance;
for(uint jj = 0; jj < citys_cnt - 1; jj++)
{
uint pre_city_idx = cur_route.at(jj);
uint next_city_idx = cur_route.at(jj + 1);
delta_pheromone.at(pre_city_idx - 1).at(next_city_idx - 1) += Q / rl;
}
uint end_city_idx = cur_route.back();
uint start_city_idx = cur_route.front();
delta_pheromone.at(end_city_idx - 1).at(start_city_idx - 1) += Q / rl;
}
// 更新所有城市之间的信息素浓度
for(uint i1 = 0; i1 < citys_cnt; i1++)
{
for(uint i2 = 0; i2 < citys_cnt; i2++)
{
if(i1 == i2)
{
continue;
}
double origin_con = pheromone_info.at(i1).at(i2);
// 新的信息素浓度 = 挥发剩余浓度 + 新增信息素浓度
pheromone_info.at(i1).at(i2) = (1 - rho) * origin_con + delta_pheromone.at(i1).at(i2);
}
}
}
outPutBestSolution();
}
double Ant_Colony_Tsp::getRouteLength(const vector<uint> route)
{
double res = 0;
for(uint i = 0; i < citys_cnt - 1; i++)
{
uint pre_city_idx = route.at(i);
uint next_city_idx = route.at(i + 1);
res += citys_distace_info.at(pre_city_idx - 1).at(next_city_idx - 1);
}
uint start_idx = route.at(0);
uint end_idx = route.back();
return res += citys_distace_info.at(end_idx - 1).at(start_idx - 1);
}
void Ant_Colony_Tsp::outPutBestSolution()
{
cout << "best route:\n ";
vector<uint> best_route = best_solution.route;
for(uint i = 0; i < citys_cnt; i++){
cout << best_route.at(i) << "->";
if(i > 0 && i % 10 == 0)
{
cout << endl;
}
}
cout << best_route.at(0) << endl;
cout << "min distance:" << best_solution.distance << endl;
}
主函数调用
#include "ant_colony_tsp.h"
int main()
{
Ant_Colony_Tsp act(50, 300 ,"C:\\xxx\\city_pos_data31.txt");
act.aca();
return 0;
}
代码运行结果
多次运行结果(蚁群算法为随机搜索算法,因此搜索结果不一定为最优解):
best route:
24->20->19->17->3->18->21->22->26->28->27->
30->31->1->15->14->12->13->7->6->2->
4->8->9->10->5->16->23->11->29->25->
24
min distance:16025.1
best route:
1->15->14->12->13->11->23->16->5->6->7->
8->9->10->2->4->19->17->18->3->22->
21->20->24->25->26->28->27->30->29->31->
1
min distance:15889.9
best route:
29->30->25->20->24->19->17->18->3->22->21->
26->28->27->31->1->15->14->12->13->7->
6->5->16->4->8->9->10->2->23->11->
29
min distance:15922.4
best route:
20->21->22->18->3->19->17->16->4->8->9->
10->2->7->6->5->23->11->12->14->13->
15->1->31->29->30->27->28->26->25->24->
20
min distance:15928.1
best route:
17->19->3->18->22->21->20->24->25->26->28->
27->30->31->29->1->15->14->12->13->11->
6->5->7->2->4->8->9->10->16->23->
17
min distance:15769.3
best route:
19->17->18->3->22->21->26->28->27->31->1->
15->14->12->13->7->6->5->4->2->8->
9->10->16->23->11->29->30->25->20->24->
19
min distance:15740
best route:
14->12->13->11->23->16->5->6->7->2->4->
8->9->10->3->18->17->19->24->25->20->
21->22->26->28->27->30->31->29->1->15->
14
min distance:15601.9
matlab仿真结果:
![]() | ![]() |
---|
用此前14个城市的tsp问题测试,蚁群算法很快就能得出最优解:
best route:
5->6->12->7->13->8->11->9->10->1->2->
14->3->4->5
min distance:29.3405
笔者后续也测试了遗传算法处理31个城市的Tsp问题,结果发现,在31个城市的Tsp问题上,发现基本蚁群算法得到的寻优结果要好于基本遗传算法得到的寻优的结果,但从算法构造上来看,不难发现蚁群算法的时间复杂度明显较高。