0
点赞
收藏
分享

微信扫一扫

图论3 详解Floyd算法

生态人 2022-04-14 阅读 91

简介

编辑 播报

在计算机科学中,Floyd-Warshall算法是一种在具有正或负边缘权重(但没有负周期)的加权图中找到最短路径的算法。算法的单个执行将找到所有顶点对之间的最短路径的长度(加权)。 虽然它不返回路径本身的细节,但是可以通过对算法的简单修改来重建路径。 该算法的版本也可用于查找关系R的传递闭包,或(与Schulze投票系统相关)在加权图中所有顶点对之间的最宽路径。

Floyd-Warshall算法是动态规划的一个例子,并在1962年由Robert Floyd以其当前公认的形式出版。然而,它基本上与Bernard Roy在1959年先前发表的算法和1962年的Stephen Warshall中找到图形的传递闭包基本相同,并且与Kleene的算法密切相关 在1956年)用于将确定性有限自动机转换为正则表达式。算法作为三个嵌套for循环的现代公式首先由Peter Ingerman在1962年描述。

该算法也称为Floyd算法,Roy-Warshall算法,Roy-Floyd算法或WFI算法。 [2] 

核心思路

编辑 播报

路径矩阵

通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。 [3] 

从图的带权邻接矩阵A=[a(i,j)] n×n开始,迭代地进行n次更新,即由矩阵D(0)=A,按一个公式,构造出矩阵D(1);又用同样地公式由D(1)构造出D(2);……;最后又用同样的公式由D(n-1)构造出矩阵D(n)。矩阵D(n)的i行j列元素便是i号顶点到j号顶点的最短路径长度,称D(n)为图的距离矩阵,同时还可引入一个后继节点矩阵path来记录两点间的最短路径。

采用松弛技术(松弛操作),对在i和j之间的所有其他点进行一次松弛。所以时间复杂度为O(n^3);

状态转移方程

其状态转移方程如下: map[i,j]:=min{map[i,k]+map[k,j],map[i,j]};

map[i,j]表示i到j的最短距离,K是穷举i,j的断点,map[n,n]初值应该为0,或者按照题目意思来做。

当然,如果这条路没有通的话,还必须特殊处理,比如没有map[i,k]这条路。

算法过程

编辑 播报

1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。

2,对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。

把图用邻接矩阵G表示出来,如果从Vi到Vj有路可达,则G[i][j]=d,d表示该路的长度;否则G[i][j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i][j]表示从Vi到Vj需要经过的点,初始化D[i][j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i][j] = min( G[i][j], G[i][k]+G[k][j] ),如果G[i][j]的值变小,则D[i][j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。

比如,要寻找从V5到V1的路径。根据D,假如D(5,1)=3则说明从V5到V1经过V3,路径为{V5,V3,V1},如果D(5,3)=3,说明V5与V3直接相连,如果D(3,1)=1,说明V3与V1直接相连。 [4] 

时间复杂度与空间复杂度

编辑 播报

时间复杂度:O(n^3);

空间复杂度:O(n^2)

优缺点分析

编辑 播报

Floyd算法适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法,也要高于执行|V|次SPFA算法。

优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。

缺点:时间复杂度比较高,不适合计算大量数据。 [5] 

算法描述

编辑 播报

a) 初始化:D[u,v]=A[u,v]

b) For k:=1 to n

For i:=1 to n

For j:=1 to n

If D[i,j]>D[i,k]+D[k,j] Then

D[i,j]:=D[i,k]+D[k,j];

c) 算法结束:D即为所有点对的最短路径矩阵

参考代码

编辑 播报

C语言

#include<stdio.h>

#include<stdlib.h>

#define max 1000000000



int d[1000][1000],path[1000][1000];

int main()

