数据结构(五)图---最短路径(弗洛伊德算法)

一:定义

弗洛伊德算法是用来求所有顶点到所有顶点的时间复杂度。
虽然我们可以直接对每个顶点通过迪杰斯特拉算法求得所有的顶点到所有顶点的时间复杂度,时间复杂度为O(n*3),但是弗洛伊德算法更加简洁优雅

二:弗洛伊德的使用介绍

若是求一个顶点到其他顶点的最短距离,例如迪杰斯特拉算法,我们的距离数组和路径数组使用一维即可,但是我们这里是获取所有顶点到其余顶点的最短距离,所以我们对于数组和路径都需要使用二维数组来表示

下面我们使用一个有三个顶点的图来进行讲解:

(1)我们先定义两个二维数组D0[3][3]和P0[3][3]

D0表示顶点到顶点的最短路径权值和的矩阵。
P0表示对于顶点的最小路径前驱矩阵
将D0初始化为他的初始的图的邻接矩阵
将P0初始化为图中所示每行从0-n

(2)处理两个数组

上面的公式是以v0作为中转点,实际上我们可以使用所有邻接点作为中转点,所以我们程序使用的是下面的转化公式

注意(重点):

D1数组是我们获取的最短路径,我们直接使用顶点对应的行即可获得所有的从该顶点出发的到其他顶点的最短路径权重和
P1数组是我们获取的前驱结点,我们使用的不是顶点所对应的行,而是其对应的列,这个才是我们需要的路径

上面只是使用了一个简单的图来讲解,对于复杂的图我们依旧可以使用它

初始化

处理后

三:弗洛伊德基本思想

弗洛伊德算法定义了两个二维矩阵:
矩阵D记录顶点间的最小路径
  例如D[0][3]= 10,说明顶点0 到 3 的最短路径为10;
矩阵P记录顶点间最小路径中的中转点
  例如P[0][3]= 1 说明,0 到 3的最短路径轨迹为:0 -> 1 -> 3。
它通过3重循环,k为中转点,v为起点,w为终点,循环比较D0[v][w] 和 D0[v][k] + D0[k][w] 最小值,如果D0[v][k] + D0[k][w] 为更小值,则把D0[v][k] + D0[k][w] 覆盖保存在D1[v][w]中。

核心思想是:

D1[v][w] = min{D0[v][k] + D0[k][w],D0[v][w]}

其中D0代表原来未更新前的数据,D1表示我们修改更新后的新的数据

四:代码实现

(一)结构定义

//邻接矩阵结构
typedef struct
{
    VertexType vers[MAXVEX];    //顶点表
    EdgeType arc[MAXVEX][MAXVEX];    //邻接矩阵,可看作边表
    int numVertexes, numEdges;    //图中当前的顶点数和边数
}MGraph;

(二)弗洛伊德算法

    //使用弗洛伊德核心算法,三层循环求解
    for (k = 0; k < G.numVertexes;k++)
    {
        for (i = 0; i < G.numVertexes;i++)
        {
            for (j = 0; j < G.numVertexes;j++)
            {
                if ((*dist)[i][j]>((*dist)[i][k]+(*dist)[k][j])&&i!=j)  //i!=j使不更新中间自己到自己的数据和路径
                {
                    //将权值和更新,路径也变为中转点
                    (*dist)[i][j] = (*dist)[i][k] + (*dist)[k][j];
                    (*path)[i][j] = (*path)[i][k];
                }
            }
        }
    }

(三)打印最短路径

void ShowDistAndPath(Path P, Dist D,int n)
{
    int i, j;
    printf("Printf Dist:\n");
    for (i = 0; i < n;i++)
    {
        for (j = 0; j < n; j++)
        {
            if (i==j)
                printf("    0");    //需要将我们的无穷转换一下再显示
            else
                printf("%5d", D[i][j]);
        }
        printf("\n");
    }

    printf("Printf Path:\n");
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
            printf("%5d", P[i][j]);
        printf("\n");
    }
}

