2.1 Dijkstra路径规划原理

杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

理论参考连接

最短路径问题—Dijkstra 算法最详解
Matlab官方代码实现
最短路径分析之Dijkstra算法

2.2 Dijkstra算法图论示例

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.3 Dijkstra路径规划算法MATLAB代码

2.3.1 Matlab实现示例

MATLAB实现的路径规划动图如图:
在这里插入图片描述

2.3.2 主代码:Dijkstra_main.m

主代码,各部见注释,对其中关键数据的解释如图:
在这里插入图片描述

%% 定义一些基础数据,包括地图XY长宽,起点,终点,障碍物位置等
clc;clear;close all;
rows = 38; cols = 63;                       % 地图行列尺寸
startSub = [35,2];                          % 起点行列位置
goalSub = [1,20];                          % 终点行列位置
load('obsSub.mat');                         % 加载地图数据,为障碍物的行列坐标值,n*2格式
dy_SearchArea =[];                          % 存放所有遍历节点,用于动态绘制搜索区域
searchHead = 100;searchEnd = searchHead;    % 动态绘制搜索区域的速度

% 当使用随机障碍物时,生成随机障碍物的行列值
% randr = randi([1,Rows],100,1);randc = randi([1,Cols],100,1);
% ObsSub = [randr randc];


%% 定义栅格地图,并初始化,此部分以后内容,均不用修改-------------------------------------------------
% 初始化地图数值为1,代表全空白区域
field = ones(rows, cols);

% 起点、终点、障碍物区域的权值
% Tips:对于单个点,Filed(x,y) = 2是可以的,但是多个点需要转成索引数组才能
field(startSub(1),startSub(2)) = 4;
field(goalSub(1),goalSub(2)) = 5;

% 后续路径规划算法,对于点的位置,均用索引值进行表示(输入变量只需要一个就行)
obsR = obsSub(:,1);obsC = obsSub(:,2);
obsIndex = sub2ind([rows,cols],obsR,obsC);
startIndex = sub2ind([rows,cols],startSub(1),startSub(2));
goalIndex = sub2ind([rows,cols],goalSub(1),goalSub(2));
field(obsIndex) = 2;


%% 绘制栅格地图-----------------------------------------------------------------------------------------
% 定义函数,列数,以及障碍物坐标
cmap = [1 1 1; ...       % 1-白色-空地
    0 0 0; ...           % 2-黑色-静态障碍
    1 0 0; ...           % 3-红色-动态障碍
    1 1 0;...            % 4-黄色-起始点 
    1 0 1;...            % 5-品红-目标点
    0 1 0; ...           % 6-绿色-到目标点的规划路径   
    0 1 1];              % 7-青色-动态规划的路径

% 绘制地图,安装数值不同显示不同颜色
colormap(cmap);
image(1.5,1.5,field);

% 设置栅格属性
grid on;hold on;
set(gca,'gridline','-','gridcolor','k','linewidth',0.5,'GridAlpha',0.5);
set(gca,'xtick',1:cols+1,'ytick',1:rows+1);
set(gca, 'XAxisLocation','top')
axis image;


%% Dijkstra算法******************************************************************************************
% S/U的第一列表示栅格节点线性索引编号
% 对于S,第二列表示从起点到本节点Node已求得的最小距离;
% 对于U,第二列表示从起点到本节点Node暂时求得的最小距离

% U是n*2的向量,第一列表示地图某位置,第二列表示起点到该点的距离
U(:,1) = (1: rows*cols)';               % 将行向量转置成列向量
U(:,2) = inf;                           % 初始起点到各个点距离都是无穷inf
S = [startIndex, 0];                    % 将起点放入S集合,起点到起点距离为0
U(startIndex,:) = [];                   % 在U集合中删除起点的信息

% ======================dijkstra第一段,更新起点的邻节点及代价,目的使得某些变量不为空,进入循环
% 函数DJ_NextNodes主要获得目标点附近8个点的距离值
nextNodes = Dijkstra_NextNodes(rows, cols, startIndex, field);

% 起点周围8个点更新在U中的权值,即距离值
for i = 1:8
    next = nextNodes(i,1);
    
     % 判断该子节点是否存在,然后将子节点权值更新到U
    if ~isinf(next)                        
        idx = find(U(:,1) == next);         % 找到子节点在U数组的位置来修改值
        U(idx,2) = nextNodes(i,2);          % 更新子节点在U中的权值
    end   
end

% path为n*2向量,第一列存放地图上点的索引位置,第二列存放父节点到子节点的可达路径
for i = 1:rows*cols
    % 存放点的索引值
    path{i,1} = i; 
end