{

    int i,j,k,m,n;

    int x,y,z;

    scanf("%d%d",&n,&m);

     

    for(i=1;i<=n;i++)

        for(j=1;j<=n;j++){

            d[i][j]=max;

            path[i][j]=j;

    }

     

    for(i=1;i<=m;i++) {

            scanf("%d%d%d",&x,&y,&z);

            d[x][y]=z;

            d[y][x]=z;

    }

     

    for(k=1;k<=n;k++)

        for(i=1;i<=n;i++)

            for(j=1;j<=n;j++) {

                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];

                }

            }

    for(i=1;i<=n;i++)

        for(j=1;j<=i;j++)

          if (i!=j) printf("%d->%d:%d\n",i,j,d[i][j]);

    int f, en;

    scanf("%d%d",&f,&en);

    while (f!=en) {

        printf("%d->",f);

        f=path[f][en];

    }

    printf("%d\n",en);

    return 0;

}

C++语言

#include<iostream>

#include<vector>

using namespace std;

const int &INF=100000000;

void floyd(vector<vector<int> > &distmap,//可被更新的邻接矩阵,更新后不能确定原有边

           vector<vector<int> > &path)//路径上到达该点的中转点

//福利:这个函数没有用除INF外的任何全局量,可以直接复制!

{

    const int &NODE=distmap.size();//用邻接矩阵的大小传递顶点个数,减少参数传递

    path.assign(NODE,vector<int>(NODE,-1));//初始化路径数组 

    for(int k=1; k!=NODE; ++k)//对于每一个中转点

        for(int i=0; i!=NODE; ++i)//枚举源点

            for(int j=0; j!=NODE; ++j)//枚举终点

                if(distmap[i][j]>distmap[i][k]+distmap[k][j])//不满足三角不等式

                {

                    distmap[i][j]=distmap[i][k]+distmap[k][j];//更新

                    path[i][j]=k;//记录路径

                }

}

void print(const int &beg,const int &end,

           const vector<vector<int> > &path)//传引用,避免拷贝,不占用内存空间

           //也可以用栈结构先进后出的特性来代替函数递归 

{

    if(path[beg][end]>=0)

    {

        print(beg,path[beg][end],path);

        print(path[beg][end],end,path);

    }

    else cout<<"->"<<end;

}

int main()

{

    int n_num,e_num,beg,end;//含义见下

    cout<<"(不处理负权回路)输入点数、边数:";

    cin>>n_num>>e_num;

    vector<vector<int> > path,

          distmap(n_num,vector<int>(n_num,INF));//默认初始化邻接矩阵

    for(int i=0,p,q; i!=e_num; ++i)

    {

        cout<<"输入第"<<i+1<<"条边的起点、终点、长度(100000000代表无穷大,不联通):";

        cin>>p>>q;

        cin>>distmap[p][q];

    }

    floyd(distmap,path);

    cout<<"计算完毕,可以开始查询,请输入出发点和终点:";

    cin>>beg>>end;

    cout<<"最短距离为"<<distmap[beg][end]<<",打印路径:"<<beg;

    print(beg,end,path);

}

Matlab源代码

function Floyd(w,router_direction,MAX)

%w为此图的距离矩阵

%router_direction为路由类型:0为前向路由;非0为回溯路由

%MAX是数据输入时的∞的实际值

len=length(w);

flag=zeros(1,len);

%根据路由类型初始化路由表

R=zeros(len,len);

for i=1:len

if router_direction==0%前向路由

R(:,i)=ones(len,1)*i;

else %回溯路由

R(i,:)=ones(len,1)*i;

end

R(i,i)=0;

end

disp('');

disp('w(0)');

dispit(w,0);

disp('R(0)');

dispit(R,1);

%处理端点有权的问题

for i=1:len

tmp=w(i,i)/2;

if tmp~=0

w(i,:)=w(i,:)+tmp;

w(:,i)=w(:,i)+tmp;

flag(i)=1;

w(i,i)=0;

end

end

%Floyd算法具体实现过程

for i=1:len

for j=1:len

if j==i || w(j,i)==MAX

continue;

end

for k=1:len

if k==i || w(j,i)==MAX

continue;

end

if w(j,i)+w(i,k)<w(j,k) %Floyd算法核心代码

