【算法】怎么用算法给外卖小哥规划路线

前言

假设现在外卖小哥接了10个单,那么要怎么规划路线呢?当然,经验丰富的老哥一看就知道了,但是新人菜鸟小哥就未必能找到节省时间的路线。


那么,如果使用算法来解决这个问题会怎么样?如何在尽量少的总路程和时间内,选择一条优秀路线呢?不妨使用搜索算法来解决这个问题,例如遗传算法就可以解决这类型的问题。

主要包括两步

1.A、B两点之间的代价函数

这里的代价不仅是时间,也要考虑路程,难易程度等。当然,只考虑时间也是可以的,主要看小哥自己认为代价是什么。

2.寻找最优路径

使用优化算法,寻找一条总代价最小的路径就可以了。可以使用遗传算法、模拟退火算法、PSO算法等等。

一、送餐的代价函数

我们需要考虑到多种因素,包括但不限于:
1.交通状况:交通状况是影响外卖小哥送餐速度的重要因素之一。规划路线时需要考虑到交通拥堵、红绿灯等因素,选择最佳的路线。
2.餐厅出餐速度:外卖小哥需要考虑到餐厅出餐速度,避免等待时间过长,影响送餐速度。
3.客户要求:有些客户可能有特殊要求,比如需要外卖小哥爬楼梯等,规划路线时需要考虑到这些因素

对上述每个因素,都分别进度量,最后综合,从而得到两点之间的代价函数C(A,B).

二、送餐的最优路径

接下来,就是一个TSP的问题了,也就是求解如何走完所有取餐、送餐点,并使得总代价时间最小。在这里,我们不妨使用遗传算法,

2.1 遗传算法简介

遗传算法(Genetic Algorithm,GA)是由美国的John Holland教授于20世纪70年代首次提出的,它是一种自适应随机搜索启发式算法。遗传算法以自然选择规律与遗传理论为依托,模拟生物进化过程中的遗传、交叉、变异等过程,通过数学的方式利用计算机仿真运算,将问题的求解过程转换成类似生物进化中的染色体基因的交叉、变异等过程。

遗传算法在求解较为复杂的组合优化问题时,相对一些常规的优化算法,通常能够较快地获得较好的优化结果。它被广泛应用于复杂函数系统优化、机器学习、系统识别、故障诊断、分类系统、控制器设计、神经网络设计、自适应滤波器设计等领域。

详细可参考《老饼讲解|【原理】遗传算法-入门讲解

2.2 遗传算法的算法流程

遗传算法的算法流程如下

2.3 遗传算法应用于路径规划

用遗传算法,需要设计好该问题的以下三点:
👉1. 染色体交换方式:解与解之间如何进行部分交换
👉2. 基因变异的方式:单个解如何作出随机改变    
👉3. 适应度的定义   :用于评估解的优秀程度      
对本问题我们可以设计如下: 
  
 (1)遗传算法染色体交换方式
将解群两两随机配对,每一对之间(设为a,b)以如下方式交换:
设a=[0,1,5,3,2,4],b=[1,2,5,0,3,4],先随机抽出a的一个片段a_piece,
假设a_piece= [5,3,2],再在b中找出 a_piece 的排序 b_piece=[2,5,3],
将b_piece替换到a,将a_piece替换到b,得到:a=[0,1,2,5,3,4],b=[1,5,3,0,2,4]

(2)遗传算法基因变异方式
设a=[0,1,5,3,2,4], 
先随机抽出a的一个片段a_piece,假设a_piece= [5,3,2],
将a_piece翻转( [2,3,5])后再回代a,得到a=[0,1,2,3,5,4]

(3)遗传算法适应度设计:
本问题的目标函数一定是大于0的,我们简单的设为  Accept = \dfrac{10}{C(A,B))+1}
即可(F是目标函数的值),就可以达到函数值越小,适应度越大的效果。

设计完以上三点后,只需按遗传算法的算法流程,实现代码即可

三、送餐的最优路径-代码实现

3.1代码说明

下面,我们使用代码实现上述算法。但为了便于实现,作出如下两点假设与简化:

