遗传算法之TSP问题

1、遗传算法

遗传算法的基本内容在之前的博客已经讲述过了,详情请点击:遗传算法

遗传算法的上一篇博客解决的是函数优化问题,即求解最大值或最小值问题;而此次要解决的是组合优化问题中的TSP问题,即旅行商问题。

旅行商问题:给定一系列城市位置,求解访问每一座城市一次并回到起始城市的最短回路,又称TSP问题。

TSP问题就是要找到图中的最短哈密尔顿回路,即全局最短路径。

那么,这两种到底有何区别呢?

在遗传算法的上一篇博客中,交叉变异操作是通过0与1之间相互转换即可简单实现。但在TSP问题中,若只采取简单的交换操作,则城市序列内容易包含重复的城市。

因此,TSP问题中的交叉操作:

  • 先确定两个城市序列中进行交换的城市。
  • 交换城市时,观察城市序列中是否会包含重复城市;若重复了,则增加一个交换操作。例如:已知城市序列1中的城市A与城市序列2中的城市B进行交叉操作,考虑城市A与城市B直接交换时,若交换过后的城市序列1中含有两个城市B,则将城市序列1中原来就已经含有的城市B换成城市A,城市序列2同理操作。

这样就满足了城市序列中不包含重复城市的要求。
若还不清楚,请看下面这个例子,当城市序列A、B如下:

未进行交叉操作前:
A: 7 6 5 4 3 2
B: 4 3 5 2 6 7

已知进行交换的城市为城市序列中的后三个城市,即城市序列A中的 4、3、2 城市与城市序列B中的 2、6、7 城市进行交换。

  • 城市序列A中:城市4替换成城市2,不包含重复城市;城市3替换成城市6,由于城市序列中已存在该城市,因此将城市序列中的城市6替换成城市3;城市2替换成城市7,由于城市序列中已存在该城市,因此将城市序列中的城市7替换成城市2。
  • 城市序列B中:城市2替换成城市4,由于城市序列中已存在该城市,因此将城市序列中的城市4替换成城市2;城市6替换成城市3,由于城市序列中已存在该城市,因此将城市序列中的城市3替换成城市6,城市7替换成城市2,不包含重复城市。

最终,交叉操作完成后的城市序列分别为:

交叉操作完成后:
A: 2 3 5 4 6 7
B: 2 6 5 4 3 2

TSP问题中的变异操作:直接将同一城市序列中的两个城市进行交换。

2、遗传算法实现TSP问题

2.1 代码

<1> city.m:随机生成N个城市的坐标并保存

N=25;		%%城市的个数
citys=randn(N,2);		%%随机生成城市位置
save citys.mat		%%将城市位置保存到citys.mat中

<2> plot_route.m:实现连点画图

%连点画图函数 plot_route.m
function plot_route(a,R)
	scatter(a(:,1),a(:,2),'rx');
	hold on;
	plot([a(R(1),1),a(R(length(R)),1)],[a(R(1),2),a(R(length(R)),2)]);
	hold on;
	
	for i=2:length(R)
	    x0=a(R(i-1),1);
	    y0=a(R(i-1),2);
	    x1=a(R(i),1);
	    y1=a(R(i),2);
	    xx=[x0,x1];
	    yy=[y0,y1];
	    plot(xx,yy);
	    hold on;
	end
	
end

<3> mylength.m:计算染色体的路程代价

%染色体的路程代价函数  mylength.m
function len=myLength(D,p)%p是一个排列
	[N,NN]=size(D);
	len=D(p(1,N),p(1,1));
	for i=1:(N-1)
	len=len+D(p(1,i),p(1,i+1));
	end
end

<4> fit.m:适应度函数

%适应度函数fit.m,每次迭代都要计算每个染色体在本种群内部的优先级别,类似归一化参数。越大约好!
function fitness=fit(len,m,maxlen,minlen)
fitness=len;
for i=1:length(len)
    fitness(i,1)=(1-(len(i,1)-minlen)/(maxlen-minlen+0.0001)).^m;
end

<5> cross.m:进行交叉操作

%交叉操作函数  cross.m
function [A,B]=cross(A,B)
L=length(A);
if L<10
    W=L;
elseif ((L/10)-floor(L/10))>=rand&&L>10
    W=ceil(L/10)+8;
else
    W=floor(L/10)+8;
end

