仿生算法求解TSP最短路徑問題(Matlab實現)

說到最短路徑的求解,我們想到的往往是Dijkstra算法、Floyd算法、SPFA算法,這些算法都非常的經典,這些算法往往保證了路徑最短,但是走過的路徑可能構不成一個環,也就是說上述算法在修路,修橋這些方面能夠很好地被應用,因爲用到的材料會保證最少,但如果你是計劃着出去旅遊,準備着將N個旅遊景點都走完,並保證每個景點只去一次,最後正好還回到你家的話(例如TSP問題),今天我們要學習的這種仿生算法就能很好地解決你的問題。

仿生算法其實是模擬的人類基因的,染色體交叉,變異,它體現了達爾文“物競天擇,適者生存”的思想,通過代數的增加,子代會越來越適應環境,不適應的在迭代的過程中會被逐漸淘汰掉,這樣就保證了結果比較優(如果迭代次數設置地大一點,結果基本可以認爲最優了)

 

https://www.bilibili.com/video/av22056690/?redirectFrom=h5

這個鏈接是B站上的一個教學視頻,讀者可以打開看看,講得很詳細,這是第四章的內容

 

關鍵詞:種羣大小、父代、子代、收斂、交叉、交叉概率、變異、變異概率、代溝

 

以下是代碼實現(代碼一塊一塊地粘在變異環境中就能運行,你只需要修改參數即可):

主函數:

clear              %運用了仿生學中的生物遺傳變異,如果感覺結果不是很優,你可以增大迭代次數,這裏設了200次
clc
close all
X = [16.47,96.10
    16.47,94.44
    20.09,92.54
    22.39,93.37
    25.23,97.24
    22.00,96.05
    20.47,97.02
    17.20,96.29
    16.30,97.38
    14.05,98.12
    16.53,97.38
    21.52,95.59
    19.41,97.13
    20.09,92.55];   %各個城市的座標位置