1.由于我们还没有得到任意两点A、B之间的代价函数C(A,B)的具体表达式,而为了能够实现代码,我们不妨先用距离函数D(A,B)替代代价函数C(A,B),

2.对于地点之间的路线和交通是复杂的,需要依赖了第三方服务的支持,这里我们简单为两点的直线距离就好,

3.2 遗传算法规划路径-代码实现

代码实现如下:

%------代码说明:展示遗传算法求解TSP问题 -----------------
% 来自《老饼讲解神经网络》www.bbbdata.com ,matlab版本:2018a 
function gaforTSP()
clc;clear all;close all ;
rng(999)
global distMat;
cityLoc = [3.64,2.68  ;...
           4.18,1.76  ;...
           3.71,2.60  ;...
           1.33,3.30  ;...
           4.20,2.96  ;...
           3.92,1.82  ;...
           2.55,1.64  ;...
           2.37,1.02  ;...
           3.43,2.09  ;...
           3.54,0.70  ;...
           3.51,1.62  ;...
           3.44,0.80  ;...
           3.24,2.77  ;...
           2.38,2.32  ;...
           2.56,2.24  ;...
           3.01,2.03  ;...
           2.79,2.51  ;...
           4.03,1.16  ;...
           3.47,0.70  ;...
           1.30,1.69] ;   