ShowDistAndPath

五:全部代码实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "queue.h"

#define MAXVEX 100    //最大顶点数
#define INFINITY 65535    //用0表示∞

typedef char VertexType;    //顶点类型,字符型A,B,C,D...
typedef int EdgeType;    //边上权值类型10,15,...

//邻接矩阵结构
typedef struct
{
    VertexType vers[MAXVEX];    //顶点表
    EdgeType arc[MAXVEX][MAXVEX];    //邻接矩阵,可看作边表
    int numVertexes, numEdges;    //图中当前的顶点数和边数
}MGraph;

typedef int Dist[MAXVEX][MAXVEX];    //存放各个顶点到其余顶点的最短路径权值和
typedef int Path[MAXVEX][MAXVEX];    //存放各个顶点到其余顶点前驱顶点位置

//创建邻接矩阵
void CreateMGraph(MGraph* G);
//显示邻接矩阵
void showGraph(MGraph G);

void Floyd(MGraph G,Path* path,Dist* dist);
void ShowDistAndPath(Path P, Dist D,int n);

void Floyd(MGraph G, Path* path, Dist* dist)
{
    int i,j,k;

    //初始化path和dist
    for (i = 0; i < G.numVertexes;i++)
    {
        for (j = 0; j < G.numVertexes;j++)
        {
            (*dist)[i][j] = G.arc[i][j];
            (*path)[i][j] = j;    //初始化为这个的一个好处就是自己到自己的路径就是自己,我们不用修改
        }
    }

    //使用弗洛伊德核心算法,三层循环求解
    for (k = 0; k < G.numVertexes;k++)
    {
        for (i = 0; i < G.numVertexes;i++)
        {
            for (j = 0; j < G.numVertexes;j++)
            {
                if ((*dist)[i][j]>((*dist)[i][k]+(*dist)[k][j])&&i!=j)
                {
                    //将权值和更新,路径也变为中转点
                    (*dist)[i][j] = (*dist)[i][k] + (*dist)[k][j];
                    (*path)[i][j] = (*path)[i][k];
                }
            }
        }
    }
}

void ShowDistAndPath(Path P, Dist D,int n)
{
    int i, j;
    printf("Printf Dist:\n");
    for (i = 0; i < n;i++)
    {
        for (j = 0; j < n; j++)
        {
            if (i==j)
                printf("    0");    //需要将我们的无穷转换一下再显示
            else
                printf("%5d", D[i][j]);
        }
        printf("\n");
    }

    printf("Printf Path:\n");
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
            printf("%5d", P[i][j]);
        printf("\n");
    }
}

int main()
{
    MGraph MG;
    CreateMGraph(&MG);
    showGraph(MG);
    Path path;
    Dist dist;
    Floyd(MG, &path, &dist);
    ShowDistAndPath(path, dist, MG.numVertexes);
    system("pause");
    return 0;
}

//生成邻接矩阵
void CreateMGraph(MGraph* G)
{
    int i, j, k, w;
    G->numVertexes = 9;
    G->numEdges = 16;
    //读入顶点信息
    G->vers[0] = ‘A‘;
    G->vers[1] = ‘B‘;
    G->vers[2] = ‘C‘;
    G->vers[3] = ‘D‘;
    G->vers[4] = ‘E‘;
    G->vers[5] = ‘F‘;
    G->vers[6] = ‘G‘;
    G->vers[7] = ‘H‘;
    G->vers[8] = ‘I‘;

    //getchar();    //可以获取回车符
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[i][j] = INFINITY;    //邻接矩阵初始化

    //创建了有向邻接矩阵
    G->arc[0][1] = 1;
    G->arc[0][2] = 5;
    G->arc[1][2] = 3;
    G->arc[1][3] = 7;
    G->arc[1][4] = 5;
    G->arc[2][4] = 1;
    G->arc[2][5] = 7;
    G->arc[3][4] = 2;
    G->arc[3][6] = 3;
    G->arc[4][5] = 3;
    G->arc[4][6] = 6;
    G->arc[4][7] = 9;
    G->arc[5][7] = 5;
    G->arc[6][7] = 2;
    G->arc[6][8] = 7;
    G->arc[7][8] = 4;

    for (i = 0; i < G->numVertexes;i++)
        for (k = i; k < G->numVertexes;k++)
            G->arc[k][i] = G->arc[i][k];
}

