一、引言
哈喽大家好,今天要给大家介绍的是“蚁群算法”。跟粒子群算法一样,蚁群算法也是基于对生物行为的研究所受到启发而产生的。它的诞生比粒子群算法还要早3年,在1992年的某一天,一位叫Marco Dorigo的在他的博士论文中提出了蚁群算法,并称其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。。。好了历史背景介绍到止,接下来就认真讲一下何谓蚁群算法吧。
二、蚂蚁寻食
2.1 科普知识
很久以前,生物学家就对蚂蚁捕食行为很好奇。因为,可能大家不知道,蚂蚁的视力特别差,你可以把它当成瞎子,可是每次却都能找到食物,并且从它所找到的路径,还是从洞穴到食物的最短路径。大家可以想一想,每次你的桌子上有掉一颗糖,过不了多久,就立马有蚂蚁出现了,而且还是一成群的蚂蚁沿着同一条线路过来的。这是为什么呢?难道是蚂蚁的鼻子很灵敏吗?
后来生物学家们经过长时间的研究发现,并不是蚂蚁的鼻子很灵敏,而是他们之间有一种很特别的信息传递方式——信息素,正是这种特别的信息传递方式,使得蚂蚁这种生物,不会迷路哈哈。
好言归正传,这个信息素是怎么用的呢?其实是这样的,蚂蚁在行走的过程会释放一种化学物质,这种化学物质就叫“信息素”,这是用来标识自己的行走路径。而在寻找食物的过程中,蚂蚁根据这个信息素的浓度来选择行走的方向,最终就到达了食物的所在地。
2.2 讲个故事
有一天,有2个蚂蚁(分别叫小黄和大黄吧)出来找食物了。从洞穴出来,有两条路可以走,选择哪条路的概率是一致的,因为两条路目前都没有信息素。我们假设结果是大黄走了直线路,小黄走了曲线路。如下图

