简介
Dijkstra算法, 是由[荷兰]计算机科学家Edsger Wybe Dijkstra在1956年发现的算法, 它使用类似广度优先搜索的方法解决赋权图的单源最短路径问题. 本算法从一个起点出发找到该点到图中其他顶点的最短路径, 产生一个最短路径树. 需要注意的是, Dijkstra算法不能处理带有负权边的图.
算法流程
基本思路:每次取出未访问顶点中距离最小的点, 用该顶点更新其他顶点的距离.
输入:赋权有向图 G = ( V , E , W ) , G=(V,E,W), G=(V,E,W), 其中 V = { v 1 , . . . , v n } , E = { e i , j : = { v i , v j } } , W = w i , j ( e i , j ) V=\{v_1,...,v_n\},E=\{e_{i,j}:=\{v_i,v_j\}\},W=w_{i,j}(e_{i,j}) V={v1,...,vn},E={ei,j:={vi,vj}},W=wi,j(ei,j)分别代表顶点, 边及权重.
输出:从起点 s s s到 ∀ v i ∈ V − { s } \forall v_i\in V-\{s\} ∀vi∈V−{s}的最短路径.
-
初始点集合 S = { s } S=\{s\} S={s};
-
计算初始点 s s s到其余各点的直接距离 d i s t [ s , v i ] , ∀ v i ∈ V − S dist[s,v_i],\forall v_i\in V-S dist[s,vi],∀vi∈V−S;
-
选出满足距离最小 m i n v k ∈ V d i s t [ s , v k ] min_{v_k\in V}dist[s,v_k] minvk∈Vdist[s,vk]的点 v k v_k vk, 并将该点加入到集合 S S S中, 即 S ∪ v k → S S\cup{v_k}\rightarrow S S∪vk→S,更新 V − S V-S V−S中各顶点的 d i s t dist dist值, i.e.,
如果 d i s t [ s , v k ] + w k , i < d i s t [ s , v i ] dist[s,v_k]+w_{k,i}<dist[s,v_i] dist[s,vk]+wk,i<dist[s,vi], 则 d i s t [ s , v i ] = d i s t [ s , v k ] + w k , i , ∀ v i ∈ V − S dist[s,v_i]=dist[s,v_k]+w_{k,i}, \forall v_i\in V-S dist[s,vi]=dist[s,vk]+wk,i,∀vi∈V−S;
-
返回步骤3, 直至 S = V S=V S=V算法终止.
伪代码
Algorithm: Dijkstra
Input:
D
i
r
e
c
t
e
d
g
r
a
p
h
G
=
(
V
,
E
,
W
)
w
i
t
h
w
e
i
g
h
t
Directed \quad graph\quad G=(V,E,W) \quad with\quad weight
DirectedgraphG=(V,E,W)withweight
Output:
A
l
l
t
h
e
s
h
o
r
t
e
s
t
p
a
t
h
s
f
o
r
t
h
e
s
o
u
r
c
e
v
e
r
t
e
x
s
t
o
o
t
h
e
r
v
e
r
t
e
x
All \quad the\quad shortest\quad paths\quad for\quad the\quad source\quad vertex\quad s \quad to\quad other\quad vertex
Alltheshortestpathsforthesourcevertexstoothervertex
1:
S
←
s
S\leftarrow {s}
S←s
2:
d
i
s
t
[
s
,
s
]
←
0
dist[s,s]\leftarrow 0
dist[s,s]←0
3:
f
o
r
v
i
∈
V
−
S
d
o
for \quad v_i\in V-S \quad do
forvi∈V−Sdo
4:
d
i
s
t
[
s
,
v
i
]
←
w
(
s
,
v
i
)
(
w
h
e
n
v
i
n
o
t
f
o
u
n
d
,
d
i
s
t
[
s
,
v
i
]
←
∞
)
dist[s,v_i]\leftarrow w(s,v_i)\quad (when \quad v_i \quad not \quad found,\quad dist[s,v_i]\leftarrow \infty)
dist[s,vi]←w(s,vi)(whenvinotfound,dist[s,vi]←∞)
5:
w
h
i
l
e
V
−
S
≠
∅
d
o
while \quad V-S\neq \varnothing\quad do
whileV−S=∅do
6:
f
i
n
d
m
i
n
v
k
∈
V
d
i
s
t
[
s
,
v
k
]
f
r
o
m
t
h
e
s
e
t
V
−
S
find \quad min_{v_k\in V}dist[s,v_k]\quad from \quad the \quad set \quad V-S
findminvk∈Vdist[s,vk]fromthesetV−S
7:
S
←
S
∪
{
v
k
}
S\leftarrow S\cup\{v_k\}
S←S∪{vk}
8:
f
o
r
v
k
∈
V
−
S
d
o
for \quad v_k \in V-S \quad do
forvk∈V−Sdo
9:
i
f
d
i
s
t
[
s
,
v
k
]
+
w
k
,
i
<
d
i
s
t
[
s
,
v
i
]
t
h
e
n
if \quad dist[s,v_k]+w_{k,i}< dist[s,v_i]\quad then
ifdist[s,vk]+wk,i<dist[s,vi]then
10:
d
i
s
t
[
s
,
v
i
]
←
d
i
s
t
[
s
,
v
k
]
+
w
k
,
i
dist[s,v_i]\leftarrow dist[s,v_k]+w_{k,i}
dist[s,vi]←dist[s,vk]+wk,i
实例计算
选择
v
1
v_1
v1为起点开启算法的整个流程,
S
=
v
1
S={v_1}
S=v1, 则接下来要计算从
v
1
v_1
v1出发, 到达其余各点的直接距离
d
i
s
t
[
v
2
,
v
1
]
∼
d
i
s
t
[
v
6
,
v
1
]
dist[v_2,v_1]\sim dist[v_6,v_1]
dist[v2,v1]∼dist[v6,v1], 注意这里所说的直接距离是指只通过
v
1
v_1
v1到达其他各点路径的长度, 这是第一个for循环在做的事情. 显然, 只通过顶点
v
1
v_1
v1是无法到达顶点
v
3
,
v
4
,
v
5
v_3,v_4,v_5
v3,v4,v5的, 因此我们有
d
i
s
t
[
v
1
,
v
2
]
=
w
1
,
2
=
10
,
d
i
s
t
[
v
1
,
v
3
]
=
∞
,
d
i
s
t
[
v
1
,
v
4
]
=
∞
,
d
i
s
t
[
v
1
,
v
5
]
=
∞
,
d
i
s
t
[
v
1
,
v
6
]
=
w
1
,
6
=
3
,
d
i
s
t
[
v
1
,
v
1
]
=
0.
dist[v_1,v_2]=w_{1,2}=10,\quad dist[v_1,v_3]=\infty,\\ dist[v_1,v_4]=\infty, \quad dist[v_1,v_5]=\infty,\\ dist[v_1,v_6]=w_{1,6}=3, \quad dist[v_1,v_1]=0.
dist[v1,v2]=w1,2=10,dist[v1,v3]=∞,dist[v1,v4]=∞,dist[v1,v5]=∞,dist[v1,v6]=w1,6=3,dist[v1,v1]=0.
到这里, 伪代码的第4步结束, 现在开始第5步检查集合
V
−
S
=
∅
V-S=\varnothing
V−S=∅是否成立, 显然不是,因为
V
−
S
=
{
v
2
,
v
3
,
v
4
,
v
5
,
v
6
}
.
V-S=\{v_2,v_3,v_4,v_5,v_6\}.
V−S={v2,v3,v4,v5,v6}.
则进行第6步, 选出经过第一个for循环之后, 集合
V
−
S
V-S
V−S中相对于其他点而言到
v
1
v_1
v1距离最短的顶点
v
j
v_j
vj, 显然是
v
6
v_6
v6. 则执行第7步,
S
=
S
∪
{
v
6
}
=
{
v
1
,
v
6
}
,
S=S\cup\{v_6\}=\{v_1,v_6\},
S=S∪{v6}={v1,v6},
说明从起点
v
1
v_1
v1出发到顶点
v
6
v_6
v6的最短路径已找到. 由于集合
S
S
S新加入点, 计算路径时允许通过新的点, 这样就可能会改变原有的路径长度, 因此要执行8-10步对距离进行更新, 以
d
i
s
t
[
v
1
,
v
2
]
dist[v_1,v_2]
dist[v1,v2]的更新为例, 原来从
v
1
v_1
v1到
v
2
v_2
v2只能走直接的距离
d
i
s
t
[
v
1
,
v
2
]
=
w
1
,
2
=
10
dist[v_1,v_2]=w_{1,2}=10
dist[v1,v2]=w1,2=10, 而现在将
v
6
v_6
v6加入到集合
S
S
S后, 我们多了一条路线
v
1
→
v
6
→
v
2
v_1\rightarrow v_6 \rightarrow v_2
v1→v6→v2, 两条路线都可以的话我们需要选择距离相对短的走法, 而
d
i
s
t
[
v
1
,
v
2
]
=
10
>
d
i
s
t
[
v
1
,
v
6
]
+
w
6
,
2
=
5
,
dist[v_1,v_2]=10 > dist[v_1,v_6]+w_{6,2}=5,
dist[v1,v2]=10>dist[v1,v6]+w6,2=5,
所以选择后者. 其余变化的顶点的分析是类似的, 最终我们得到
d
i
s
t
[
v
1
,
v
2
]
=
5
,
d
i
s
t
[
v
1
,
v
3
]
=
∞
,
d
i
s
t
[
v
1
,
v
4
]
=
d
i
s
t
[
v
1
,
v
6
]
+
w
6
,
4
=
9
,
d
i
s
t
[
v
1
,
v
5
]
=
d
i
s
t
[
v
1
,
v
6
]
+
w
6
,
5
=
4
,
d
i
s
t
[
v
1
,
v
6
]
=
w
1
,
6
=
3
,
d
i
s
t
[
v
1
,
v
1
]
=
0.
dist[v_1,v_2]=5,\quad dist[v_1,v_3]=\infty,\\ dist[v_1,v_4]=dist[v_1,v_6]+w_{6,4}=9, \quad dist[v_1,v_5]=dist[v_1,v_6]+w_{6,5}=4,\\ dist[v_1,v_6]=w_{1,6}=3,\quad dist[v_1,v_1]=0.
dist[v1,v2]=5,dist[v1,v3]=∞,dist[v1,v4]=dist[v1,v6]+w6,4=9,dist[v1,v5]=dist[v1,v6]+w6,5=4,dist[v1,v6]=w1,6=3,dist[v1,v1]=0.
现在,算法已经从头到尾执行了一遍, 然后需要回到第5步判断while循环条件确定算法是否继续, 此时
V
−
S
=
{
v
2
,
v
3
,
v
4
,
v
5
}
≠
∅
,
V-S=\{v_2,v_3,v_4,v_5\}\neq \varnothing,
V−S={v2,v3,v4,v5}=∅,
所以算法继续执行, 但计算过程与上述分析仍然是类似的, 这里不再赘述, 我们给出最终的结果:
d
i
s
t
[
v
1
,
v
2
]
=
d
i
s
t
[
v
1
,
v
6
]
=
5
,
d
i
s
t
[
v
1
,
v
3
]
=
d
i
s
t
[
v
1
,
v
6
]
+
w
6
,
2
+
w
2
,
3
=
12
,
d
i
s
t
[
v
1
,
v
4
]
=
d
i
s
t
[
v
1
,
v
6
]
+
w
6
,
4
=
9
,
d
i
s
t
[
v
1
,
v
5
]
=
d
i
s
t
[
v
1
,
v
6
]
+
w
6
,
5
=
4
,
d
i
s
t
[
v
1
,
v
6
]
=
w
1
,
6
=
3
,
d
i
s
t
[
v
1
,
v
1
]
=
0.
S
=
{
v
1
,
v
2
,
v
3
,
v
4
,
v
5
,
v
6
}
\begin{aligned} &dist[v_1,v_2]=dist[v_1,v_6]=5,\quad dist[v_1,v_3]=dist[v_1,v_6]+w_{6,2}+w_{2,3}=12,\\ & dist[v_1,v_4]=dist[v_1,v_6]+w_{6,4}=9, \quad dist[v_1,v_5]=dist[v_1,v_6]+w_{6,5}=4,\\ & dist[v_1,v_6]=w_{1,6}=3,\quad dist[v_1,v_1]=0.\\ & S=\{v_1,v_2,v_3,v_4,v_5,v_6\} \end{aligned}
dist[v1,v2]=dist[v1,v6]=5,dist[v1,v3]=dist[v1,v6]+w6,2+w2,3=12,dist[v1,v4]=dist[v1,v6]+w6,4=9,dist[v1,v5]=dist[v1,v6]+w6,5=4,dist[v1,v6]=w1,6=3,dist[v1,v1]=0.S={v1,v2,v3,v4,v5,v6}
此时
V
=
S
V=S
V=S, 算法结束,我们得到了起点
v
1
v_1
v1到其余各点的最短距离.
Code
function [minimum,path] = Dijkstra(w,start,terminal)
% Dijkstra算法
% 计算起始点到终点的最短路径
% w是所求图的带权连接矩阵
% start, terminal分别是路径的起点和终点
n=size(w,1);
length_node=ones(1,n)*inf;
length_node(start)=0;
father_node(start)=start;
set(1)=start;
Now_best=start;
% n为所求图的顶点个数, length_node存放到各点的最短路径,father_node表示父亲顶点用于还原路径
% 初始化时将除start以外的顶点的length_node设置为无穷大
% 数组set存放已经搜好的顶点, 初始化时只有start
while length(set)<n
Next_best=0;
k=inf;
% 遍历所有顶点, 将不再集合set中的顶点选出来
for i=1:n
flag=0;
for j=1:length(set)
if i==set(j)
flag=1;
end
end
% 判断是否有中继节点使得它们之间的距离更短, 如果有, 更新距离及顶点
if flag==0
if length_node(i)>(length_node(Now_best)+w(Now_best,i))
length_node(i)=length_node(Now_best)+w(Now_best,i);
father_node(i)=Now_best;
end
% 找到目前路径最短的顶点放入顶点集
if k>length_node(i)
k=length_node(i);
Next_best=i;
end
end
end
set=[set Next_best];
Now_best=Next_best;
end
minimum=length_node(terminal);
path(1)=terminal;
i=1;
% 按倒序结果推出最短路径
while path(i)~=start
path(i+1)=father_node(path(i));
i=i+1;
end
path(i)=start;
% 翻转得到最短路径
path=path(end:-1:1);
end
% Dijkstra_test
w=[0,7,9,inf,inf,14;
7,0,10,15,inf,inf;
9,10,0,11,inf,2;
inf,15,11,0,6,inf;
inf,inf,inf,6,0,9;
14,inf,2,inf,9,0];
start=1;terminal=5;
[minimum,path]=Dijkstra(w,start,terminal);
disp(minimum)
disp(path)
20
1 3 6 5
Appendix
%% matlab做有权无向图
% 函数graph(s,t,w): 可在s和t中的对应节点之间以w的权重创建边, 并生成一个图
s=[1,2,3,4];
t=[2,3,1,1];
w=[3,8,9,2];
G=graph(s,t,w);
figure;
plot(G,'EdgeLabel',G.Edges.Weight,'linewidth',2)
set(gca,'XTick',[],'YTick',[]);
%% 查找有权图的最短路径
s=[9 9 1 1 2 2 2 7 7 6 6 5 5 4];
t=[1 7 7 2 8 3 5 8 6 8 5 3 4 3];
w=[4 8 3 8 2 7 4 1 6 6 2 14 10 9];
G=graph(s,t,w);
figure;
plot(G,'EdgeLabel',G.Edges.Weight,'linewidth',2)
set(gca,'Xtick',[],'YTick',[]);
[P,d]=shortestpath(G,9,4); % P里面存储的是最短路径上面的节点
% 在图中高亮我们的最短路径
myplot=plot(G,'EdgeLabel',G.Edges.Weight,'linewidth',2); %首先将图赋给一个变量
highlight(myplot,P,'EdgeColor','r');
%求出任意两点的最短路径矩阵
D=distances(G)
D(1,2) % 1 -> 2的最短路径
D(9,4) % 9 -> 4的最短路径
% 找出给定范围内的所有点 nearest(G,s,d)
% 返回图形G中与节点s的距离在d之内的所有节点
[nodeIds,dist]=nearest(G,2,10)
D =
0 6 13 20 10 9 3 4 4
6 0 7 14 4 6 3 2 10
13 7 0 9 11 13 10 9 17
20 14 9 0 10 12 17 16 24
10 4 11 10 0 2 7 6 14
9 6 13 12 2 0 6 6 13
3 3 10 17 7 6 0 1 7
4 2 9 16 6 6 1 0 8
4 10 17 24 14 13 7 8 0
ans =
6
ans =
24
nodeIds =
8
7
5
1
6
3
9
dist =
2
3
4
6
6
7
10
Reference
1.《算法》课程,屈婉玲教授.
2. 知乎专栏 https://zhuanlan.zhihu.com/p/129373740
3. CSDN博客 https://blog.csdn.net/weixin_46308081/article/details/119254473