//显示邻接矩阵
void showGraph(MGraph G)
{
    for (int i = 0; i < G.numVertexes; i++)
    {
        for (int j = 0; j < G.numVertexes; j++)
        {
            if (G.arc[i][j] != INFINITY)
                printf("%5d", G.arc[i][j]);
            else
                printf("    0");
        }
        printf("\n");
    }
}

六:循环分析

问:可不可以先循环i和j,然后把k放到最内层呢?

答案是不行的,如果打乱了i、j、k的顺序,则程序无法得出正确的结果。

可以把k想象成一个阶段,即k为中转点时,枚举i、j,通过k的变动不停地松弛i、j之间的最短路。因为i、j可以重复遍历,但k不能。如果k在内层循环,程序无法进行多次的松弛操作,也就是程序出错的原因。
我们可以认为,我们每一次的整个数组的变化都是建立在同一个中转k值基础上才能够得到正确的数据,我们每次更新完整个数组后才可以去变化k值,去重新更新一次新的,正确的数组
当我们将k放入内层,数组的每次内部更新变为动态了,我们不确定那些是正确的最短路径,因为某些数据没有得到正确的结果,就被拿到下一次继续使用了

错误实验:

  for (i = 0; i < G.numVertexes;i++)
    {
        for (j = 0; j < G.numVertexes;j++)
        {
            for (k = 0; k < G.numVertexes; k++)
            {
                if ((*dist)[i][j]>((*dist)[i][k] + (*dist)[k][j]) && i != j)
                {
                    //将权值和更新,路径也变为中转点
                    (*dist)[i][j] = (*dist)[i][k] + (*dist)[k][j];
                    (*path)[i][j] = (*path)[i][k];
                }
            }
        }
    }

七:性能分析

