Engee 文档
Notebook

查找图中的最短路径

在这个示例中,我们将使用Graphs.jl 库的函数创建一个图形,并找出图形上两点之间的最短路径。

使用图形

在物流、运输、信息或社交网络以及其他相关系统中,图形经常被用作模型的元素。它们是由顶点(vertex,顶点)和边(edges)组成的抽象数学对象。通常,每个顶点都有一个唯一的编号,而边则用两个数字表示--即它们所连接的顶点的编号。

In [ ]:
Pkg.add(["GraphRecipes", "Graphs"])
In [ ]:
# Если библиотека у вас установлена, закомментируйте следующую строчку:
import Pkg; Pkg.add( ["Graphs", "GraphRecipes"] )
In [ ]:
using Graphs, GraphRecipes

软件包 GraphsRecipes.jl 负责绘制图形。

让我们创建一个有三个节点和几条边的简单有向图。

In [ ]:
g = DiGraph()
add_vertices!( g, 3 );

现在图中有三个节点,让我们用边将它们连接起来。

In [ ]:
add_edge!( g, 1, 2 );
add_edge!( g, 1, 3 );
add_edge!( g, 2, 3 );

得出这个图形:

In [ ]:
gr() # Упрощенная отрисовка
graphplot( g, names=1:nv(g) )
Out[0]:

请注意,节点名称names 取自 "外部 "数组1:nv(g) ,该数组存储了从1nv 的节点编号(顶点数,即顶点数)。

创建地图,在地图上搜索路径

作为寻找最短路径的领域,我们将采用一个随机矩阵,并设定沿着主对角线穿越矩阵的目标。当然,除了选择 "正确 "的伪随机数发生器起始值Random.seed!() 之外,我们无法确保这张 "地图 "的连通性以及从 A 点到达 B 点的可能性。

In [ ]:
using Random;
Random.seed!( 4 )

карта = rand( 30, 30 ) .> 0.3;
heatmap( карта )

# Точка старта и финиша
точка_А = CartesianIndex(1,1)
точка_Б = CartesianIndex(30,30)

# Добавим на график токи старта и финиша
scatter!( [точка_А[1]], [точка_А[2]], c=:red )
scatter!( [точка_Б[1]], [точка_Б[2]], c=:green, leg=false )
Out[0]:

我们的图将是不定向的,也就是说,沿着每条边,既可以到达那里,也可以返回那里。

In [ ]:
g = Graph();
индексы_доступных_точек = LinearIndices( карта )[ карта .== 1 ]
println( "Количество площадок, куда можно наступать: ", count(индексы_доступных_точек .> 0 ))
Количество площадок, куда можно наступать: 634

辅助对象(坐标转换)

让我们保存一个矩阵,其中将存储所有地图点的二维坐标。

In [ ]:
координаты_всех_точек = CartesianIndices( size(карта) );

创建一个图形,将图形顶点编号转换为坐标:

In [ ]:
координаты_точек_графа = Dict(zip( 1:length(индексы_доступных_точек), координаты_всех_точек[индексы_доступных_точек] ))
first( sort(collect(координаты_точек_графа)), 5 )
Out[0]:
5-element Vector{Pair{Int64, CartesianIndex{2}}}:
 1 => CartesianIndex(1, 1)
 2 => CartesianIndex(2, 1)
 3 => CartesianIndex(3, 1)
 4 => CartesianIndex(4, 1)
 5 => CartesianIndex(5, 1)

现在--反转这个字典,就能将坐标转换为图形中的点号。

In [ ]:
индексы_точек_по_координатам = Dict(value => key for (key, value) in координаты_точек_графа)
first( sort(collect(индексы_точек_по_координатам)), 5 )
Out[0]:
5-element Vector{Pair{CartesianIndex{2}, Int64}}:
 CartesianIndex(1, 1) => 1
 CartesianIndex(2, 1) => 2
 CartesianIndex(3, 1) => 3
 CartesianIndex(4, 1) => 4
 CartesianIndex(5, 1) => 5

让我们将点坐标添加到数组xxyy - 我们将把它们传递给图形绘制过程。

In [ ]:
xy = Tuple.(first.(sort(collect(индексы_точек_по_координатам))));
xx = first.(xy);
yy = last.(xy);

填充图形

由于我们将使用一个标准函数来查找最短路径,而不是自己从头开始创建,因此我们需要创建一个图来进行查找。

In [ ]:
g = Graph()
add_vertices!( g, length(координаты_всех_точек[ индексы_доступных_точек ][:]) )
Out[0]:
634

让我们遍历所有点,如果它们之间有通道,就将它们连接到相邻点。

In [ ]:
MAX_X,MAX_Y = size( карта )

for вершина in 1:nv(g)
    x,y = Tuple(координаты_точек_графа[вершина])
    точка_справа = CartesianIndex( x + 1, y )
    точка_снизу = CartesianIndex( x, y + 1 )
    
    # Соединим текущую точку с точкой справа, если есть связанность согласно карте пройденных точек 
    if x < MAX_X && карта[точка_справа] == 1 add_edge!(g, вершина, индексы_точек_по_координатам[точка_справа]); end;
    # То же самое для точки снизу
    if y < MAX_Y && карта[точка_снизу] == 1 add_edge!(g, вершина, индексы_точек_по_координатам[точка_снизу]); end;
end

看看我们的图中是否添加了边以及添加了多少条边:

In [ ]:
g
Out[0]:
{634, 871} undirected simple Int64 graph

从输出结果来看,在 634 个点上添加了 840 条边。让我们显示图形:

In [ ]:
heatmap( карта', title="Карта", titlefont=font(9), leg=:false )
graphplot!( g, x = xx, y = yy, nodeshape=:circle, nodecolor=:red, nodestrokecolor=:red, nodesize=1.5, edgecolor=:red, curves=true, size=(500,500) )
Out[0]:

我们无需 "展开 "图形,因为我们已经知道了每个节点的坐标。其他绘制参数可在 此处学习。

寻找最短路径

我们使用 A-star 算法,它利用启发式方法搜索最短路径。输出结果是一组边。

In [ ]:
# Нахождение кратчайшего пути
path = a_star( g, индексы_точек_по_координатам[точка_А], индексы_точек_по_координатам[точка_Б] );
first( path, 5 )
Out[0]:
5-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}:
 Edge 1 => 2
 Edge 2 => 27
 Edge 27 => 51
 Edge 51 => 52
 Edge 52 => 79

让我们制作一个从 A 点到 B 点所需访问的图点数组。

In [ ]:
points = Tuple.( vcat([точка_А], [ координаты_точек_графа[dst(v)] for v in path ]) );
first( points, 5 )
Out[0]:
5-element Vector{Tuple{Int64, Int64}}:
 (1, 1)
 (2, 1)
 (2, 2)
 (2, 3)
 (3, 3)
In [ ]:
# Вывод карты (со смещенными значениями для нужной яркости графика)
heatmap( карта' .* 0.3 .+ 0.7, title="Кратчайший путь", leg=false, c=:grays, aspect_ratio=:equal )

# Вывод графа допустимых перемещений по карте
#graphplot!( g, x = xx, y = yy, curves=true, nodeshape=:circle, size=(400,400) )

# Вывод кратчайшего пути
plot!( first.(points), last.(points), lw=3, linez=range(0,1,length(points)), c=:viridis, yflip=true )
Out[0]:

结论

图形是非常强大的数学对象,可以让您在不同的数据中找到结构 - 循环、树、最短或最长路径以及连接区域。在动态系统建模环境中使用图形为工程师提供了非常有趣的可能性。