w(j,k)=w(j,i)+w(i,k);

if router_direction==0%前向路由

R(j,k)=R(j,i);

else %回溯路由

R(j,k)=R(i,k);

end

end

end

end

%显示每次的计算结果

disp(['w(',num2str(i),')'])

dispit(w,0);

disp(['R(',num2str(i),')'])

dispit(R,1);

end

%中心和中点的确定

[Center,index]=min(max(w'));

disp(['中心是V',num2str(index)]);

[Middle,index]=min(sum(w'));

disp(['中点是V',num2str(index)]);

end

function dispit(x,flag)

%x:需要显示的矩阵

%flag:为0时表示显示w矩阵,非0时表示显示R矩阵

len=length(x);

s=[];

for j=1:len

if flag==0

s=[s sprintf('%5.2f\t',x(j,:))];

else

s=[s sprintf('%d\t',x(j,:))];

end

s=[s sprintf('\n')];

end

disp(s);

disp('---------------------------------------------------');

end

% 选择后按Ctrl+t取消注释号%

%

% 示例:

% a=[

% 0,100,100,1.2,9.2,100,0.5;

% 100,0,100,5,100,3.1,2;

% 100,100,0,100,100,4,1.5;

% 1.2,5,100,0,6.7,100,100;

% 9.2,100,100,6.7,0,15.6,100;

% 100,3.1,4,100,15.6,0,100;

% 0.5,2,1.5,100,100,100,0

% ];

%

% b=[

% 0,9.2,1.1,3.5,100,100;

% 1.3,0,4.7,100,7.2,100;

% 2.5,100,0,100,1.8,100;

% 100,100,5.3,0,2.4,7.5;

% 100,6.4,2.2,8.9,0,5.1;

% 7.7,100,2.7,100,2.1,0

% ];

%

% Floyd(a,1,100)

% Floyd(b,1,100)

pascal语言

program floyd;

varst,en,f:integer;

k,n,i,j,x:integer;

a:array[1..10,1..10] of integer;

path:array[1..10,1..10] of integer;

begin

readln(n);

for i:=1 to n do

begin

for j:=1 to n do

begin

read(k);

if k<>0 thena[i,j]:=k

elsea[i,j]:=maxint;

path[i,j]:=j;

end;

readln;

end;

for x:=1 to n do

for i:=1 to n do

for j:=1 to n do

if a[i,j]>a[i,x]+a[x,j] then

begin

a[i,j]:=a[i,x]+a[x,j];

path[i,j]:=path[i,x];

end;

readln(st,en);

writeln(a[st,en]);

f:=st;

while f<> en do

begin

write(f);

write('-->');

f:=path[f,en];

end;

writeln(en);

end.

java语言

//以无向图G为入口,得出任意两点之间的路径长度length[i][j],路径path[i][j][k],

//途中无连接得点距离用0表示,点自身也用0表示

public class FLOYD {

int[][] length = null;// 任意两点之间路径长度

int[][][] path = null;// 任意两点之间的路径

public FLOYD(int[][] G) {

int MAX = 100;int row = G.length;// 图G的行数

int[][] spot = new int[row][row];// 定义任意两点之间经过的点

int[] onePath = new int[row];// 记录一条路径

length = new int[row][row];

path = new int[row][row][];

for (int i = 0; i < row; i++)// 处理图两点之间的路径

for (int j = 0; j < row; j++) {

if (G[i][j] == 0)G[i][j] = MAX;// 没有路径的两个点之间的路径为默认最大

if (i == j)G[i][j] = 0;// 本身的路径长度为0

}

for (int i = 0; i < row; i++)// 初始化为任意两点之间没有路径

for (int j = 0; j < row; j++)

spot[i][j] = -1;

for (int i = 0; i < row; i++)// 假设任意两点之间的没有路径

onePath[i] = -1;

for (int v = 0; v < row; ++v)

for (int w = 0; w < row; ++w)

length[v][w] = G[v][w];

for (int u = 0; u < row; ++u)

for (int v = 0; v < row; ++v)

for (int w = 0; w < row; ++w)

if (length[v][w] > length[v][u] + length[u][w]) {

length[v][w] = length[v][u] + length[u][w];// 如果存在更短路径则取更短路径

spot[v][w] = u;// 把经过的点加入

}

for (int i = 0; i < row; i++) {// 求出所有的路径

int[] point = new int[1];

for (int j = 0; j < row; j++) {

point[0] = 0;

onePath[point[0]++] = i;

outputPath(spot, i, j, onePath, point);

path[i][j] = new int[point[0]];

for (int s = 0; s < point[0]; s++)

path[i][j][s] = onePath[s];

}

}

}

void outputPath(int[][] spot, int i, int j, int[] onePath, int[] point) {// 输出i// 到j// 的路径的实际代码,point[]记录一条路径的长度

if (i == j)return;

if (spot[i][j] == -1)

onePath[point[0]++] = j;

// System.out.print(" "+j+" ");

else {

outputPath(spot, i, spot[i][j], onePath, point);

outputPath(spot, spot[i][j], j, onePath, point);

}

}

public static void main(String[] args) {

int data[][] = {

{ 0, 27, 44, 17, 11, 27, 42, 0, 0, 0, 20, 25, 21, 21, 18, 27, 0 },// x1

{ 27, 0, 31, 27, 49, 0, 0, 0, 0, 0, 0, 0, 52, 21, 41, 0, 0 },// 1

{ 44, 31, 0, 19, 0, 27, 32, 0, 0, 0, 47, 0, 0, 0, 32, 0, 0 },// 2

{ 17, 27, 19, 0, 14, 0, 0, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0 },// 3

{ 11, 49, 0, 14, 0, 13, 20, 0, 0, 28, 15, 0, 0, 0, 15, 25, 30 },// 4

{ 27, 0, 27, 0, 13, 0, 9, 21, 0, 26, 26, 0, 0, 0, 28, 29, 0 },// 5

{ 42, 0, 32, 0, 20, 9, 0, 13, 0, 32, 0, 0, 0, 0, 0, 33, 0 },// 6

{ 0, 0, 0, 0, 0, 21, 13, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0 },// 7

{ 0, 0, 0, 0, 0, 0, 0, 19, 0, 11, 20, 0, 0, 0, 0, 33, 21 },// 8

{ 0, 0, 0, 0, 28, 26, 32, 0, 11, 0, 10, 20, 0, 0, 29, 14, 13 },// 9

{ 20, 0, 47, 30, 15, 26, 0, 0, 20, 10, 0, 18, 0, 0, 14, 9, 20 },// 10

{ 25, 0, 0, 0, 0, 0, 0, 0, 0, 20, 18, 0, 23, 0, 0, 14, 0 },// 11

{ 21, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 27, 22, 0, 0 },// 12

{ 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0 },// 13

{ 18, 41, 32, 31, 15, 28, 0, 0, 0, 29, 14, 0, 22, 0, 0, 11, 0 },// 14

{ 27, 0, 0, 0, 25, 29, 33, 0, 33, 14, 9, 14, 0, 0, 11, 0, 9 },// 15

{ 0, 0, 0, 0, 30, 0, 0, 0, 21, 13, 20, 0, 0, 0, 0, 9, 0 } // 16

};

for (int i = 0; i < data.length; i++)

for (int j = i; j < data.length; j++)

if (data[i][j] != data[j][i])return;

FLOYD test=new FLOYD(data);

for (int i = 0; i < data.length; i++)

for (int j = i; j < data[i].length; j++) {

System.out.println();

System.out.print("From " + i + " to " + j + " path is: ");

for (int k = 0; k < test.path[i][j].length; k++)

System.out.print(test.path[i][j][k] + " ");

System.out.println();

System.out.println("From " + i + " to " + j + " length :"+ test.length[i][j]);

}

}

}
举报

相关推荐

0 条评论