最短路径算法

原文链接 http://dongzi85.blog.163.com/blog/static/8079890520089288468222/

最短路径算法

分类:

  • 无回路网络:拓扑排序法
  • 无负权网络:Dijkstra算法,Bellman-Ford算法
  • 带负权网络:Bellman-Ford算法
  • 所有点对之间的最短路径:Floyd-Warshall算法

在图中存在负权的情况下,最短路径不一定存在。可以证明:
  • [定理]:在无向连通图中,两点间的最短路径存在当且仅当不含负权圈(往死里转吧,无穷小)。
由于问题变的复杂了,我们无法找到一个像Dihijkstra算法那样时间复杂度这么低的方法,但是仍然有一个不错的方法,就是Bellman-Ford迭代算法。

Bellman Ford算法(SSSP):
基本思想:如果两点间有最短路,那么每个顶点最多经过一次。如果一个顶点经过两次,那么必定走了一个圈,如果这是个正圈,显然是不合算的;如果是负圈,则根据上述定理,不存在最短路径。
初始化d[起点]=0,d[其他点]= 无穷

for k=1 to n-1 do
for 每条边 (u,v) do
    if (d[u]<无穷) and (d[u]+w(u,v)<d[v])then d[v]=d[u]+w(u,v);

时间复杂度O(mn)。

Floyd-Warshall算法(ASSP):

求多源、无负权边的最短路。用矩阵记录图。时效性较差,时间复杂度O(V^3)。
Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理
有向图最短路径问题。
Floyd-Warshall算法的时间复杂度为O(N^3),空间复杂度为O(N^2)。
Floyd-Warshall的原理是动态规划:

考虑最短路的最优子结构性质。设d[i,j,k]是从节点i到节点j的最短路径,且只允许经过[1...k]中的节点,考察节点k,有两种情况:

  • 经过k:则d[i,j,k]=d[i,k,k-1]+d[k,j,k-1]
  • 不经过k:则d[i,j,k]=d[i,j,k-1]
于是,d[i,j,k]=min{d[i,k,k-1]+d[k,j,k-1], d[i,j,k-1]}

for k=1 to n do
   for i=1 to ndo
      for j=1 to ndo
        if (d[i,k]<无穷) and(d[k,j]<无穷)and
d[i,k]+d[k,j]<d[i,j]then
            d[i,j]=d[i,k]+d[k,j];

时间复杂度为O(n3).

最短路算法总结:
  • 标号修正算法(Bellman-Ford):算法迭代式的,标号都是临时的,算法思想是不断逼近最优解,最后一步达到最优解。
  • 标号设定算法(Dijkstra):算法执行过程就是不断把临时标号设定成为永久标号的过程。
  • 标号设定算法时间复杂度低,但适用范围小,例如Dijkstr算法就不适用于含负权边的情况。标号修正算法适用范围相对较广,但时间复杂度较高。

 

附模板(原文链接 http://www.cnblogs.com/touchsunlight/archive/2010/08/09/1795816.html)

完全最短路径(Floyd算法):[复杂度:O(n^3)]

// 矩阵mat初始值INT_MAX

// 结果 mat[i][j] 为点i到j的最短路径

// mat[i][j] == INT_MAX时候为不可到达

 

void Floyd(int n)

{ 

    int i, j, k;

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

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

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

                if (mat[i][k] != INT_MAX &&

                    mat[k][j] != INT_MAX &&

                    mat[i][k]+mat[k][j] < mat[i][j])

                        mat[i][j] = mat[i][k] + mat[k][j];

}

单源最短路径Dijkstra算法:

// mat初始值为INT_MAX,即不可到达

// s表示起始点,p重点,n节点个数,返回s到p的最短路径

// 当返回结果为INT_MAX时,表示不可达

// 结果dis为第s点到其他点的最短路径

 

int dis[MAXN];

bool flag[MAXN];

int mat[MAXN][MAXN];

 

void Dijkstra(int s, int p, int n)

{

    int i, j;

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

        dis[i] = mat[s][i], flag[i] = false;

    flag[s] = true, dis[s] = 0;

 

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

    {

        int k = s, t = INT_MAX;

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

            if (!flag[j] && dis[j] < t)

                k = j, t = dis[j];

        flag[k] = true;

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

            if (!flag[j] && mat[k][j] != INT_MAX

                && dis[k] + mat[k][j] < dis[j])

                    dis[j] = dis[k] + mat[k][j];

    }

}

Bellman-Ford算法:

适用范围:

  1. 单源最短路径(从源点s到其它所有顶点v);
  2. 有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);
  3. 边权可正可负(如有负权回路输出错误提示);
  4. 差分约束系统;

算法描述:

  1. 对每条边进行|V|-1次Relax操作;
  2. 如果存在(u,v)∈E使得dis[u]+w<dis[v],则存在负权回路;否则dis[v]即为s到v的最短距离,pre[v]为前驱。
For i:=1 to |V|-1 do //v为顶点数

  For 每条边(u,v)∈E do  //对每条边进行遍历

    Relax(u,v,w);

For 每条边(u,v)∈E do

  If dis[u]+w<dis[v] Then Exit(False)

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值