Floyd算法适用于APSP(All Pairs Shortest Paths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。
此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法,也要高于执行|V|次SPFA算法。
优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
缺点:时间复杂度比较高O(n*3),不适合计算大量数据。

补充:

无论是迪杰斯特拉算法还是弗洛伊德算法,对于有向图,无向图都是可以使用的。
另外我们的最短路径一般都是针对有环图,无环图使用拓扑排序可以获得

原文地址:https://www.cnblogs.com/ssyfj/p/9495960.html

时间: 2024-07-29 10:04:51

数据结构(五)图---最短路径(弗洛伊德算法)的相关文章

最短路径--弗洛伊德算法[求任意一对顶点间的最短路径]

转自大神:https://www.cnblogs.com/wangyuliang/p/9216365.html !!!!!!!!!!!!!!!!!!!!!!!!!注意             迪杰斯特拉算法和弗洛伊德算法(求最短路径) 都是有向图!!!!单边的 暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有4个城市8条公路,公路上的数字表示这条公路的长短.请注意这些公路是单向

图(最短路径算法————迪杰斯特拉算法和弗洛伊德算法).RP

文转:http://blog.csdn.net/zxq2574043697/article/details/9451887 一: 最短路径算法 1. 迪杰斯特拉算法 2. 弗洛伊德算法 二: 1. 迪杰斯特拉算法 求从源点到其余各点的最短路径 依最短路径的长度递增的次序求得各条路径 路径长度最短的最短路径的特点: 在这条路径上,必定只含一条弧,并且这条弧的权值最小. 下一条路径长度次短的最短路径的特点: 它只可能有两种情况:或是直接从源点到该点(只含一条弧):或者是从源点经过顶点v1,再到达该顶

[从今天开始修炼数据结构]图的最短路径 —— 迪杰斯特拉算法和弗洛伊德算法的详解与Java实现

在网图和非网图中,最短路径的含义不同.非网图中边上没有权值,所谓的最短路径,其实就是两顶点之间经过的边数最少的路径:而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,我们称路径上第一个顶点是源点,最后一个顶点是终点. 我们讲解两种求最短路径的算法.第一种,从某个源点到其余各顶点的最短路径问题. 1,迪杰斯特拉(Dijkstra)算法 迪杰斯特拉算法是一个按路径长度递增的次序产生最短路径的算法,每次找到一个距离V0最短的点,不断将这个点的邻接点加入判断,更新新加入的点到V0的距

Floyd(弗洛伊德算法)---每对顶点的最短路径---《数据结构》严蔚敏

// exam1.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #include <stack> using namespace std; #define MAXVEX 20 #define INT_MAX 10000 typedef struct ArcNode { int adj; void* info; }ArcNode; typedef ArcNode AdjMat[MAX

图的基础算法(大话数据结构笔记)

概述 线性表的每个元素有线性关系,每个数据元素只有一个直接前去和一个直接后继.树的数据元素之间有着明细那的层次关系,并且每层上的数据元素可能和下一层中多个元素相关,但只能和上一层中一个元素相关.这和一对父母可以有很多孩子,但每个孩子却只能有一对父母是一个道理.可现实中,人与人之间关系复杂,不是简单一对一,一对多的关系.这种复杂关系更适合用图来表示.在图结构中,节点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关.如下图所示:无向边:Edge (vi,vj)有向边:也叫弧,Arc. <v

数据结构与算法系列研究七——图、prim算法、dijkstra算法

图.prim算法.dijkstra算法 1. 图的定义 图(Graph)可以简单表示为G=<V, E>,其中V称为顶点(vertex)集合,E称为边(edge)集合.图论中的图(graph)表示的是顶点之间的邻接关系. (1) 无向图(undirect graph)      E中的每条边不带方向,称为无向图.(2) 有向图(direct graph)      E中的每条边具有方向,称为有向图.(3) 混合图       E中的一些边不带方向, 另一些边带有方向.(4) 图的阶      指

两点之间最短路径:弗洛伊德算法

弗洛伊德算法是计算无向有权图中两点间最短路径的算法,复杂度为O(n^3).其思路是将两点间距离分为过(指定的)第三点或是不过,然后取它们的最小值,如此循环就可以得到两点之间真正的最小值. void floyd() { for (int k = 0; k < n; ++k) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { //在当前i到j经过k点的路径与直连的路径中选最短 matrix[i][j] = min(ma

最短路径之弗洛伊德算法(Floyd)

Floyd算法又称为插点法,是一种用于寻找给定的加权图中多源点之间最短路径的算法. 路径矩阵 通过一个图的权值矩阵求出它的每两点间的最短路径矩阵. 从图的带权邻接矩阵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)为图的距离矩阵,同时还可引入一个后

数据结构:单源最短路径--Dijkstra算法

Dijkstra算法 单源最短路径 给定一带权图,图中每条边的权值是非负的,代表着两顶点之间的距离.指定图中的一顶点为源点,找出源点到其它顶点的最短路径和其长度的问题,即是单源最短路径问题. Dijkstra算法 求解单源最短路径问题的常用方法是Dijkstra(迪杰斯特拉)算法.该算法使用的是贪心策略:每次都找出剩余顶点中与源点距离最近的一个顶点. 算法思想 带权图G=<V,E>,令S为已确定了最短路径顶点的集合,则可用V-S表示剩余未确定最短路径顶点的集合.假设V0是源点,则初始 S={V