%%W为需要交叉的位数
p=unidrnd(L-W+1);%随机产生一个交叉位置
%fprintf('p=%d ',p);%交叉位置
for i=1:W
    x=find(A==B(1,p+i-1));
    y=find(B==A(1,p+i-1));
    [A(1,p+i-1),B(1,p+i-1)]=exchange(A(1,p+i-1),B(1,p+i-1));
    [A(1,x),B(1,y)]=exchange(A(1,x),B(1,y));
end
end

<6> exchange.m:进行对调,即交换

%对调函数 exchange.m
function [x,y]=exchange(x,y)
temp=x;
x=y;
y=temp;
end

<7> Mutation.m:进行变异操作

%变异函数 Mutation.m
function a=Mutation(A)
index1=0;index2=0;
nnper=randperm(size(A,2));
index1=nnper(1);
index2=nnper(2);
%fprintf('index1=%d ',index1);
%fprintf('index2=%d ',index2);
temp=0;
temp=A(index1);
A(index1)=A(index2);
A(index2)=temp;
a=A;
end

<8> main.m:主函数

clear;clc;		%%清除控制台数据

%%输入参数
N=25;               %%城市的个数
M=100;               %%种群的个数
ITER=2000;               %%迭代次数
m=2;                %%适应值归一化淘汰加速指数
Pc=0.8;             %%交叉概率
Pmutation=0.05;       %%变异概率

%%读入城市的坐标
load citys.mat		%%从citys.mat中读取数据,数据矩阵名为citys 
pos=citys;

%%生成城市之间距离矩阵
D=zeros(N,N);
for i=1:N
    for j=i+1:N
        dis=(pos(i,1)-pos(j,1)).^2+(pos(i,2)-pos(j,2)).^2;	%%欧氏距离
        D(i,j)=dis^(0.5);
        D(j,i)=D(i,j);
    end
end

%%生成初始群体
popm=zeros(M,N);
for i=1:M
    popm(i,:)=randperm(N);	%%随机排列,比如[2 4 5 6 1 3]
end

%%随机选择一个种群
R=popm(1,:);
subplot(2,2,1);
scatter(pos(:,1),pos(:,2),'rx');	%%画出所有城市坐标
axis([-3 3 -3 3]);
subplot(2,2,2);
plot_route(pos,R);      %%画出初始种群对应各城市之间的连线
axis([-3 3 -3 3]);

%%初始化种群及其适应函数
fitness=zeros(M,1);
len=zeros(M,1);
for i=1:M%计算每个染色体对应的总长度
    len(i,1)=myLength(D,popm(i,:));
end

maxlen=max(len);%最大回路
minlen=min(len);%最小回路
fitness=fit(len,m,maxlen,minlen);
rr=find(len==minlen);%找到最小值的下标,赋值为rr
R=popm(rr(1,1),:);%提取该染色体,赋值为R
for i=1:N
    fprintf('%d ',R(i));%把R顺序打印出来
end
fprintf('\n');

fitness=fitness/sum(fitness);	%%归一
distance_min=zeros(ITER+1,1);  %%各次迭代的最小的种群的路径总长
nn=M;
iter=0;
while iter<=ITER
    fprintf('迭代第%d次\n',iter);
    %%选择操作--轮赌
    p=fitness./sum(fitness);
    q=cumsum(p);%累加
    for i=1:(M-1)
        len_1(i,1)=myLength(D,popm(i,:));
        r=rand;
        tmp=find(r<=q);
        popm_sel(i,:)=popm(tmp(1),:);
    end 
    [fmax,indmax]=max(fitness);%求当代最佳个体
    popm_sel(M,:)=popm(indmax,:);
    
    %%交叉操作
    nnper=randperm(M);
    for i=1:M*Pc*0.5
        A=popm_sel(nnper(i),:);
        B=popm_sel(nnper(i+1),:);
        [A,B]=cross(A,B);
        popm_sel(nnper(i),:)=A;
        popm_sel(nnper(i+1),:)=B;
    end
    
    %%变异操作
    for i=1:M
        pick=rand;
        while pick==0
             pick=rand;
        end
        if pick<=Pmutation
           popm_sel(i,:)=Mutation(popm_sel(i,:));
        end
    end
    
    %%求适应度函数
    NN=size(popm_sel,1);
    len=zeros(NN,1);
    for i=1:NN
        len(i,1)=myLength(D,popm_sel(i,:));
    end
    
    maxlen=max(len);
    minlen=min(len);
    distance_min(iter+1,1)=minlen;
    fitness=fit(len,m,maxlen,minlen);
    rr=find(len==minlen);
    fprintf('minlen=%d\n',minlen);
    R=popm_sel(rr(1,1),:);
    for i=1:N
        fprintf('%d ',R(i));
    end
    fprintf('\n');
    popm=[];
    popm=popm_sel;
    iter=iter+1;