两个蚂蚁在行走过程中分泌信息素,用于标识路线。接着,过了一段时间,大黄找到了奶酪,此时小黄还在行走中。如下图
接着大黄要回去的时候,由于直线路含有信息素而曲线路没有信息素(因为当前小黄还没走到奶酪附近),于是大黄选择走有信息素的道路也就是直线路。又过了一段时间,大黄回到了路的起点,而小黄刚走到奶酪附近。可以看出现在两条路的信息素浓度是2: 1。如图
接着小黄要回去的时候,由于直线路含有的信息素浓度比曲线路高,于是小黄选择走有信息素浓度高的道路也就是直线路。最终当小黄也回到路的起点的时候,两条路的信息素浓度比例是3: 1。
以后,每当有蚂蚁遇到起点岔路的时候,都会优先选择信息素浓度高的道路。这样,一大批的蚂蚁就能够准确的找到最短路径了。
2.3 启发
我们从这个蚂蚁找食物的过程中,发现其实,这个跟我们之前提到的旅行商问题很像。大家还记得旅行商问题是什么么?不记得没关系,我再讲一下就好了。有这么一个旅行商人,他要拜访n个城市,但是每次走的路径是有限制的,即每个城市只能拜访一次,并且最后要回到原来出发的城市。如何选择路径使得总路程为最小值。蚂蚁找食物和旅行商问题都是为了求得最短路径。那接下来,我们就根据利用蚁群算法来解决我们的旅行商问题。诶博主等等你还没讲蚁群算法。别急,我两者一起讲,蚁群算法这东西需要实例比较好解释。
三、蚁群算法的基本原理
本节以TSP问题为例介绍蚁群算法的原理。
1. 每只蚂蚁从一个城市走到另一个城市的过程中都会在路径上释放信息素,并且蚂蚁选择下一个城市的依据是一个概率公式,如下:
$ P_{ij}^{k}(t) = \left\{\begin{matrix} \frac{\tau_{ij}^{\alpha}(t)\cdot \eta_{ij}^{\beta}(t)}{\sum_{s \notin tabu_{k}}\tau_{is}^{\alpha}(t)\cdot \eta_{is}^{\beta}(t)} & j \notin tabu_{k}\\ 0 & other \end{matrix}\right.$
上式中,各个符号的意义如下:
- $\alpha$ -- 信息素启发式因子,它反映了信息素对蚂蚁路径选择的作用;
- $\beta$ -- 期望启发式因子,它反映了信息素在蚂蚁选择路径时被重视的程度;
- $d_{ij}$ -- 城市i和j之间的距离;
- $\eta_{ij}(t)$ -- 启发函数,表达式为$\eta_{ij}(t) = \frac{1} {d_{ij}}$;
- $tabu_{k}$ -- 禁忌表,记录蚂蚁k当前所走过的城市;
- $\tau_{ij}$ -- 城市i到城市j的路径上的信息素的量;
2. 蚂蚁留下的信息素,因为是化学物质,因此随着时间的过去信息素也应该要以一定的速率挥发。根据不同的规则我们可以将蚁群算法分为三种模型——蚁周模型(Ant-Cycle)、蚁量模型(Ant-Quantity)和蚁密模型(Ant-Density)。通常使用的是蚁周模型,故本文只介绍蚁周模型,其他两种模型大家自行查阅下哈。规则是:完成一次路径循环后,蚂蚁才释放信息素。有了这么一个规则后,我们可以来想想,经过一次路径循环后,路径上的信息素为多少?
根据上面所提供的信息,我们可以知道,当所有的蚂蚁完成一次路径循环后,才更新信息素。因此路径上的信息素应该分为两部分:之前未挥发所残留的信息素 + 经过当前循环所有蚂蚁在经过该路径后所留下的信息素。用公式表述如下:
$\tau_{ij}(t + n) = (1 - \rho ) \cdot \tau_{ij}(t) + \Delta \tau_{ij}(t, t + n )$
$\Delta \tau_{ij}(t, t + n) = \sum_{k = 1}^{m}\Delta \tau_{ij}^{k}(t, t + n)$
其中,
- $\rho$ -- 信息素挥发因子,$\rho \in [0, 1)$;
- $\Delta \tau_{ij}(t, t + n)$ -- 经过一次循环后城市i到城市j的路径上的信息素的增量;
- $(t, t+n)$ -- 走过n步以后蚂蚁即完成一次循环;
- $\Delta \tau_{ij}^{k}(t, t + n)$ -- 表示经过一次循环后蚂蚁k在它走过的路上的信息素增量。
好了,现在我们的未挥发所残留的信息素很好解,定义一个信息素挥发因子$\rho$便能解决。但是,经过一次循环所有蚂蚁留下的信息素该怎么定义?在蚁周模型中,它被定义如下:
$\Delta \tau_{ij}^{k}(t, t + n) = \left\{\begin{matrix} \frac{Q}{L_{k}} & Ant \ k \ walk \ though \ path \ (i,j)\\ 0 & other \end{matrix}\right.$
它表明,蚂蚁留下的信息素跟它走过的完整路径的总长度有关。越长则留下的信息素越少,这个应该很好理解。为了找到更短的路径,就应该让短的路径信息素浓度高些。其中这个$Q$是控制比例常数,一般定为1。
3. 有了数学公式后,我们可以写出蚁群算法的基本步骤:
(1) 参数初始化:
通常可以按照以下策略来进行参数组合设定:
- 确定蚂蚁数目:蚂蚁数目与城市规模之比约为1.5;
- 参数粗调:即调整取值范围较大的$\alpha$,$\beta$及$Q$;
- 参数微调:即调整取值范围较小的$\rho$;
% 参数初始化
NC_max = 200; % 最大迭代次数,取100~500之间
m = 50; % 蚂蚁的个数,一般设为城市数量的1.5倍
alpha = 1; % α 选择[1, 4]比较合适
beta = 5; % β 选择[3 4 5]比较合适
rho = 0.1; % ρ 选择[0.1, 0.2, 0.5]比较合适
Q = 1;
NC = 1; % 迭代次数,一开始为1
Eta = 1 ./ D; % η = 1 / D(i, j) ,这里是矩阵
Tau = ones(n, n); % Tau(i, j)表示边(i, j)的信息素量,一开始都为1
Table = zeros(m, n); % 路径记录表
rBest = zeros(NC_max, n); % 记录各代的最佳路线
lBest = inf .* ones(NC_max, 1); % 记录各代的最佳路线的总长度
lAverage = zeros(NC_max, 1); % 记录各代路线的平均长度
(2) 随机将蚂蚁放于不同出发点,对每只蚂蚁计算其下个访问城市,直到所有蚂蚁访问完所有城市。
% 第1步:随机产生各个蚂蚁的起点城市
start = zeros(m, 1);
for i = 1: m
temp = randperm(n);
start(i) = temp(1);
end
Table(:, 1) = start; % Tabu表的第一列即是所有蚂蚁的起点城市
citys_index = 1: n; % 所有城市索引的一个集合
% 第2步:逐个蚂蚁路径选择
for i = 1: m
% 逐个城市路径选择
for j = 2: n
tabu = Table(i, 1: (j - 1)); % 蚂蚁i已经访问的城市集合(称禁忌表)
allow_index = ~ismember(citys_index, tabu);
Allow = citys_index(allow_index); % Allow表:存放待访问的城市
P = Allow;
% 计算从城市j到剩下未访问的城市的转移概率
for k = 1: size(Allow, 2) % 待访问的城市数量
P(k) = Tau(tabu(end), Allow(k))^alpha * Eta(tabu(end), Allow(k))^beta;
end
P = P / sum(P); % 归一化
% 轮盘赌法选择下一个访问城市(为了增加随机性)
Pc = cumsum(P);
target_index = find(Pc >= rand);
target = Allow(target_index(1));
Table(i, j) = target;
end
end
(3) 计算各蚂蚁经过的路径长度$L_{k}$,记录当前迭代次数最优解(即最短路径和最短距离),同时对路径上的信息素浓度进行更新
% 第3步:计算各个蚂蚁的路径距离
length = zeros(m, 1);
for i = 1: m
Route = Table(i, :);
for j = 1: (n - 1)
length(i) = length(i) + D(Route(j), Route(j + 1));
end
length(i) = length(i) + D(Route(n), Route(1));
end
% 第4步:计算最短路径距离及平均距离
if NC == 1
[min_Length, min_index] = min(length);
lBest(NC) = min_Length;
lAverage(NC) = mean(length);
rBest(NC, :) = Table(min_index, :);
else
[min_Length, min_index] = min(length);
lBest(NC) = min(lBest(NC - 1), min_Length);
lAverage(NC) = mean(length);
if lBest(NC) == min_Length
rBest(NC, :) = Table(min_index, :);
else
rBest(NC, :) = rBest((NC - 1), :);
end
end
% 第5步:更新信息素
Delta_tau = zeros(n, n);
for i = 1: m
for j = 1: (n - 1)
Delta_tau(Table(i, j), Table(i, j + 1)) = Delta_tau(Table(i, j), Table(i, j + 1)) + Q / length(i);
end
Delta_tau(Table(i, n), Table(i, 1)) = Delta_tau(Table(i, n), Table(i, 1)) + Q / length(i);
end
Tau = (1 - rho) .* Tau + Delta_tau;
% 第6步:迭代次数加1,并且清空路径记录表
NC = NC + 1;
Table = zeros(m, n);
(4) 判断是否达到最大迭代次数,若否,返回步骤2;是,结束程序。
(5) 输出结果,并根据需要输出寻优过程中的相关指标,如运行时间、收敛迭代次数等。
四、完整Matlab代码
以下是利用蚁群算法计算TSP问题的代码:
%% I. 清空环境
clc
clear all
%% II. 符号说明
% C -- n个城市的坐标
% NC_max -- 最大迭代次数
% m -- 蚁群中蚂蚁的数量,一般设置为城市的1.5倍
% D(i, j) -- 两城市i和之间的距离
% Eta(i, j) = 1 ./ D(i, j) -- 启发函数
% alpha -- 表征信息素重要程度的参数
% beta -- 表征启发函数重要程度的参数
% rho -- 信息素挥发因子
% Q --
% rBest -- 各代最佳的路线
% lBest -- 各代最佳路线的长度
% lAverage --各代的平均长度
%% III. 导入城市位置数据
citys = [16.4700 96.1000
16.4700 94.4400
20.0900 92.5400
22.3900 93.3700
25.2300 97.2400
22.0000 96.0500
20.4700 97.0200
17.2000 96.2900
16.3000 97.3800
14.0500 98.1200
16.5300 97.3800
21.5200 95.5900
19.4100 97.1300
20.0900 92.5500];
%% IV. 计算距离矩阵
D = Distance(citys); % 计算距离矩阵
n = size(D, 1); % 城市的个数
%% V. 初始化参数
NC_max = 200; % 最大迭代次数,取100~500之间
m = 22; % 蚂蚁的个数,一般设为城市数量的1.5倍
alpha = 1; % α 选择[1, 4]比较合适
beta = 5; % β 选择[3 4 5]比较合适
rho = 0.1; % ρ 选择[0.1, 0.2, 0.5]比较合适
Q = 1;
NC = 1; % 迭代次数,一开始为1
Eta = 1 ./ D; % η = 1 / D(i, j) ,这里是矩阵
Tau = ones(n, n); % Tau(i, j)表示边(i, j)的信息素量,一开始都为1
Table = zeros(m, n); % 路径记录表
rBest = zeros(NC_max, n); % 记录各代的最佳路线
lBest = inf .* ones(NC_max, 1); % 记录各代的最佳路线的总长度
lAverage = zeros(NC_max, 1); % 记录各代路线的平均长度
%% VI. 迭代寻找最佳路径
while NC <= NC_max
% 第1步:随机产生各个蚂蚁的起点城市
start = zeros(m, 1);
for i = 1: m
temp = randperm(n);
start(i) = temp(1);
end
Table(:, 1) = start; % Tabu表的第一列即是所有蚂蚁的起点城市
citys_index = 1: n; % 所有城市索引的一个集合
% 第2步:逐个蚂蚁路径选择
for i = 1: m
% 逐个城市路径选择
for j = 2: n
tabu = Table(i, 1: (j - 1)); % 蚂蚁i已经访问的城市集合(称禁忌表)
allow_index = ~ismember(citys_index, tabu);
Allow = citys_index(allow_index); % Allow表:存放待访问的城市
P = Allow;
% 计算从城市j到剩下未访问的城市的转移概率
for k = 1: size(Allow, 2) % 待访问的城市数量
P(k) = Tau(tabu(end), Allow(k))^alpha * Eta(tabu(end), Allow(k))^beta;
end
P = P / sum(P); % 归一化
% 轮盘赌法选择下一个访问城市(为了增加随机性)
Pc = cumsum(P);
target_index = find(Pc >= rand);
target = Allow(target_index(1));
Table(i, j) = target;
end
end
% 第3步:计算各个蚂蚁的路径距离
length = zeros(m, 1);
for i = 1: m
Route = Table(i, :);
for j = 1: (n - 1)
length(i) = length(i) + D(Route(j), Route(j + 1));
end
length(i) = length(i) + D(Route(n), Route(1));
end
% 第4步:计算最短路径距离及平均距离
if NC == 1
[min_Length, min_index] = min(length);
lBest(NC) = min_Length;
lAverage(NC) = mean(length);
rBest(NC, :) = Table(min_index, :);
else
[min_Length, min_index] = min(length);
lBest(NC) = min(lBest(NC - 1), min_Length);
lAverage(NC) = mean(length);
if lBest(NC) == min_Length
rBest(NC, :) = Table(min_index, :);
else
rBest(NC, :) = rBest((NC - 1), :);
end
end
% 第5步:更新信息素
Delta_tau = zeros(n, n);
for i = 1: m
for j = 1: (n - 1)
Delta_tau(Table(i, j), Table(i, j + 1)) = Delta_tau(Table(i, j), Table(i, j + 1)) + Q / length(i);
end
Delta_tau(Table(i, n), Table(i, 1)) = Delta_tau(Table(i, n), Table(i, 1)) + Q / length(i);
end
Tau = (1 - rho) .* Tau + Delta_tau;
% 第6步:迭代次数加1,并且清空路径记录表
NC = NC + 1;
Table = zeros(m, n);
end
%% VII. 结果显示
[shortest_Length, shortest_index] = min(lBest);
shortest_Route = rBest(shortest_index, :);
disp([\'最短距离:\' num2str(shortest_Length)]);
disp([\'最短路径:\' num2str([shortest_Route shortest_Route(1)])]);
%% VIII. 绘图
figure(1)
plot([citys(shortest_Route,1); citys(shortest_Route(1),1)],...
[citys(shortest_Route,2); citys(shortest_Route(1),2)],\'o-\');
grid on
for i = 1: size(citys, 1)
text(citys(i, 1), citys(i, 2), [\' \' num2str(i)]);
end
text(citys(shortest_Route(1), 1), citys(shortest_Route(1), 2), \' 起点\');
text(citys(shortest_Route(end), 1), citys(shortest_Route(end), 2), \' 终点\');
xlabel(\'城市位置横坐标\')
ylabel(\'城市位置纵坐标\')
title([\'蚁群算法优化路径(最短距离: \' num2str(shortest_Length) \')\'])
figure(2)
plot(1: NC_max, lBest, \'b\', 1: NC_max, lAverage, \'r:\')
legend(\'最短距离\', \'平均距离\')
xlabel(\'迭代次数\')
ylabel(\'距离\')
title(\'各代最短距离与平均距离对比\')
其中,求两城市之间的路径距离被封装到Distance.m中,如下:
function D = Distance(citys)
%% 计算两两城市之间的距离
% 输入:各城市的位置坐标(citys)
% 输出:两两城市之间的距离(D)
n = size(citys, 1);
D = zeros(n, n);
for i = 1: n
for j = i + 1: n
D(i, j) = sqrt((citys(i, 1) - citys(j, 1))^2 + (citys(i, 2) - citys(j, 2))^2);
D(j, i) = D(i, j);
end
D(i, i) = 1e-4; %对角线的值为0,但由于后面的启发因子要取倒数,因此用一个很小数代替0
end
运行上述代码,获得的结果如下:
最短距离:29.6889
最短路径:8 1 11 9 10 2 14 3 4 5 6 12 7 13 8
获得的优化路径图如下: