前一段时间后台有小伙伴问我能不能写一个人工鱼群(AF)求带时间窗车辆路径问题(VRPTW)的代码,由于时间有限,小编只写了一份用AF求容量受限的车辆路径问题(CVRP)的代码,仅供参考。


大体的思路是先对人工鱼进行编码,然后采用人工鱼群算法求解TSP问题中的觅食、聚群、追尾和随机行为对人工鱼群进行更新。


但是亟需需要解决的问题是:对于CVRP问题,如何对人工鱼进行编码。如果顾客数目为L,提供的车辆数目为K,那么编码的长度为K*L,编码的方式为对[1,K*L]之间所有整数的随机排序(用matlab语言表示为randperm(K*L)),编码表现形式为[S1,S2,...,SK*L],Sj表示第j个位置上的数字。

举个例子,如果顾客数目为3,提供的车辆数目为2,那么一条可能的人工鱼为[3 6 4 1 2 5]。


编码结束后,需要对人工鱼进行解码,即这条人工到底代表什么含义,或者说我们根据这条人工鱼能否解码出每个顾客由哪辆车服务,以及每辆车访问顾客的顺序是什么

下面开始对人工鱼进行解码处理:

已知:人工鱼的表现形式[S1,S2,...,SK*L],Sj表示第j个位置上的数字。

下面两个计算公式计算出第j个位置上的数字Sj所对应的顾客编号i和该顾客由车辆m进行服务。



表示向下取整,对应matalb的floor函数

有了上述两个公式可以计算出人工鱼中每个位置上数字所对应的顾客编号i和服务车辆m,但这还不够,如何根据根据这条人工鱼能否解码出每个顾客由哪辆车服务,以及每辆车访问顾客的顺序是什么?我们可以根据下面的流程图来进行解码

下面举个例子来演示解码过程,有3个顾客,需求量分别为[1 2 1],有2辆车,载重量都为2。如果人工鱼编码为[1 2 3 4 5 6],解码步骤如下:

顾客1的flag(1)=0,即顾客1没有被服务,demand(1)+load(1)=1<=2=cap,因此将顾客1添加到路径1中,即R(1)=[1],并标记顾客1已经被服务flag(1)=1,然后更新车辆1的当前装载量load(1)=load(1)+demand(1)=1。

此时j=j+1=2<=K*L=6

顾客2的flag(2)=0,即顾客2没有被服务,demand(2)+load(1)=3>2=cap,因此不能将顾客2添加到路径1中,此时h=m+1=2,然后判断车辆2能否服务顾客2,即判断demand(2)+load(2)=2<=2=cap,因此将顾客2添加到路径2中,即R(2)=[2],并标记顾客2已经被服务flag(2)=1,然后更新车辆2的当前装载量load(2)=load(2)+demand(2)=2。

此时j=j+1=3<=K*L=6

顾客3的flag(3)=0,即顾客3没有被服务,demand(3)+load(1)=2<=2=cap,因此将顾客3添加到路径1中,即R(1)=[1,3],并标记顾客3已经被服务flag(3)=1,然后更新车辆1的当前装载量load(1)=load(1)+demand(3)=2。

此时flag中所有元素全为1,即所有顾客都被服务,则终止循环并输出车辆调度方案:

车辆1:1 3

车辆2:2


解码的代码如下

%% 对染色体进行解码
%输入:
% L             顾客数目
% K             车辆数目
% chrom         解的编码方式
% demand        每个顾客的需求配送量
% cap           每辆车的载重量
%输出:
% vc            每辆车所经过的顾客,删除掉没有任务的车辆
% reasonable    标记当前解是否合理,即所有顾客是否都被服务。如果合理则reasonable=1,否则reasonable=0
% 染色体编码形式为[S(1),S(2),...,S(K*L)]
%解码思路:
%1.遍历染色体chrom上的每个基因位,根据公式求出第j个基因位S(j)所对应的顾客编号i和配送车辆编号m为:
% i=S(j)-L*fLoor((S(j)-1)/L)   m=1+fLoor((S(j)-1)/L)
%2.判断第m辆车的容量还能够是否服务顾客i,如果能够服务,则标记flag(i)=1,该车的剩余容量也发生变化;
% 如果不能够服务,则从遍历后面的车辆,看是否能服务该顾客
%3.直到遍历完第K*L个基因,检验flag是否都为1,如果都为1则说明是一个合理的解,反之,则说明该解不合理
function [vc,reasonable]=Decode(L,K,chrom,demand,cap)
vc=cell(K,1);                           %每辆车所经过的顾客
load=zeros(K,1);                        %初始每辆车的装载量为0
flag=zeros(L,1);                        %标记每个顾客是否被服务