% 更新起点到周围8个点的路径
% Tips:起点到任意有效点的路径都有,但是是否为最短路径,通过U集合比较来判断
% Tips:程序在这一步,并没有进行最短路径的比较
for i = 1:8
    next =  nextNodes(i,1);
    if ~isinf(nextNodes(i,2))
        % 起点到周围点的路径
        path{next,2} = [startIndex,nextNodes(i,1)]; 
    end
end

% =======================dijkstra第二段,各种数据不为空之后,进行循环遍历
while ~isempty(U)
    % ************求最短路径在U集合找出当前最小距离值的节点,并移除该节点至S集合中
    [dist_min, idx] = min(U(:,2));                      % 在U集合中找出此时最短距离的节点和距离值
    node = U(idx, 1);                                   % 获得最短距离的节点索引值
    S(end+1,:) = [node, dist_min];                      % 最短距离的节点索引和距离添加到S集合
    U(idx,:) = [];                                      % 将其移除出U
    
    % ===========在循环中绘制动态搜索的区域,即所有被判断过的Node组成的集合
    if find(node==obsIndex)
    elseif node == goalIndex
    else
        dy_SearchArea(end+1) = node;                         % 动态搜索区域不包括起点终点和障碍物点
        field(dy_SearchArea) = 7;                            % 搜索区域为青色
        if mod(searchHead,searchEnd) == 0                    % 控制动图绘制速度
            image(1.5,1.5,field);                            % 联合drawnow函数绘制动态效果
            drawnow;
        end
    end
    searchHead = searchHead + 1;                            % 更新刷新速度
    
    % 从获得的新节点Node出发(相当于Node = startpos),进行循环判断求最短路径
    nextNodes = Dijkstra_NextNodes(rows, cols, node, field);

    % 依次遍历Node(父节点)的邻近节点(子节点),判断子节点是否在U集合中,更新子节点的距离值
    for i = 1:8 
        next = nextNodes(i,1);
                           
        % 需要判断的子节点,子节点不为空,而且不在S集合里(已选择点的集合)
        if ~isinf(next)  && ~ismember(next, S(:,1))
            idx_U = find(next == U(:,1));               % 找出U集合中节点childNode的索引值
            cost = nextNodes(i,2);                      % 获取子节点距离值
            
            % 判断是否更新,此部分见图解,理解
            % Tips:首先U(:2)初始化默认为inf无穷大,需要一步步的判断更新距离值
            if dist_min + cost < U(idx_U, 2)
                U(idx_U, 2) = dist_min + cost;
                path{next, 2} = [path{node, 2}, next];  % 更新最优路径
            end
        end
    end
end


% =======================dijkstra第三段:找出目标最优路径
optPath = path{goalIndex,2};        % dijkstra算法,目标点在path的第二列上存放到目标点的最短路径集合
                                    % Tips:从此中能看到,在未到达目标点钱,路径规划的最短路径都非正确的
                                    % Tips:因此想要绘制动态路径曲线这种方法是不行的
                                    % Tips:只能绘制动态的搜索区域(判断U集合的第二列距离值是否非inf)

%% 绘制最优路径的折线图***********************************************************************************
% 由于绘图函数plot针对的窗口是x,y,它们与row,col是的先后顺序是相反的,即X对应col,y对应row
image(1.5,1.5,field);                                       % 将动画速度未显示完的搜索区域全部显示
field(dy_SearchArea) = 1;                                   % 最后将搜索区域的值改回为空白值
[plotr,plotc] = ind2sub([rows,cols],optPath);
plot(plotc+0.5,plotr+0.5,'LineWidth',2.5);


2.3.3 函数代码:Dijkstra_NextNodes.m

function nextNodes = Dijkstra_NextNodes(rows,cols,nodeIndex,field)
% DJ_NEIGHBORNODES 获取当前点(父节点)的索引值和地图信息Field来进行下一个节点(子节点)的信息更新
% 更新的信息包括子节点的索引值和父节点到子节点的距离值

%% 将父节点装换成行列式位置信息,方便进行上下左右移动,并对存放子节点的数据进行初始化
[row,col] = ind2sub([rows,cols],nodeIndex);             % 父节点装换成行列式位置信息
nextNodes = inf(8,2);                                   % 子节点的信息列表
movePos = [-1,1;0,1;1,1;-1,0;1,0;-1,-1;0,-1;1,-1];      % 父节点移动方向列表

