zoukankan      html  css  js  c++  java
  • [图的最短路径算法]Dijkstra, Bellman-Ford, Floyd-Warshall

    随着算法教学进度的推进,虽然关于图论的专题只开了头,讲了$DFS$和$BFS$...

    可是,万恶的计算机网络作业居然都是这样的题目,此处省略脏话1000+字,本真离散数学曾经学过的良心,默默温习一下~但是,与此同时也算是对算法的一点点预习吧~~~(你心态真好!!喂,刚刚搞完JAVA的大作业,然后就得知马上要交网络的作业啊喂!!!

    正题开始

    一. Dijkstra算法

    中文维基百科译为"戴克斯特拉"。很遗憾,这种算法不能解决权值为负的情况,可是谁叫我们一般情况下权值都是正值呢?它的主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

    下面又开始了无耻的转载了......

    1. 算法思想

    设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

    2. 算法步骤

    a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。

    b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。

    c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。

    d.重复步骤b和c直到所有顶点都包含在S中。

    3. 动画示例

    动态演示Dijkstra

    4. 代码实现

     1 const int  MAXINT = 32767;
     2 const int MAXNUM = 10;
     3 int dist[MAXNUM];
     4 int prev[MAXNUM];
     5 
     6 int A[MAXUNM][MAXNUM];
     7 
     8 void Dijkstra(int v0)
     9 {
    10     bool S[MAXNUM];                                  // 判断是否已存入该点到S集合中
    11       int n=MAXNUM;
    12     for(int i=1; i<=n; ++i)
    13     {
    14         dist[i] = A[v0][i];
    15         S[i] = false;                                // 初始都未用过该点
    16         if(dist[i] == MAXINT)    
    17               prev[i] = -1;
    18         else 
    19               prev[i] = v0;
    20      }
    21      dist[v0] = 0;
    22      S[v0] = true;   
    23     for(int i=2; i<=n; i++)
    24     {
    25          int mindist = MAXINT;
    26          int u = v0;                               // 找出当前未使用的点j的dist[j]最小值
    27          for(int j=1; j<=n; ++j)
    28             if((!S[j]) && dist[j]<mindist)
    29             {
    30                   u = j;                             // u保存当前邻接点中距离最小的点的号码 
    31                   mindist = dist[j];
    32             }
    33          S[u] = true; 
    34          for(int j=1; j<=n; j++)
    35              if((!S[j]) && A[u][j]<MAXINT)
    36              {
    37                  if(dist[u] + A[u][j] < dist[j])     //在通过新加入的u点路径找到离v0点更短的路径  
    38                  {
    39                      dist[j] = dist[u] + A[u][j];    //更新dist 
    40                      prev[j] = u;                    //记录前驱顶点 
    41                   }
    42               }
    43      }
    44 }

    5. 算法实例

    给出一个无向图G

    图

    用Dijkstra算法找出以A为起点的单源最短路径步骤如下

    steps

    (Dijkstra算法转载自华山大师兄博客: http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html)

    二、Bellman-Ford算法

    贝尔曼-福特算法,它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。

    1. 算法流程

    给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, 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到各顶点的最短路径长度。

    我个人倒是觉得有点像DFS啊,对刚刚更新的结点继续下一层的搜索、计算权值,取更小的那个作为新的权值。当每个结点在这一轮都不再更新的时候,算法结束。

    2. 算法的三部分

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

    之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。

    可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(VE).

    3. 算法示例

    4. 算法代码 

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <limits.h>
    using namespace std;
    //表示一条边
    struct Edge
    {
        int src, dest, weight;
    };
    
    //带权值的有向图
    struct Graph
    {
        // V 顶点的数量, E 边的数量
        int V, E;
    
        // 用边的集合 表示一个图
        struct Edge* edge;
    };
    
    // 创建图
    struct Graph* createGraph(int V, int E)
    {
        struct Graph* graph = (struct Graph*) malloc( sizeof(struct Graph) );
        graph->V = V;
        graph->E = E;
    
        graph->edge = (struct Edge*) malloc( graph->E * sizeof( struct Edge ) );
    
        return graph;
    }
    
    // 打印结果
    void printArr(int dist[], int n)
    {
        printf("Vertex   Distance from Source
    ");
        for (int i = 0; i < n; ++i)
            printf("%d 		 %d
    ", i, dist[i]);
    }
    
    // 获得单源最短路径,同时检测 负权回路
    void BellmanFord(struct Graph* graph, int src)
    {
        int V = graph->V;
        int E = graph->E;
        int dist[V];
    
        // 第一步初始化
        for (int i = 0; i < V; i++)
            dist[i]   = INT_MAX;
        dist[src] = 0;
    
        // 第二步:松弛操作
        for (int i = 1; i <= V-1; i++)
        {
            for (int j = 0; j < E; j++)
            {
                int u = graph->edge[j].src;
                int v = graph->edge[j].dest;
                int weight = graph->edge[j].weight;
                if (dist[u] + weight < dist[v])
                    dist[v] = dist[u] + weight;
            }
        }
    
        // 第三步: 检测负权回路.  上面的操作保证没有负权回路的存在,
        // 如果找到了更短的路径,则说明存在负权回路
        for (int i = 0; i < E; i++)
        {
            int u = graph->edge[i].src;
            int v = graph->edge[i].dest;
            int weight = graph->edge[i].weight;
            if (dist[u] + weight < dist[v])
                printf("Graph contains negative weight cycle");
        }
    
        printArr(dist, V);
        return;
    }
    
    // 测试
    int main()
    {
        /* 创建 例子中的那个图的结构 */
        int V = 5;
        int E = 8;
        struct Graph* graph = createGraph(V, E);
    
        // add edge 0-1 (or A-B in above figure)
        graph->edge[0].src = 0;
        graph->edge[0].dest = 1;
        graph->edge[0].weight = -1;
    
        // add edge 0-2 (or A-C in above figure)
        graph->edge[1].src = 0;
        graph->edge[1].dest = 2;
        graph->edge[1].weight = 4;
    
        // add edge 1-2 (or B-C in above figure)
        graph->edge[2].src = 1;
        graph->edge[2].dest = 2;
        graph->edge[2].weight = 3;
    
        // add edge 1-3 (or B-D in above figure)
        graph->edge[3].src = 1;
        graph->edge[3].dest = 3;
        graph->edge[3].weight = 2;
    
        // add edge 1-4 (or A-E in above figure)
        graph->edge[4].src = 1;
        graph->edge[4].dest = 4;
        graph->edge[4].weight = 2;
    
        // add edge 3-2 (or D-C in above figure)
        graph->edge[5].src = 3;
        graph->edge[5].dest = 2;
        graph->edge[5].weight = 5;
    
        // add edge 3-1 (or D-B in above figure)
        graph->edge[6].src = 3;
        graph->edge[6].dest = 1;
        graph->edge[6].weight = 1;
    
        // add edge 4-3 (or E-D in above figure)
        graph->edge[7].src = 4;
        graph->edge[7].dest = 3;
        graph->edge[7].weight = -3;
    
        BellmanFord(graph, 0);
    
        return 0;
    }

     (Bellman-Ford算法转载自飘过的小牛博客:http://blog.csdn.net/niushuai666/article/details/6791765)

    三、Floyd-Warshall算法

    1. 算法简介

    Floyd-Warshall算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall算法的时间复杂度为O(N3),空间复杂度为O(N2)。 

    2. 算法描述 

    1)算法思想原理: 

         Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在) 

          从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。 

    2).算法描述: 

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

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

    3).Floyd算法过程矩阵的计算----十字交叉法 

    方法:两条线,从左上角开始计算一直到右下角 如下所示

     给出矩阵,其中矩阵A是邻接矩阵,而矩阵Path记录u,v两点之间最短路径所必须经过的点 

    $A_{-1}=left[egin{array}{cccc}
    0 & 5 & infty & 7  \
     infty & 0 & 4 & 2 \
    3 & 3 &0 & 2 \
    infty & infty &1  & 0 \
    end{array} ight]$

    $Path_{-1}=left[egin{array}{cccc}
    -1 & -1&-1 & -1  \
     -1& -1 & -1 & -1 \
    -1 & -1 & -1 & -1 \
    -1 & -1 &-1 & -1 \
    end{array} ight]$

    相应计算方法如下: 

     

     

    最后A3即为所求结果

    3. 算法代码

    这种算法的代码写起来超级简单啊~

     1 typedef struct          
     2 {        
     3     char vertex[VertexNum];                                //顶点表         
     4     int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表         
     5     int n,e;                                               //图中当前的顶点数和边数         
     6 }MGraph; 
     7 
     8 void Floyd(MGraph g)
     9 {
    10    int A[MAXV][MAXV];
    11    int path[MAXV][MAXV];
    12    int i,j,k,n=g.n;
    13    for(i=0;i<n;i++)
    14       for(j=0;j<n;j++)
    15       {   
    16              A[i][j]=g.edges[i][j];
    17             path[i][j]=-1;
    18        }
    19    for(k=0;k<n;k++)
    20    { 
    21         for(i=0;i<n;i++)
    22            for(j=0;j<n;j++)
    23                if(A[i][j]>(A[i][k]+A[k][j]))
    24                {
    25                      A[i][j]=A[i][k]+A[k][j];
    26                      path[i][j]=k;
    27                 } 
    28      } 
    29 }

    算法时间复杂度:$O(n^3)$

    (Floyd-Warshall算法转载自华山大师兄博客: http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html)

  • 相关阅读:
    C语言练习之计算某年日是该年的第几天
    C语言练习之 猴子吃桃问题
    C语言练习之 求阶乘
    C语言学习(四)
    C语言学习(三)
    C语言学习(二)
    C语言学习(一)
    自定义函数汇总
    #2019121200026 最大子序列和
    #2019121000025-LGTD
  • 原文地址:https://www.cnblogs.com/godfray/p/4077146.html
Copyright © 2011-2022 走看看