最短路径问题

最短路径

1.1 最短路径的定义

最短路径问题_第1张图片
其数学表现形式为:
最短路径问题中,我们给定一个带权重的有向图G=(V, E) 和权重函数 ω:ER , 该权重函数将每条边映射到实数值的权重上,图中一条路径 p=<v0,v1,...,vk> 的权重 ω(p) 是构成该路径的所有边的权重之和:
δ(p)={min{ω(p):uv},,uv
从节点 u 到节点 v 最短路径则定义为任何一条权重 ω(p)=δ(u,v) 的路径p。

最短路径可分为单源最短路径问题和多源最短路径问题。
1. 单源最短路径问题: 给定一个图 G=(V,E) , 我们希望找到从给源节点 sV 到每个节点 vV 的最短路径。
2. 多源最短路径问题 : 求任意两个节点的最短路径。

注意:这里讨论的算法都假设,不存在权重为负值的边并且不存在环路

1.2 单源最短路径

最短路径算法通常依赖最短路径的一个重要的性质:两个节点之间的一条最短路径包含着其他的最短路径。

1.2.1 无权图的最短路径问题

对于无权图来说,我们可以使用广度优先搜索算来进行求解。但是我们需要对BFS算法进行修改。因为,我们需要计算源点到任何节点的最短路径,所以我们需要一个数组dist用来保存最短路径的距离和使用path数组用来保存路径。
及dist[W] = S 到W的最短距离。则有dis[s] = 0 。
path[W] = S到W进过的某个节点。
伪代码为:

void shortestPath(Vertex s) {
    dist[|V|] = -1;//首先对数组进行初始化
    path[|V|];    //定义一个path数组
    queue Q;  //创建一个队列
    Q.push(s);   //将源点插入到队列中
    while (!Q.empty()) {  //如果队列不为空
        v = Q.front(); Q.pop();  //出队列
        for (v的每个邻接点 w) {
            if (dist[w] == -1) {  //如果等于-1表明,我们没有计算该节点
                dist[w] = dist[v] + 1;
                path[w] = v;  //表示s到w的路径经过了v
                Q.push(w);  //将w压入队列
            }
        } // for
    } // while
}

其时间复杂度为 O(|V|+|E|)

1.2.2 有权图的最短路径问题

Dijkstra算法解决的是带权重图上单源最短路径问题,该算法要求所有的边的权重都为非负值。Dijkstra算法在运行过程中维持的关键信息是一组节点集合 S 。从源节点 s 到该集合中每个节点之间的最短路径已经被找到。算法重复从节点集 VS 中选择最短路径估计最小的节点 u , 将 u 加入到集合 S ,然后对所有从 u 发出的边进行松弛。
最短路径问题_第2张图片

其伪代码表示为:

void Dijkstra(Vertex s) {
    while (1) {
        v = 未收录顶点中dist最小者;
        if (这样的v不存在) break;  //表明所有的节点都已经收录了
        collocted[v] = true;   //收录该节点 
        for (v 的每个邻接点 w) {
            if (collected[w] == false) {
                if (dist[v] + E < dist[w]) {
                    dist[w] = dist[v] + E ;
                    path[w] = v;
                }
            }
        } // for
    }
}

在上述代码中v = 未收录顶点中dist最小者; 对于不同的存储方式其算法的复杂度是不一样的。
最短路径问题_第3张图片

Dijkstra算法的“栗子”请看 07-图6 旅游规划 (25分)

3. 多源最短路径

多源最短路径问题:给定一个图,求任意两个节点之间的最短路径。

最短路径问题_第4张图片

对于方法1直接调用单源最短路径算法,因为,我们前面已经介绍了单源最短路径算法。因此,这里将不再继续讨论。

现在我们重点来讨论一下Floyd-Warshall算法

最短路径的结构
假定图 G 的所有节点为 V=1,2,...,n ,考虑其中的一个子集 1,2,...,k ,这里 k 是某个小于 n 的整数。对于任意节点对 i,jV , 考虑从节点 i 到节点 j 的所有中间节点均取自集合 1,2,...,k 的路径,并且设 p 为其中权重最小的路径(路径 p 是简单路径)。Floyd-Warshall算法利用路径 p 和从 i j 中间节点均取自集合{1,2,…,k-1}的最短路径之间的关系。该关系依赖于节点 k 是否是路径 p 上的一个中间节点。

  • 如果节点 k 不是路径 p 上的中间节点,则路径 p 上的所有中间节点都属于集合 1,2,...,k1 。因此,从节点 i 到节点 j 的中间节点取自集合{1,2,…,k-1}的一条最短路径也是从节点 i 到节点 j 的中间节点取自集合 1,2,...,k 的一条最短路径。
  • 如果节点 k 是路径 p 上的中间节点,则将路径 p 分解为 ikj ,如下图所示。因此,有 p1 是从节点 i 到节点 k 的中间节点全部取自集合 {1,2,...,k} 的一条最短路径。事实上,我们可以得出更强的结论。因为节点 k 不是路径 p1 上的中间节点,路径 p1 上的所有中间节点都属于集合 1,2,...,k1 。因此, p1 是从节点 i 到节点 k 的中间节点全部取自集合 1,2,...,k1 的一条最短路径。类似地, p2 是从节点 k 到节点 j 的中间节点全部取自集合 1,2,...,k1 的一条最短路径。

最短路径问题_第5张图片

所有节点对最短路径问题的一个递归解
因此,我们可以定义一个求最短路径估计的递归公式。设 d(k)ij 为从节点 i 到节点 j 的所有中间节点全部取自集合 1,2,...,k 的一条最短路径的权重值。当 k=0 时,从节点 i 到节点 j 的一条不包括编号大于0的中间节点的路径将没有任何中间节点。这样的路径最多只有一条边,因此, d(0)ij=ωij 。因此,我们可以递归的定义 d(k)ij 如下:
d(k)ij={ωij,min{d(k1)ij,d(k1)ik+d(k1)kj}, k=0 k1

其伪代码表示为:

void floyd() {
    //初始化操作
    D[N][N] = 图的权重W; // D[i][j] = W_{ij}
    PATH[N][N] = -1;
    for k = 1 to N 
        for i = 1 to N 
            for j = 1 to  N 
                if (D[i][j] < D[i][k] + D[k][j] ) {
                    D[i][j] = D[i][k] + D[k][j];
                    path[i][j] = k;
                }
}

其Floyd算法的时间复杂度为 O(N3)
Floyd算法的栗子请看 07-图4 哈利·波特的考试

Reference:
(1). 《算法导论》第三版 Thomas H.Cormen et al.
(2). 数据结构–陈越、何钦铭

你可能感兴趣的:(数据结构,图最短路径)