目录
遗传算法解决MTSP问题
个体的基因型:
基本算法:
代码及其解释
案例:
遗传算法代码:
代码结果
关于其他MTSP问题
同一起点同一终点:
随机起点随机终点
问题类型:解决所有旅行商从同一地点出发,但不回到出发点,且各旅行商的终点不同。
一个个体的基因型包含两段:
1、路径基因型
2、中断点基因型
这些意味着什么呢?假设有3个旅行商,10个城市,城市代号1为起始点,那么假设遗传算法中产生了这么一个个体,他的基因型为:
路径基因型:[2 3 5 6 7 9 10 8]
中断点基因型:[2 4]
那么,这个个体代表的信息为:
旅行商1的旅游路径:1 2 3
旅行商2的旅游路径:1 5 6
旅行商3的旅游路径:1 7 9 10 8
1、设置5000个迭代次数,每一次迭代产生一个最佳个体,若这厮的路径距离小于历史的全局最小值,就作为全局最小值。
2、从本次迭代中的个体,随机分成n组,从每一组中的最佳个体里修改基因片段(有的改路径基因型,有的改中断点基因型),从而得到子代。
3、子代再一次产生最小路径值,若再次小于历史的最小值,则设置他为全局最小值。再次以2的方法产生子代
4、直到5000次迭代结束为止。
35个城市,5个旅行商。找到最小路径。
n = 35; %设置城市个数
xy = 10*rand(n,2); %随机产生城市的坐标,实际应用中可以自己输入坐标。主要用以画图,真正起作用的是距离矩阵啦。
salesmen = 5; %设置旅行商的人数
min_tour = 3; %设置每个旅行商至少走过三个城市(除去起始点和终止点的话就是一个城市)
pop_size = 80; %设置种群的个数,必须是8的倍数,因为代码中以 8 做为步骤 2 的分组个数
num_iter = 5e3; %设置迭代总次数, i.e. 5000次
a = meshgrid(1:n); %用以计算距离矩阵。
dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),n,n); %计算距离矩阵(欧式距离),可以自己输入。
[opt_rte,opt_brk,min_dist] = mtspof_ga(xy,dmat,salesmen,min_tour, ...
pop_size,num_iter,1,1); %运行代码
根据以上的基本步骤,写出代码如下(包括:检查输入是否合理、画图与统计。)
function varargout = mtspofs_ga(xy,dmat,salesmen,min_tour,pop_size,num_iter,show_prog,show_res)
% MTSPOFS_GA Fixed Start Open Multiple Traveling Salesmen Problem (M-TSP) Genetic Algorithm (GA)
% Finds a (near) optimal solution to a variation of the "open" M-TSP by
% setting up a GA to search for the shortest route (least distance needed
% for each salesman to travel from the start location to unique individual
% cities without returning to the starting location)
%
% Summary:
% 1. Each salesman starts at the first point, but travels to a unique
% set of cities after that (and none of them close their loops by
% returning to their starting points)
% 2. Except for the first, each city is visited by exactly one salesman
%
% Note: The Fixed Start is taken to be the first XY point
%
% Input:
% xy (float):城市坐标,可以 2 维或者 3 维。
% dmat (float) 距离矩阵 N X N 维
% salesman (scalar integer) 旅行商个数。
% min_tour (scalar integer) 不包含起始点,每个旅行商最少应该 travel 的数量。
% pop_size (scalar integer) 遗传算法中个体的数量。
% num_iter (scalar integer) 算法一共迭代的次数
% show_prog (scalar logical) 如果等于1,就将迭代过程画出来
% show_res (scalar logical) 如果等于1,就将算法最后的结果展示出来
%
% Output:
% opt_rte (integer array) 输出最优个体的路径基因型
% opt_brk (integer array) 输出最优个体的中断点基因型
% min_dist (scalar float) 最有个体代表的所有旅行商的距离之和。
%
%
% Author: Joseph Kirk
% Email: [email protected]
% Release: 1.3
% Release Date: 6/2/09
% commenter: zhuo
%检测输入参数,如果某些参数没有输入,那么改段代码就自动帮你加了。
nargs = 8;
for k = nargin:nargs-1
switch k
case 0
xy = 10*rand(40,2); % 设置城市坐标的默认值为 40 个,随机产生 2 维坐标
case 1
N = size(xy,1); %设置城市数目 N = size(xy,1)
a = meshgrid(1:N);
dmat = reshape(sqrt(sum((xy(a,:)-xy(a',:)).^2,2)),N,N); %产生相应的距离矩阵。
case 2
salesmen = 5; %旅行商个数的默认值为 5 个。
case 3
min_tour = 2; %默认每个旅行商至少走 2 个城市
case 4
pop_size = 80;
case 5
num_iter = 5e3;
case 6
show_prog = 1;
case 7
show_res = 1;
otherwise
end
end
%验证输入是否可行,验证原理为城市个数 N 是否和 距离矩阵的 size相等
[N,dims] = size(xy);
[nr,nc] = size(dmat);
if N ~= nr || N ~= nc
error('Invalid XY or DMAT inputs!')
end
n = N - 1; % 除去了起始点
% Sanity Checks 还是验证输入:可以不看
salesmen = max(1,min(n,round(real(salesmen(1)))));
%验证输入的旅行商个数是不是大于1,并且是整数,否则帮你四舍五入改了
min_tour = max(1,min(floor(n/salesmen),round(real(min_tour(1)))));
%验证输入的minTour是不是大于1,并且是整数,否则帮你四舍五入改了
pop_size = max(8,8*ceil(pop_size(1)/8));
%验证输入的个体数是否为8的整数(因为后面的分组8个为一组),否则帮你用ceil函数改了
num_iter = max(1,round(real(num_iter(1))));
%验证输入的迭代次数是否大于1,否则帮改了
show_prog = logical(show_prog(1));
%验证是否为1或0,不然帮你改了,下同。
show_res = logical(show_res(1));
num_brks = salesmen-1; % 设置中断点的个数,下面可以不看。
dof = n - min_tour*salesmen;
addto = ones(1,dof+1);
for k = 2:num_brks
addto = cumsum(addto);
end
cum_prob = cumsum(addto)/sum(addto);
% Initialize the Populations
pop_rte = zeros(pop_size,n); % 所有个体的路径基因型
pop_brk = zeros(pop_size,num_brks); % 所有个体的中断点基因型
for k = 1:pop_size
pop_rte(k,:) = randperm(n)+1; %初始化所有个体的路径基因型。
%这里为什么加 1 呢?因为我们 n=N-1=34 ,因此产生的路径基因型必须在[2,35]中产生。
pop_brk(k,:) = randbreaks(); %产生中断点,函数代码在下方(子函数)
end
% 选择画图时的颜色(不同旅行商的路径画在图上要用不同颜色呀)
clr = [1 0 0; 0 0 1; 0.67 0 1; 0 1 0; 1 0.5 0];
if salesmen > 5
clr = hsv(salesmen);
end
% Run the GA
global_min = Inf;
total_dist = zeros(1,pop_size); %每个个体的总距离构成的 1X80 维的向量
dist_history = zeros(1,num_iter); %每一次迭代个数中最优个体的总距离构成的 1X5000 维的向量。
tmp_pop_rte = zeros(8,n); %暂时变量,用完就丢。用于产生新个体的,(路径的基因型)
tmp_pop_brk = zeros(8,num_brks); %同上,用于产生新的中断点的基因型
new_pop_rte = zeros(pop_size,n); %新生代的路径基因型,为一个 80X34 的矩阵,每一行代表每一个个体。
new_pop_brk = zeros(pop_size,num_brks); %同上,用于中断点基因型 80X4 的矩阵。
if show_prog
pfig = figure('Name','MTSPOFS_GA | Current Best Solution','Numbertitle','off');
end
for iter = 1:num_iter %计算一次迭代过程中,每一个个体的总距离
for p = 1:pop_size
d = 0;
p_rte = pop_rte(p,:);
p_brk = pop_brk(p,:);
rng = [[1 p_brk+1];[p_brk n]]';
for s = 1:salesmen
d = d + dmat(1,p_rte(rng(s,1))); % 加上起点到第二个城市的距离
%计算路径中城市的总距离:
for k = rng(s,1):rng(s,2)-1
d = d + dmat(p_rte(k),p_rte(k+1));
end
end
total_dist(p) = d; %将每一个个体的距离存入到矩阵中。
end
% Find the Best Route in the Population
[min_dist,index] = min(total_dist); %找到最优个体(index)
dist_history(iter) = min_dist; %将其总距离存入到历史矩阵中。
%下一步是比较当前最优与全局最优,如果比他还小,那么就替换全局最优。
%并更新当前的路径图。
if min_dist < global_min
global_min = min_dist;
opt_rte = pop_rte(index,:);
opt_brk = pop_brk(index,:);
rng = [[1 opt_brk+1];[opt_brk n]]';
%更新路径图。
if show_prog
% Plot the Best Route
figure(pfig);
for s = 1:salesmen
rte = [1 opt_rte(rng(s,1):rng(s,2))];
if dims == 3, plot3(xy(rte,1),xy(rte,2),xy(rte,3),'.-','Color',clr(s,:));
else plot(xy(rte,1),xy(rte,2),'.-','Color',clr(s,:)); end
title(sprintf('Total Distance = %1.4f, Iteration = %d',min_dist,iter));
hold on
end
if dims == 3, plot3(xy(1,1),xy(1,2),xy(1,3),'ko');
else plot(xy(1,1),xy(1,2),'ko'); end
hold off
end
end
% 产生子代算法
rand_grouping = randperm(pop_size); %用于将子代打乱顺序。将原来的1-80的子代顺序打乱成随机。
for p = 8:8:pop_size
%分组,每组8个个体,配合上面的代码,避免变成分层抽样。
rtes = pop_rte(rand_grouping(p-7:p),:);
brks = pop_brk(rand_grouping(p-7:p),:);
dists = total_dist(rand_grouping(p-7:p));
%挑选出每组中最优的家伙(山大王的感觉)
[ignore,idx] = min(dists);
best_of_8_rte = rtes(idx,:);
best_of_8_brk = brks(idx,:);
%随机产生两个位置,用于打乱这个山大王的基因型,从而生成新的子代。
rte_ins_pts = sort(ceil(n*rand(1,2)));
I = rte_ins_pts(1);
J = rte_ins_pts(2);
%用山大王先生产生子代。(有修改路径基因型的,有修改中断点基因型的,有不变的)怎么产生的大家应该可以看到懂。
for k = 1:8 %
tmp_pop_rte(k,:) = best_of_8_rte;
tmp_pop_brk(k,:) = best_of_8_brk;
switch k
case 2 % Flip
tmp_pop_rte(k,I:J) = fliplr(tmp_pop_rte(k,I:J));
case 3 % Swap
tmp_pop_rte(k,[I J]) = tmp_pop_rte(k,[J I]);
case 4 % Slide
tmp_pop_rte(k,I:J) = tmp_pop_rte(k,[I+1:J I]);
case 5 % Modify Breaks
tmp_pop_brk(k,:) = randbreaks();
case 6 % Flip, Modify Breaks
tmp_pop_rte(k,I:J) = fliplr(tmp_pop_rte(k,I:J));
tmp_pop_brk(k,:) = randbreaks();
case 7 % Swap, Modify Breaks
tmp_pop_rte(k,[I J]) = tmp_pop_rte(k,[J I]);
tmp_pop_brk(k,:) = randbreaks();
case 8 % Slide, Modify Breaks
tmp_pop_rte(k,I:J) = tmp_pop_rte(k,[I+1:J I]);
tmp_pop_brk(k,:) = randbreaks();
otherwise % Do Nothing
end
end
%新生代
new_pop_rte(p-7:p,:) = tmp_pop_rte;
new_pop_brk(p-7:p,:) = tmp_pop_brk;
end
pop_rte = new_pop_rte;
pop_brk = new_pop_brk;
end
%下面的代码用于显示 results
if show_res
figure('Name','MTSPOFS_GA | Results','Numbertitle','off');
subplot(2,2,1);
if dims == 3, plot3(xy(:,1),xy(:,2),xy(:,3),'k.');
else plot(xy(:,1),xy(:,2),'k.'); end
title('City Locations');
subplot(2,2,2);
imagesc(dmat([1 opt_rte],[1 opt_rte]));
title('Distance Matrix');
subplot(2,2,3);
rng = [[1 opt_brk+1];[opt_brk n]]';
for s = 1:salesmen
rte = [1 opt_rte(rng(s,1):rng(s,2))];
if dims == 3, plot3(xy(rte,1),xy(rte,2),xy(rte,3),'.-','Color',clr(s,:));
else plot(xy(rte,1),xy(rte,2),'.-','Color',clr(s,:)); end
title(sprintf('Total Distance = %1.4f',min_dist));
hold on;
end
if dims == 3, plot3(xy(1,1),xy(1,2),xy(1,3),'ko');
else plot(xy(1,1),xy(1,2),'ko'); end
subplot(2,2,4);
plot(dist_history,'b','LineWidth',2);
title('Best Solution History');
set(gca,'XLim',[0 num_iter+1],'YLim',[0 1.1*max([1 dist_history])]);
end
% 返回结果
if nargout
varargout{1} = opt_rte; %参数1 最佳个体的路径基因型
varargout{2} = opt_brk; %参数2 最佳个体的中断点基因型
varargout{3} = min_dist; %参数3 最佳个体的总距离
end
%产生终端点代码(随机生成)
%为什么要单独写呢?因为前面有要求,每一个旅行商至少走个2个城市(不包含起点)。
function breaks = randbreaks()
if min_tour == 1 %如果没有限制,那么:
tmp_brks = randperm(n-1);
breaks = sort(tmp_brks(1:num_brks));
else % 如果有限制,则强制中断的点,使得城市个数满足要求
num_adjust = find(rand < cum_prob,1)-1;
spaces = ceil(num_brks*rand(1,num_adjust));
adjust = zeros(1,num_brks);
for kk = 1:num_brks
adjust(kk) = sum(spaces == kk);
end
breaks = min_tour*(1:num_brks) + cumsum(adjust);
end
end
end
在实现遗传算法的时候,这个代码并没有用到两个个体的交叉互换,而是用了一个个体通过变异产生新个体。下面是变异函数的几点解释:
希望能够对读者的理解略尽绵薄之力
统计结果:
https://blog.csdn.net/weixin_42141390/article/details/103783581#%E5%9F%BA%E6%9C%AC%E7%AE%97%E6%B3%95%EF%BC%9A
https://blog.csdn.net/weixin_42141390/article/details/103640085