[图的最短路径算法]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\n");
    for (int i = 0; i < n; ++i)
        printf("%d \t\t %d\n", 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[\begin{array}{cccc}
0 & 5 & \infty & 7  \\
 \infty & 0 & 4 & 2 \\
3 & 3 &0 & 2 \\
\infty & \infty &1  & 0 \\
\end{array}\right]$

$Path_{-1}=\left[\begin{array}{cccc}
-1 & -1&-1 & -1  \\
 -1& -1 & -1 & -1 \\
-1 & -1 & -1 & -1 \\
-1 & -1 &-1 & -1 \\
\end{array}\right]$

相应计算方法如下: 

 

 

 

最后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)

 

posted @ 2014-11-05 19:37 Godfray 阅读(...) 评论(...) 编辑 收藏