【基础算法】最短路径: Floyd, Dijkstra, Bellman

简单起见(这回只写伪代码好了), 对于图的定义如下:

  • node index = {1,2,...,n}
  • e[u,v] = distance of edge(u,v); if (u,v) is not an edge, e[u,v]=INF
  • 令N=点的数量, M=边的数量

任意两点最短路径: Floyd-Warshall

Floyd算法解决的问题是: 对于任意两个节点s(source)和t(target), 求s到达t的最短路径.
Floyd算法的思想是动态规划:
  • 定义d(i,j,k)为点i到点j之间, 只允许借道节点1…k的最短路径
  • 初始化: d(i,j,0)=e[i,j](即i到j之间不经由其他任何中转节点的最短路径)
  • 更新dij的公式就是: d(i,j,k)=min( d(i,j,k-1), d(i,k-1,k-1)+e[k,j])
  • 更新n次

每次更新dij的意思就是: 现在从i到j可以经过节点k了, 那么看一下ij之间从k这个点经过的话(i → k → j 这条路)能不能缩短dij.
i到j最短路径最终就是: d(i,j,n) 即i到j的路程可以经过1~n中的任何中转节点.

伪代码
for all [i,j]: // initialize   
    d[i,j] = e[i,j]    
for k = 1 ~ n: // relax for k times:    
    for all [i,j]:   
        d[i,j] = min(d[i,j], d[i,k] + e[k,j])
完整代码
    void Floyd(int[][] e, int n) {
        // 记录任意两点间的最短距离
        int[][] d = Arrays.copyOf(e, e.length);
        // 记录最短距离路径
        int[][] path = new int[n + 1][n + 1];
        // 三重循环
        // 选中的中间值
        for (int k = 1; k <= n; k++) {
            // 数组横坐标
            for (int i = 1; i <= n; i++) {
                // 数组纵坐标
                for (int j = 1; j <= n; j++) {
                    // 如果以k中间点为中间点检测到路径更短
                    if (d[i][j] > d[i][k] + e[k][j]) {
                        // 更新路径值
                        d[i][j] = d[i][k] + e[k][j];
                        // 更新要经过的中间点
                        path[i][j] = k; 
                    }
                }
            }
        }
    }

核心代码只有最后三行… 运行结束后d[i,j]就保存着任意i和j之间的最短路径长度.
程序主循环n次, 每次要处理遍历所有的ij组合, 所以复杂度是O(N^3).

单源最短路径: Dijkstra

Dijkstra算法解决的问题是: 没有负权边的情况下, 从源节点s到其他任意节点t的路径长度.

维护一个dist数组, dist[i]就表示(目前为止) si的最短距离.
对于每个元素, 标记是否其dist是否已经确定不再更改(或者说维护两个集合: 一个集合的dist确定, 另一个未确定).

Dijkstra算法是一种贪心策略:

每次在未确定最短路径的节点里挑选距离s最近的那个点, 把这个点标记为已经确定dist, 然后对从这个点出发的边进行松弛.
为了标记每个点, 这里用一个bool数组表示: determined[i]true表示idist已经是最短路程, 为false表示还不确定.

算法如下:

  • 初始化dist[i] = e[s,i], determined[i]全为false
  • 在dist未确定的元素里(determined[i]==false)寻找一个dist最小的节点u:
  • 标记u的dist已经确定determined[i]=true
  • 用u的所有出边进行松弛: si如果经过(u,i)这条边会不会变近? dist[i] = min(dist[i], dist[u]+e[u,i])
  • 重复循环直到所有的点都确定dist(or 重复N遍即可: 每次只会确定一个新的节点的距离)
伪代码
for i in 1~n:   
    dist[i] = e[s,i]   
    determined[i] = false   
loop N times:    
    u = argmin(dist[i]) among all i that determined[i]==false   
    determined[u] = true // determine one node at each loop     
    for v such that e[u,v]<INF:   
        dist[v] = min( dist[v], dist[u]+e[u,v] )
完整代码
    int[] Dijkstra(int e[][], int N) {
        int min, pos = 1;
        int[] dist = new int[N + 1];
        boolean[] determined = new boolean[N + 1];
        // 初始化所有节点的dist[]数组为其到源点的距离
        for (int i = 1; i <= N; i++) {
            dist[i] = e[1][i];
            determined[i] = false;
        }
        determined[1] = true;

        for (int i = 1; i <= N; i++) {
            // 在dist未确定的元素里(determined[i]==false)寻找一个dist最小的节点u
            min = Integer.MAX_VALUE;
            for (int j = 1; j <= N; j++) {
                // 找到一个距离源点最近的节点
                if (!determined[j] && dist[j] < min) {
                    pos = j;
                    min = dist[j];
                }
            }
            // 表示源点到所有其他节点都不可达
            if (min == Integer.MAX_VALUE) {
                break;
            }
            // 标记pos节点为已访问的节点
            determined[pos] = true;

            for (int j = 1; j <= N; j++) {
                // 如果i节点经由pos节点到达j节点的距离小于i节点直接到j节点的距离(即i->pos->j的距离小于i->j的距离),则更新j节点的dist[j]值
                if (!determined[j] && dist[pos] + e[pos][j] < dist[j]) {
                    dist[j] = dist[pos] + e[pos][j];
                }
            }
        }
        return dist;
    }

以上代码的复杂度为O(N^2), 不过如果用堆来优化寻找最近的u的距离, 复杂度可以变得更低.

有负权边的单源最短路径: Bellman-Ford

Dijkstra算法的缺点在于不能处理边长为负数的情况, 而这就是Bellman-Ford算法解决的.

Bellman算法也是一种动态规划(动态规划这个东西就是Bellman提出来的):
  • 定义d(i,k)为源点si最多经过k条边的最短距离
  • 初始化: d(i,1)=e[s,i]
  • 每次更新di的公式: for all (u,v): d(v,k)=min( d(v,k-1), d(u,k-1)+e[u,v] )
  • 更新n-1次

为什么是更新n-1次? 因为s到i的最短路径至多只有n-1条边(即s到i的路径经过了所有n个点).
注意每次更新, 需要把所有的边试一遍: 看看用每条边(u,v)能不能松弛dv.

伪代码
for i in 1~n:   
    dist[i] = e[s,i]   
loop n-1 times:    
    for all (u,v) such that e[u,v]<INF:   
        dist[v] = min( dist[v], dist[u]+e[u,v] )

核心代码也是最后三行… 太tm精妙了!!
外层循环N-1次, 内层循环M次, 所以代码的复杂度是O(NM).

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值