distMat = dist(cityLoc') ;                                     % 计算城市距离
N = size(cityLoc,1);                                           % 城市个数
rng(888)                                                       % 为方便复现结果,设定随机种子

%----------------参数初始化-----------------------
m = 30;    %种群规模
t = 5000;  %迭代次数
var_rate = 0.2;   %变异概率
%--------------初始化种群-------------------------
xg = cell(m,1);                                                 % 初始化种群
for i =1:m
    xg{i} = randperm(N);                                        % 随机初始化种群
end

h_best_x = xg{1};           % 初始化历史最优个体
h_best_F = calF(h_best_x);  % 初始化历史最优个体的目标函数值

F_list = [];                 % 每代最优函数值记录列表
for i=1:t
    % ----种群交换染色体与基因变异-----
    xg = exPart(xg);                                            % 交换染色体
    xg = genVar(xg,var_rate);                                   % 基因变异
    
    [best_x,best_F] =  getBest(xg);                             % 获取本代最佳个体
    if(best_F <h_best_F)                                        % 更新历史最优个体
        h_best_F = best_F;                                      % 更新历史最优函数值
        h_best_x = best_x;                                      % 更新历史最优个体
    end
    
    disp(['第',num2str(i),'代:最优F=',num2str(best_F)])         % 显示当前轮的最优个体
    F_list = [F_list,best_F];                                   % 记录历代最优个体
    
    % 判断是否满足退出迭代条件.
    % 退出条件:近20代最优个体变化不大,则退出
    last_F = F_list(max(1,end-20):end);                         % 截取最后20轮的最佳函数值
    diff = (max(last_F)-min(last_F))/max(abs(mean(last_F)),1);  % 最近20轮的差异
    if((i>20) && (diff<0.001))                                  % 判断没什么差异
        break                                                   % 则退出迭代
    end
    %-------------赌轮盘生成下一代---------------------------
    PPan = getPPan(xg);                                         % 计算种群的概率轮盘
    xg   = genChildGroup(xg,PPan,h_best_x);                     % 根据概率轮盘选出下一代种群
    
    
    %------------------画图------------------------------------
    hold off
    plot(cityLoc(:,1),cityLoc(:,2),'o','MarkerEdgeColor','k','MarkerFaceColor','y','MarkerSize',10,'LineWidth',2)
    hold on
    plot(cityLoc([h_best_x,h_best_x(1)],1),cityLoc([h_best_x,h_best_x(1)],2))
    drawnow
end
% 打印最终结果(历史最优)
disp(['h_best_F=',num2str(h_best_F)])                            % 打印历史最优目标函数值
disp(['h_best_x=',num2str(h_best_x)])                            % 打印历史最优个体
end


%------------目标函数值计算方法------------------------------------------
function d= calF(x)
global distMat;
d = distMat(x(end),x(1));                                         % 初始化总距离
for i=1:length(x)-1                                              
    d = d+ distMat(x(i),x(i+1));                                  % 累加各个城市之间的距离
end
end

%------------获取种群中目标函数值最好的一个-------------------------------
function [x,F] = getBest(xg)
F_list = inf(length(xg),1);                                       % 初始化各个个体的函数值
for i = 1:length(xg)
    F_list(i) = calF(xg{i});                                      % 计算各个个体的目标函数值
end
[F,best_index] =min(F_list) ;                                     % 获取最优的目标函数值与索引
x = xg{best_index};                                               % 根据索引获取最优的个体
end                                                               
																  
%------------计算种群进入下一代的概率轮盘-----------------        
function PPan = getPPan(xg)                                       
F_list = inf(length(xg),1);                                       % 初始化各个个体的函数值
for i = 1:length(xg)                                              
    F_list(i) = calF(xg{i});                                      % 计算各个个体的目标函数值
end                                                               
F_list   = F_list - min(F_list);                                  % 令函数值全大于0
acp_list = 1./(1+F_list);                                         % 计算适应度
PPan     = cumsum(acp_list./sum(acp_list));                       % 根据适应度计算轮盘概率
end

%------------染色体交换-------------------------------
function  xg = exPart(xg)
m       = length(xg);                                             % 种群大小
[vn,n]  = size(xg{1});                                            % 变量个数,编码长度
ex_list = randperm(m);                                            % 随机生成种群排序,用于下面两两匹对交换
for i=1:2:m                                                       % 逐对交换
    a = xg{ex_list(i)};                                           % 当前要交换的个体a
    b = xg{ex_list(i+1)};                                         % 当前要交换的个体b
    ex_part  = randperm(n);                                       % 随机生成1到n的随机整数
    ex_part  = ex_part(1:2);                                      % 提取前两位作为要交换的起始结束位置
    ex_start = min(ex_part);                                      % 小者作为交换的起始位置
    ex_end   = max(ex_part);                                      % 大者作为交换的结束位置
    a_piece  = a(ex_start:ex_end);                                % 提出出a要交换的片段
    [~,idx]  = ismember(a_piece,b);                               % 找出a的片段在b中的位置
    idx      = sort(idx);                                         % 对位置进行排序
    b_piece  = b(idx);                                            % 提出b中的片段
    a(ex_start:ex_end) = b_piece;                                 % 将b的片段赋予a
    b(idx)             = a_piece;                                 % 将a的片段赋予b
    xg{ex_list(i)}     = a;                                       % 将个体a重新替换回种群
    xg{ex_list(i+1)}   = b;                                       % 将个体b重新替换回种群
end
end

%------------基因变异-------------------------------
function xg =  genVar(xg,var_rate)
m     = length(xg);                                               % 种群大小
[~,n] = size(xg{1});                                              % 编码长度
for i= 1:m                                                        
    a  = xg{i};                                                   % 提取出当前个体
    if(rand()<var_rate)                                           % 判断当前个体是否变异
        var_part  = randperm(n);                                  % 随机生成变异的位置
        var_part  = var_part(1:2);                                % 截断前两位作为变异起始结束位置
        var_start = min(var_part);                                % 小者作为变异起始位置
        var_end   = max(var_part);                                % 大者作为变异结束位置
        a(var_start:var_end) = flip(a(var_start:var_end));        % 将变异位置翻转
    end                                                           
    xg{i} = a ;                                                   % 将变异后的个体替换进种群
end
end

%------------生成下代种群-------------------------------
function child_xg = genChildGroup(xg,PPan,best_x)
m = length(xg);                                                   % 种群个数
child_xg    = cell(m,1);                                          % 初始化下一代种群
child_xg{1} = best_x;                                             % 最佳的必定选到下一个种群
for i=2:m                                                        
    select_idx  = min(find(rand()<PPan));                         % 生成一个随机数,并找到第一个小于该随机数的位置,就是轮盘指向的位置
    child_xg{i} = xg{select_idx};                                 % 将轮盘选出的个体选入下一代种群
end
end

运行后的结果如下:


如果觉得本文有帮助,点个赞吧!