图的最短路径算法及matlab实现(Dijkstra算法、Floyd算法、Bellman-Ford算法、Johnson 算法)

图的最短路径算法

Dijkstra算法

Dijkstra算法研究的是从初始点到其他任一结点的最短路径,即单源最短路径问题,其对图的要求是不存在负权值的边。

Dijkstra算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

举例说明:

已知图的邻接矩阵为
[ 0 50 ∞ 40 25 10 50 0 15 20 ∞ 25 ∞ 15 0 10 20 ∞ 40 20 10 0 10 25 25 ∞ 20 10 0 55 10 25 ∞ 25 55 0 ] \left[\begin{array}{cccccc} {0} & {50} & {\infty} & {40} & {25} & {10} \\ {50} & {0} & {15} & {20} & {\infty} & {25} \\ {\infty} & {15} & {0} & {10} & {20} & {\infty} \\ {40} & {20} & {10} & {0} & {10} & {25} \\ {25} & {\infty} & {20} & {10} & {0} & {55} \\ {10} & {25} & {\infty} & {25} & {55} & {0} \end{array}\right] 050402510500152025150102040201001025252010055102525550
行向量 pb 、 index1、 index2 、d 分别用来存放 P 标号信息、标号顶点顺序、标号顶点索引、最短通路的值,其中
p b ( i ) = { 1  当i顶点已标号  0  当i顶点未标号  p b(i)=\left\{\begin{array}{ll} {1} & { \text { 当i顶点已标号 } } \\ {0} & {\text { 当i顶点未标号 } } \end{array}\right. pb(i)={ 10 i顶点已标号  i顶点未标号 
$index2(i) $存放始点到第i 点最短通路中第i 顶点前一顶点的序号

d(i)存放由始点到第i 点最短路的长度。

clc,clear
a=zeros(6);
a(1,2)=50;a(1,4)=40;a(1,5)=25;a(1,6)=10;               
a(2,3)=15;a(2,4)=20;a(2,6)=25;
a(3,4)=10;a(3,5)=20;
a(4,5)=10;a(4,6)=25;
a(5,6)=55;
a=a+a'  ;                                                
a(find(a==0))=inf; %将a=0的数全部替换为无强大               
pb(1:length(a))=0;pb(1)=1;  %当一个点已经求出到原点的最短距离时,其下标i对应的pb(i)赋1
index1=1; %存放存入S集合的顺序
index2=ones(1,length(a)); %存放始点到第i点最短通路中第i顶点前一顶点的序号
d(1:length(a))=inf;d(1)=0;  %存放由始点到第i点最短通路的值
temp=1;  %temp表示c1,算c1到其它点的最短路。
while sum(pb)<length(a)  %看是否所有的点都标记为P标号
tb=find(pb==0); %找到标号为0的所有点,即找到还没有存入S的点
d(tb)=min(d(tb),d(temp)+a(temp,tb));%计算标号为0的点的最短路,或者是从原点直接到这个点,又或者是原点经过r1,间接到达这个点
tmpb=find(d(tb)==min(d(tb)));  %求d[tb]序列最小值的下标
temp=tb(tmpb(1));%可能有多条路径同时到达最小值,却其中一个,temp也从原点变为下一个点
pb(temp)=1;%找到最小路径的表对应的pb(i)=1
index1=[index1,temp];  %存放存入S集合的顺序
temp2=find(d(index1)==d(temp)-a(temp,index1));
index2(temp)=index1(temp2(1)); %记录标号索引
end
d, index1, index2

运算结果为:


d =

     0    35    45    35    25    10


index1 =

     1     6     5     2     4     3


index2 =

     1     6     5     6     1     1

Floyd算法

弗洛伊德算法是解决图中任意两点间的最短路径的一种算法,可以正确处理有向图的最短路径问题。

Floyd算法要求不可存在负权回路。在Floyd算法中一般有两个矩阵,一个距离矩阵D,一个路由矩阵R,其中距离矩阵用于存储任意两点之间的最短距离,而路由矩阵则记录任意两点之间的最短路径信息。

其思想是:任意两点间的路径存在中转和不中转两种情况,如果可以从一个点进行中转,就进行比较从这个点中转和不中转的距离,存储距离小的情况,并更新距离矩阵和路由矩阵

从算法思想中可以发现我们要遍历n个中转点,在每个中转点进行操作的时候,需要对任意两点之间的距离进行遍历。那么算法就应该有三重循环,第一重循环是遍历中转点,第二重和第三重循环是遍历任意两个点之间的距离。而更新距离矩阵的条件就是
D ( i , K ) + D ( K , j ) < D ( i , j ) D(i, K)+D(K, j)<D(i, j) D(i,K)+D(K,j)<D(i,j)
更新为: D ( i , j ) = D ( i , K ) + D ( K , j ) D(i,j)=D(i, K)+D(K, j) D(i,j)=D(i,K)+D(K,j)

这个时候就需要更新我们的路由矩阵: R ( i , j ) = R ( i , K ) R(i,j)=R(i,K) R(i,j)=R(i,K)

路由矩阵很好理解,比如最开始是 R ( 4 , 3 ) = 3 R(4,3) = 3 R(4,3)=3,表示V4到V3一步就可以到达V3,如果现在可以从V2中转到达,那么 R ( 4 , 3 ) = R ( 4 , 2 ) = 2 R(4,3) = R(4,2) =2 R(4,3)=R(4,2)=2,表示V4->V3​要先经过V2才能到达。

因此,我们就可以给出代码:

function [D,path,min1,path1]=floyd(a,start,terminal)
D=a;n=size(D,1);path=zeros(n,n);
for i=1:n
    for j=1:n
        if D(i,j)~=inf
            path(i,j)=j;
        end
    end
end
for k=1:n
    for i=1:n
        for j=1:n
            if D(i,k)+D(k,j)<D(i,j)
                D(i,j)=D(i,k)+D(k,j);
                path(i,j)=path(i,k);
            end
        end
    end
end
if nargin==3
    min1=D(start,terminal);
    m(1)=start;
    i=1;
    path1=[ ]; 
    while path(m(i),terminal)~=terminal
        k=i+1;
        m(k)=path(m(i),terminal);
        i=i+1;
    end
    m(i+1)=terminal;
    path1=m;
end

依然使用刚才的矩阵:

a = [0,50,inf,40,25,10;
    50,0,15,20,inf,25;
    inf,15,0,10,20,inf;
    40,20,10,0,10,25;
    25,inf,20,10,0,55;
    10,25,inf,25,55,0];
%不相连的顶点间距离设为无穷远
[D,path]=floyd(a)

可获得结果为:

D =

     0    35    45    35    25    10
    35     0    15    20    30    25
    45    15     0    10    20    35
    35    20    10     0    10    25
    25    30    20    10     0    35
    10    25    35    25    35     0


path =

     1     6     5     5     5     6
     6     2     3     4     4     6
     5     2     3     4     5     4
     5     2     3     4     5     6
     1     4     3     4     5     1
     1     2     4     4     1     6

Bellman-Ford算法

Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。此时,Bellman-Ford算法就是一种可行的方法。

给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为inf, Distant[s]为0;

以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;

为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。

Bellman-Ford算法可以大致分为三个部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(e(u,v)),判断是否存在这样情况:
d ( v ) > d ( u ) + w ( u , v ) d(v) > d (u) + w(u,v) dv>d(u)+w(u,v)
若存在,则返回错误,表示途中存在从源点可达的权为负的回路。

以下给出代码实现:

function Bellman_Ford(d,n,s)
%d为已知图的邻接矩阵,n为顶点数(各顶点标号为1,2,...,n),s为源点标号
for i=1:n %初始化dist,pre
    dist(i)=inf; %dist(i)为s,i之间的最短路的长度
    pre(i)=NaN; %pre(i)为s到i的最短路上i的前一个顶点
end
dist(s)=0;
for k=1:n-1
    for i=1:n 
        for j=1:n
            if d(i,j)~=inf
                if dist(j)>dist(i)+d(i,j)
                    dist(j)=dist(i)+d(i,j);
                    pre(j)=i;
                end
            end
        end
    end
end
for i=1:n
    for j=1:n
        if d(i,j)~=inf
            if dist(i)+d(i,j)<dist(j)%判断有无负权回路
                error('Negetive WeightCircut');
            end
        end
    end
end
dist
pre
end
clc;clear
w=[0 -2 1 8 inf inf inf inf;2 0 inf 6 1 inf inf inf;1 inf 0 7 inf inf 9 inf;...
      8 6 7 0 5 1 2 inf;inf 1 inf 5 0 3 inf 9;inf inf inf 1 3 0 4 6;...
      inf inf 9 2 inf 4 0 3;inf inf inf inf 9 6 3 0];
i=1;m=8;

Bellman_Ford(w,m,i)

输出结果为:

dist =

     0    -2     1     3    -1     2     5     8


pre =

   NaN     1     1     6     2     5     4     5

如果在其中添加负权回路

clc;clear
w=[0 -2 1 -8 inf inf inf inf;2 0 inf 6 1 inf inf inf;1 inf 0 7 inf inf 9 inf;
      8 6 7 0 5 1 2 inf;inf 1 inf 5 0 3 inf 9;inf inf inf 1 3 0 4 6;
      inf inf 9 2 inf 4 0 3;inf inf inf inf 9 6 3 0];
i=1;m=8;

Bellman_Ford(w,m,i)

输出为

错误使用 Bellman_Ford (line 24)
Negetive WeightCircut

Johnson 算法

Johnson算法主要用于求稀疏图上的全源最短路径,可以处理存在负权的图,其主体思想是利用重赋权值的方法把一个带负权的图转化为权值非负的图,然后再利用N次Dijkstra算法求出全源最短路径。

其中,对图的权值进行重赋的过程是:

在图G中增加一个新结点S,并让其与其他结点相连,形成一幅新图G’,对G’进行Bellman-Ford算法计算从S到各结点的最短路径h,删除结点S,然后根据新权重确定公式:
w ′ ( u , v ) = w ( u , v ) + h ( u ) − h ( v ) w'(u,v)=w(u,v)+h(u)-h(v) w(u,v)=w(u,v)+h(u)h(v)
对原图的权重进行修改,使得权重都为正。

然后进行n次Dijkstra算法即可。

猜你喜欢

转载自blog.csdn.net/wmhsjtu/article/details/104148954