%% 更新父节点朝着各个方向移动到子节点的距离
for i = 1:8
    % 父节点有效移动范围,不能越界
    if 0 < row+movePos(i,1) && row+movePos(i,1) <= rows && 0 < col+movePos(i,2) && col+movePos(i,2) <= cols  
        subscript = [row+movePos(i,1),col+movePos(i,2)];
        index = sub2ind([rows,cols], subscript(1),subscript(2));
        nextNodes(i,1) = index;
        % 子节点不为障碍物时,通过计算二范数公式计算距离
        if field(subscript(1), subscript(2)) ~= 2       % 子节点不为障碍物
            cost = norm(subscript - [row,col]);         % 二范数计算距离
            nextNodes(i,2) = cost;                      % 更新信息表
        end   
    end
end

end

2.4 Dijkstra路径规划算法Python代码

可以参照PythonRobotics:(https://github.com/redglassli/PythonRobotics#a-algorithm

本栏参代码逻辑和MATLAB是一致的

2.4.1 Python实现dijkstra示例

采用3 移动机器人路径规划(1- 栅格地图绘制)中第1.4.3热力图绘制的静态最短路径图像

在这里插入图片描述

采用3 移动机器人路径规划(1- 栅格地图绘制)中1.4.5的plot方法绘制的动态搜索和最短路径如下图

在这里插入图片描述

2.4.2 辅助函数PathPlanning.py

3 移动机器人路径规划(1- 栅格地图绘制)的第1.4.2是一样的

2.4.3 Dijkstra路径规划Python代码

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import colors
import copy

import PathPlanning


'''
Dijkstra_NextNode(field,node)找寻周围节点路径函数,比MATLAB省略部分多余输入
'''
def Dijkstra_NextNode(field,node):
    rows = len(field)
    cols = len(field[0])
    movepos = [[-1, 1], [0, 1], [1, 1], [-1, 0],
               [1, 0], [-1, -1], [0, -1], [1, -1]]
    nextnodes = [[np.inf,np.inf],[np.inf,np.inf],[np.inf,np.inf],[np.inf,np.inf],
                [np.inf,np.inf],[np.inf,np.inf],[np.inf,np.inf],[np.inf,np.inf]]

    # 将父节点装换成行列式位置信息,方便进行上下左右移动,并对存放子节点的数据进行初始化
    nodeSub = PathPlanning.ind2sub([rows,cols],node)

    ## 更新父节点朝着各个方向移动到子节点的距离
    for i in range(8):
        r = nodeSub[0] + movepos[i][0]
        c = nodeSub[1] + movepos[i][1]

        # 父节点有效移动范围,不能越界
        if -1 < r and r < rows and -1 < c and c < cols:
            nextnodes[i][0] = PathPlanning.sub2ind([rows,cols],r,c)

            # 子节点不为障碍物时,计算距离
            if field[r, c] != 2:
                dist = np.sqrt(movepos[i][0]*movepos[i][0]+movepos[i][1]*movepos[i][1])
                nextnodes[i][1] = dist

    return nextnodes

'''
# 初始化地图---------------------------------------------------------------------
'''
rows = 5
cols = 6
startSub = [2,1]
goalSub = [2,5]
obsSub = [[1,3],[2,3],[3,3]]


# 栅格地图属性
field = np.ones((rows, cols))

field[startSub[0], startSub[1]] = 4
field[goalSub[0], goalSub[1]] = 5

for i in range(len(obsSub)):
    field[obsSub[i][0], obsSub[i][1]] = 2

# 数据转换
startIndex = PathPlanning.sub2ind([rows,cols],startSub[0], startSub[1])
goalIndex = PathPlanning.sub2ind([rows,cols],goalSub[0], goalSub[1])


'''
## Dijkstra算法******************************************************************************************
# S/U的第一列表示栅格节点线性索引编号
# 对于S,第二列表示从起点到本节点Node已求得的最小距离;
# 对于U,第二列表示从起点到本节点Node暂时求得的最小距离
'''

# U_pos存放索引点
# U_dist存放起点到该点的距离
U_pos = []
U_dist = []

for i in range(rows*cols):
    U_pos.append(i)
    U_dist.append(np.inf)

# 将起点放入S集合,起点到起点距离为0
S_pos = [startIndex]
S_dist = [0]

# 在U集合中删除起点的信息
idx = U_pos.index(startIndex)
U_pos.pop(idx)
U_dist.pop(idx)

# path为n维向量,默认0->rows*cols个,按照索引的一致性,存放点到点的路线
path = []

for i in range(rows*cols):
    # append([数组])
    path.append([-1])




# ======================dijkstra第一段,更新起点的邻节点及代价,目的使得某些变量不为空,进入循环
# 函数DJ_NextNodes主要获得目标点附近8个点的距离值
nextNodes = Dijkstra_NextNode(field,startIndex)

# 起点周围8个点更新在U中的权值,即距离值
for i in range(8):
    nextnode = nextNodes[i][0]

    # 判断该子节点是否存在,然后将子节点权值更新到U
    if nextnode != np.inf:
        idx = U_pos.index(nextnode)
        U_dist[idx] = nextNodes[i][1]

    # 更新起点到周围8个点的路径
    # Tips:起点到任意有效点的路径都有,但是是否为最短路径,通过U集合比较来判断
    # Tips:程序在这一步,并没有进行最短路径的比较
    if nextNodes[i][1] != np.inf:
        path[nextnode] = copy.deepcopy([startIndex,nextNodes[i][0]])


# searhx = []
# searhy = []
# # 新建画布指定大小
# fig = plt.figure(figsize=(4,3))
# # 新建子图
# ax = fig.add_subplot(111)
# startXY = PathPlanning.sub2xy([rows,cols],startSub[0],startSub[1])
# goalXY = PathPlanning.sub2xy([rows,cols],goalSub[0],goalSub[1])
# obsX = []
# obsY = []

# for i in range(len(obsSub)):
#     obsxy = PathPlanning.sub2xy([rows,cols],obsSub[i][0],obsSub[i][1])
#     obsX.append(obsxy[0])
#     obsY.append(obsxy[1])



# =======================dijkstra第二段,各种数据不为空之后,进行循环遍历
while len(U_pos) > 0:
    # ************求最短路径在U集合找出当前最小距离值的节点,并移除该节点至S集合中
    idx = U_dist.index(min(U_dist))
    dist_min = U_dist[idx]
    node = U_pos[idx]

    S_pos.append(node)
    S_dist.append(dist_min)

    U_pos.pop(idx)
    U_dist.pop(idx)

    # # 绘图1
    # nodesub = PathPlanning.ind2sub([rows,cols],node)
    # if field[nodesub[0]][nodesub[1]] == 1:
    #     nodexy = PathPlanning.sub2xy([rows,cols],nodesub[0],nodesub[1])
    #     searhx.append(nodexy[0])
    #     searhy.append(nodexy[1])


    # 从获得的新节点Node出发(相当于Node = startpos),进行循环判断求最短路径
    nextNodes = Dijkstra_NextNode(field,node)

    # 依次遍历Node(父节点)的邻近节点(子节点),判断子节点是否在U集合中,更新子节点的距离值
    for i in range(8):
        nextnode = nextNodes[i][0]

        if nextnode != np.inf:
            if nextnode not in S_pos:
                idx_u = U_pos.index(nextnode)
                cost = nextNodes[i][1]

                # 判断是否更新,此部分见图解,理解
                # Tips:首先U(:2)初始化默认为inf无穷大,需要一步步的判断更新距离值
                if dist_min+cost < U_dist[idx_u]:
                    U_dist[idx_u] = dist_min+cost
                    path[nextnode] = copy.deepcopy(path[node])
                    path[nextnode].append(nextnode)
    


    # # 绘图2
    # plt.plot(startXY[0],startXY[1],'r+')
    # plt.plot(goalXY[0],goalXY[1],'b+')
    # plt.plot(obsX,obsY,'sk')
    # plt.plot(searhx,searhy,'sr')
    # ax.set_xlim([-1,cols])
    # ax.set_ylim([-1,rows])
    # ax.set_xticks(np.arange(cols))
    # ax.set_yticks(np.arange(rows))
    # plt.pause(0.05)
    # plt.cla()
    




opt_pathIndex = path[goalIndex]
optpathsub = []
for i in range(len(opt_pathIndex)):
    optpathsub.append(PathPlanning.ind2sub([rows,cols],opt_pathIndex[i]))

optx = []
opty = []

for i in range(0,len(optpathsub),1):
    field[optpathsub[i][0]][optpathsub[i][1]] = 6
    optxy = PathPlanning.sub2xy([rows,cols],optpathsub[i][0],optpathsub[i][1])
    optx.append(optxy[0])
    opty.append(optxy[1])

field[startSub[0], startSub[1]] = 4
field[goalSub[0], goalSub[1]] = 5

# plt.plot(startXY[0],startXY[1],'r+')
# plt.plot(goalXY[0],goalXY[1],'b+')
# plt.plot(obsX,obsY,'sk')
# plt.plot(searhx,searhy,'sr')
# plt.plot(optx,opty,'b')
# ax.set_xlim([-1,cols])
# ax.set_ylim([-1,rows])
# ax.set_xticks(np.arange(cols))
# ax.set_yticks(np.arange(rows))
# plt.pause(5)
# PathPlanning.DrawHeatMap(field)
# plt.show()

2.5 0积分资源下载

代码直接复制即可运行