end

subplot(2,2,3);
plot_route(pos,R);	%%输出最终路径
axis([-3 3 -3 3]);
subplot(2,2,4);
plot(distance_min);	%%输出各次迭代的最小的种群的路径总长

2.2 运行结果

<1>

在这里插入图片描述
左上图为城市的位置分布,共有25个城市;右上图为初始种群的路线;左下图为最终路线;右下图为2000次迭代的最小的种群的路径总长的变化,当迭代次数为429次时,已找到最短路径,长度为19.97。

<2>

在这里插入图片描述
可从此处得知:最短路径的城市序列,城市17为起点城市,城市23为终点城市。

<3>

在这里插入图片描述
总运行时间为11.146s。

2.3 参数改变

<1> 交叉概率为0.8,变异概率为0.05,城市位置不变,种群个数改变时:

种群个数 路径总长收敛时迭代次数 最短距离 总运行时间
100 429 19.97 11.146s
150 275 19.49 11.313
200 247 19.61 12.975
250 1390 20.68 15.537
300 970 18.19 14.286

其他参数不变的前提下,随着种群个数的增多,路径总长达到收敛时的迭代次数与最短距离的变化趋势为先减少后增加;总运行时间的变化趋势为持续增加。说明适当的种群数目有利于加快收敛,找到最短路径;但种群数目不能过大,否则,将起到相反作用。从图中可看出,当种群个数处于区间 [100,200] 时,能达到较好的效果。

<2> 种群个数为100,变异概率为0.05,城市位置不变,交叉概率改变时:

交叉概率 路径总长收敛时迭代次数 最短距离 总运行时间
0 527 21.38 11.685
0.2 496 20.98 11.885
0.4 656 16.63 13.432
0.6 252 21.17 18.845
0.8 429 19.97 11.146
1 453 20.17 18.532

其他参数不变的前提下,随着交叉概率的增多,路径总长达到收敛时的迭代次数、最短距离以及总运行时间的变动频繁。从图中可看出,当交叉概率为0.2或0.8附近的值时,能达到较好的效果。

<3> 种群个数为100,交叉概率为0.8,城市位置不变,变异概率改变时:

变异概率 路径总长收敛时迭代次数 最短距离 总运行时间
0.01 425 16.54 14.568
0.03 453 17.69 13.113
0.05 429 19.97 11.146
0.07 687 19.77 18.651
0.09 236 23.63 13.388

其他参数不变的前提下,随着变异概率的增多,路径总长达到收敛时的迭代次数变动频繁,变化趋势基本满足先增加后减少;最短距离的变化趋势为持续增加;总运行时间的变化趋势为先减少后增加再减少。从图中可看出,当变异概率处于区间 [0.01,0.05] 时,能达到较好的效果。

<4> 种群个数为100,交叉概率为0.8,变异概率为0.05,城市序列重排时:

重排次数 路径总长收敛时迭代次数 最短距离 总运行时间
1 429 19.97 11.146
2 501 18.02 17.021
3 267 17.45 18.53
4 356 17.39 14.777
5 1130 16.73 13.427

代码修改如下:

%%获取城市的坐标
load citys.mat %%从citys.mat中读取数据,数据矩阵名为citys 
pos=flipud(citys); %%flipud():矩阵上下翻转函数

<5> 种群个数为100,交叉概率为0.8,变异概率为0.05,城市个数改变时:

城市个数 路径总长收敛时迭代次数 最短距离 总运行时间
25 429 19.97 11.146
35 837 26.76 26.466
50 1772 33.71 15.721
75 1956 47.5 19.115
100 1859 72.33 26.466

其他参数不变的前提下,随着城市个数的增多,路径总长达到收敛时的迭代次数逼近2000次;最短距离不断大幅增多;总共运行时间也居高不下。因此,当城市个数 >35个时,遗传算法并不能很好的解决TSP问题,应采用其他算法求解(例如,蚁群算法)。
注意:当改变城市个数N时,city.m(随机生成城市坐标并保存的函数)内的城市个数应一同改变。除此之外,若运行时仍有报错,可试着先清空所有变量,再重新运行。

发布了13 篇原创文章 · 获赞 6 · 访问量 2290

猜你喜欢

转载自blog.csdn.net/weixin_44060222/article/details/103142853
今日推荐