Dijkstra算法

简介

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\} viV{s}的最短路径.

  1. 初始点集合 S = { s } S=\{s\} S={s}

  2. 计算初始点 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],viVS;

  3. 选出满足距离最小 m i n v k ∈ V d i s t [ s , v k ] min_{v_k\in V}dist[s,v_k] minvkVdist[s,vk]的点 v k v_k vk, 并将该点加入到集合 S S S中, 即 S ∪ v k → S S\cup{v_k}\rightarrow S SvkS,更新 V − S V-S VS中各顶点的 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,viVS;

  4. 返回步骤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} Ss
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 forviVSdo
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 whileVS=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 findminvkVdist[s,vk]fromthesetVS
7: S ← S ∪ { v k } S\leftarrow S\cup\{v_k\} SS{vk}
8: f o r v k ∈ V − S d o for \quad v_k \in V-S \quad do forvkVSdo
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 VS=是否成立, 显然不是,因为
V − S = { v 2 , v 3 , v 4 , v 5 , v 6 } . V-S=\{v_2,v_3,v_4,v_5,v_6\}. VS={v2,v3,v4,v5,v6}.
则进行第6步, 选出经过第一个for循环之后, 集合 V − S V-S VS中相对于其他点而言到 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 v1v6v2, 两条路线都可以的话我们需要选择距离相对短的走法, 而
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, VS={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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值