for j=1:K*L
    i=chrom(j)-L*floor((chrom(j)-1)/L);             %第j个基因位对应的顾客i
    m=1+floor((chrom(j)-1)/L);                      %第j个基因位对应的车辆m
    tempm=vc
{m};                                    %车辆m当前所服务的顾客
    find0=find(flag==0,1,'first');                  %寻找没被服务的顾客
    % 如果存在未被服务的顾客
    if ~isempty(find0)
        % 如果顾客i没有被服务
        if flag(i)==0
            % 如果车辆m能服务顾客i
            if demand(i)+load(m)<=cap
                tempm=[tempm,i];                            %将顾客i添加到车辆m的路径中
                flag(i)=1;                                  %标记顾客i已经被服务
                load(m)=load(m)+demand(i);                  %车辆m的装载量增加
                vc{m}=tempm;                                %更新车辆m当前所服务的顾客
            else
                % 如果车辆m不能服务顾客i,则遍历m之后的车辆,看能否服务顾客i
                for h=m+1:K
                    temph=vc{h};                            %车辆h当前所服务的顾客
                    if demand(i)+load(h)<=cap
                        temph=[temph,i];                    %将顾客i添加到车辆h的路径中
                        flag(i)=1;                          %标记顾客i已经被服务
                        load(h)=load(h)+demand(i);          %车辆m的装载量增加
                        vc{h}=temph;                        %更新车辆h当前所服务的顾客
                        break
                    end
                end
            end
        end
    else % 如果不存在未被服务的顾客
        break
    end
end
DEL=Judge_Del(vc);                                      %判断是否有元素丢失
vl=vehicle_load(vc,demand);                             %每辆车最终的装载量
findCap=find(vl>cap,1,'first');                         %寻找是否存在不满足容量约束的车辆
find0=find(flag==0,1,'first');                          %寻找是否存在没有被服务的顾客

%%  标记当前解是否合理,即所有顾客是否都被服务,是否有元素丢失,以及是否存在不满足容量约束的车辆
%   如果合理则reasonable=1,否则reasonable=0
if (isempty(find0))&&(isempty(DEL))&&(isempty(findCap))
    reasonable=1;
else
    reasonable=0;
end

end


但是这个编码方式有一个问题,必须提前直到车辆使用数目K,为了解决这个问题,我们先进行预处理,初步找到能服务所有顾客并满足所有约束的最少的车辆数目。比如上面的例子初始K=5,那么我们从K=5开始解码,然后在当前车辆数目的情况下解码50次,如果能成功找到合理的人工鱼,则K=K-1;如果在50次解码后,依然不能找到合理的人工鱼,则当前的K即为最少的车辆数目minK。找到minK后,然后在此基础上按照人工鱼群算法求解TSP问题中的操作求解一个“类似TSP”的问题,即求解一个最佳的排序。预处理代码如下:

%% 对解进行预处理,因为车辆数目和顾客数目都提前已知,我们需要初步确定最小车辆数目
% 输入
% L             顾客数目
% K             车辆数目
% demand        每个顾客的需求配送量
% cap           每辆车的载重量
% 输出
% minK          初步确定的最小使用车辆数目
% chromMinK     记录当车辆数目取最小时的染色体
% vc_minK       记录当车辆数目取最小时每辆车服务的顾客
% r_minK        记录当车辆数目取最小时vc_minK是否满足所有约束,1表示满足,0表示不满足
% 思路:
% 从K开始逐步递减遍历,车辆数目每次取一个数值就遍历50次,
% 如果提取满足约束要求,则车辆数目减1,反之,则跳出循环,当前车辆数目即为初步确定的最小车辆数目
function [minK,chrom_minK,vc_minK,r_minK]=Pre_Deal(L,K,demand,cap)

flag=1;                                                             %标记在当前车辆数目情况下是否存在合理解
chrom_minK=zeros(1,L*K);                                            %记录当车辆数目取最小时的染色体
while flag==1
    for i=1:50
        chrom=Encode(L,K);                                          %在车辆数目为K的情况下编码
        [~,reasonable]=Decode(L,K,chrom,demand,cap);                %判断在当前车辆数目情况下编码是否合理
        if reasonable==1
            chrom_minK=chrom;
            K=K-1;
            break
        end
    end
    % 如果在当前车辆数目情况下遍历50次,依然不能满足约束则跳出循环
    if reasonable==0
        break
    end
end
minK=K+1;                                                           %因为多减了一个,所以需要加1                                                            
[vc_minK,r_minK]=Decode(L,minK,chrom_minK,demand,cap);              %对染色体chrom_minK进行解码
end


虽然小编说其它方式按照人工鱼群算法求解TSP问题中的操作进行求解,但是代码还是需要更改一下,代码链接如下(后台回复“人工鱼群CVRP”提取代码):

链接:https://pan.baidu.com/s/1JjkUygNMsGfEqQ6oJLHBQA
提取码:9fo9