NIND = 100;         %種羣大小
MAXGEN = 200;       %最大迭代次數
Pc = 0.9;           %交叉概率,相當於基因遺傳的時候染色體交叉
Pm = 0.05;          %染色體變異
GGAP = 0.9;         %這個是代溝,通過遺傳方式得到的子代數爲父代數*GGAP
D = Distance(X);    %通過這個函數可以計算i,j兩點之間的距離
N = size(D,1);      %計算有多少個座標點
%%初始化種羣
Chrom = InitPop(NIND,N);    %Chrome代表的種羣
%%在二維圖上畫出所有的座標點
%figure
%plot(X(;,1),X(;,2),'o');
%%畫出隨機解得路線圖
DrawPath(Chrom(1,:),X)
pause(0.0001)
%輸出隨機解的路線和總距離
disp('初始種羣中的一個隨機值')
OutputPath(Chrom(1,:));%其中一個個體
Rlength = PathLength(D,Chrom(1,:));
disp(['總距離;',num2str(Rlength)]);
disp('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
%優化
gen = 0;
figure;
hold on;
box on;
xlim([0,MAXGEN])
title('優化過程')
xlabel('代數')
ylabel('最優值')
ObjV = PathLength(D,Chrom);     %計算當前路線長度,即上面隨機產生的那些個體路徑
preObjV = min(ObjV);%找出當前個體重最小的那個
while gen<MAXGEN
    %%計算適應度
    ObjV = PathLength(D,Chrom);     %計算路線長度
    %fprintf('%d   %1.10f\n',gen,min(ObjV))
    line([gen - 1,gen],[preObjV,min(ObjV)]);pause(0.0001);
    preObjV = min(ObjV);
    FitnV = Fitness(ObjV);
    %%選擇
    SelCh = Select(Chrom,FitnV,GGAP);
    %%交叉操作
    SelCH = Recombin(SelCh,Pc);
    %%變異
    SelCh = Mutate(SelCh,Pm);
    %%逆轉操作
    SelCh = Reverse(SelCh,D);
    %%重插入子代的新種羣
    Chrom = Reins(Chrom,SelCh,ObjV);
    %%更新迭代次數
    gen = gen + 1;
end
%%畫出最優解的路線圖
ObjV = PathLength(D,Chrom);     %計算路線長度
[minObjV,minInd] = min(ObjV);
DrawPath(Chrom(minInd(1),:),X)
%%輸出最優解的路線和距離
disp('最優解:')
p = OutputPath(Chrom(minInd(1),:));
disp(['總距離:',num2str(ObjV(minInd(1)))]);
disp('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')

城市間距離計算函數

function D = Distance(a)
%%計算兩兩城市之間的距離
%輸入 a 各城市的位置座標
%輸出 D 兩兩城市之間的距離

row = size(a,1);
D = zeros(row,row);
for i = 1:row
    for j = i+1:row
        D(i,j) = ((a(i,1) - a(j,1))^2 + (a(i,2)-a(j,2))^2)^0.5;
        D(j,i) = D(i,j);
    end
end

初始化種羣

function Chrom = InitPop(NIND,N)%初始化種羣,
%輸入:
%NIND:種羣大小
%N:個體染色體長度(城市個數)
%輸出:
%初始種羣
Chrom = zeros(NIND,N);  %用於存儲種羣
for i = 1:NIND
    Chrom(i,:) = randperm(N);%隨機生成初始種羣,randperm函數的用法是返回一行1~N的整數,這N個數是不同的
end

畫出遊歷路徑

function DrawPath(Chrom,X)
%%畫路線圖函數
%輸入:
%Chrom  待畫路線
%X      個城市的座標位置

R =  [Chrom(1,:) Chrom(1,1)]; %一個隨機解(個體),一共有14個城市,但是這裏R有15個值,因爲後面又補了一個Chrom(1,1),“是爲了讓路徑最後再回到起點”,這是R初始值[6,3,11,7,14,8,5,1,2,4,13,9,10,12,6],可以發現前14個值因爲randperm函數都是不一樣的
figure;
hold on
plot(X(:,1),X(:,2),'o','color',[0.5,0.5,0.5])%X(:,1),X(:,2)分別代表的X軸座標和Y軸座標,[0.5,0.5,0.5]表示對應的座標用圓圈表示
%plot(X(:,1),X(:,2),'o','color',[1,1,1])%X(:,1),X(:,2)分別代表的X軸座標和Y軸座標,
plot(X(Chrom(1,1),1),X(Chrom(1,1),2),'rv','MarkerSize',20)%標記起點
for i = 1:size(X:1)
    text(X(i,1)+0.05,X(i,2)+0.05,num2str(i),'color',[1,0,0]);
end                                 %感覺這個for循環沒什麼用,我去掉了,對結果也沒什麼影響,無非即使第一張圖上標了個1,可能是我太菜了吧
A = X(R,:);                         %A是將之前的座標順序用R打亂後重新存入A中
row = size(A,1);                    %row爲座標數+1
for i = 2:row
    [arrowx,arrowy] = dsxy2figxy(gca,A(i-1:i,1),A(i-1:i,2));    %dsxy2figxy座標轉換函數,記錄兩個點
    annotation('textarrow',arrowx,arrowy,'HeadWidth',8,'color',[0,0,1]);%將這兩個點連接起來
end
hold off
xlabel('橫座標')
ylabel('縱座標')
title('軌跡圖')
box on

在命令行窗口輸出路徑

function p=OutputPath(R)
%%輸出路線函數
%輸入   R   路線

R = [R,R(1)];
N = length(R);
p = num2str(R(1));
for i = 2:N
    p = [p,'->',num2str(R(i))];
end
disp(p)

計算種羣中每個個體(每個個體代表一種路徑)路徑總長度

function len = PathLength(D,Chrom)
%%計算所有個體的路線長度
%輸入
%D  兩兩城市之間的距離
%Chrom  個體的軌跡

[row,col] = size(D);
NIND = size(Chrom,1);
len = zeros(NIND,1);
for i = 1:NIND
    p = [Chrom(i,:) Chrom(i,1)];
    i1 = p(1:end-1);
    i2 = p(2:end);
    len(i,1) = sum(D((i1-1)*col+i2));
end

選擇較優個體

function SelCh = Select(Chrom,FitnV,GGAP)%%選擇操作
%輸入:
%Chrom 種羣
%FitnV 適應度值
%GGAP 選擇概率
%輸出:
%SelCh 被選擇的個體
NIND = size(Chrom,1);
NSel = max(floor(NIND * GGAP+.5),2);
ChrIx = Sus(FitnV,NSel);
SelCh = Chrom(ChrIx,:);

個體染色體交叉

function SelCh = Recombin(SelCh,Pc)
%交叉操作
%輸入:
%SelCh 被選擇的個體
%Pc    交叉概率
%輸出:
%SelCh 交叉後的個體

NSel = size(SelCh,1);
for i = 1:2:NSel - mod(NSel,2)
    if Pc>=rand %交叉概率PC
        [SelCh(i,:),SelCh(i+1,:)] = intercross(SelCh(i,:),SelCh(i+1,:));
    end
end

變異操作

function SelCh = Mutate(SelCh,Pm)
%變異操作
%輸入:
%SelCh  被選擇的個體
%Pm  變異概率
%輸出:
%SelCh  變異後的個體

[NSel,L] = size(SelCh);
for i = 1:NSel
    if Pm >= rand
        R = randperm(L);
        SelCh(i,R(1:2)) = SelCh(i,R(2:-1:1));
    end
end

逆轉操作

function SelCh = Reverse(SelCh,D)
%%進化逆轉函數
%輸入:
%SelCh  被選擇的個體
%D  各城市的距離矩陣
%輸出:
%SelCh  進化逆轉後的個體

[row,col] = size(SelCh);
ObjV = PathLength(D,SelCh);
SelCh1 = SelCh;
for i = 1:row
    r1 = randsrc(1,1,[1:col]);
    r2 = randsrc(1,1,[1:col]);
    mininverse = min([r1 r2]);
    maxinverse = max([r1 r2]);
    SelCh1(i,mininverse:maxinverse) = SelCh1(i,maxinverse:-1:mininverse);
end
ObjV1 = PathLength(D,SelCh1);%計算路線長度
index = ObjV1<ObjV;
SelCh(index,:)=SelCh1(index,:);

重插操作(在父代中選擇當前最優的幾個個體插入子代中,選幾個要看你代溝設的多少)

function Chrom = Reins(Chrom,SelCh,ObjV)
%%重插入子代的種羣
%輸入:
%Chrom      父代的種羣
%SelCh      子代的種羣
%ObjV       父代適應度
%輸出:
%Chrom      組合父代與子代後得到的新種羣
NIND = size(Chrom,1);
NSel = size(SelCh,1);
[TobjV,index] = sort(ObjV);
Chrom =  [Chrom(index(1:NIND-NSel),:);SelCh];

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章