最短路径问题

来源:互联网 发布:网络的益处与坏处 编辑:程序博客网 时间:2024/06/08 15:45

最短路径

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发出的边进行松弛。
Dijkstra算法

其伪代码表示为:

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<v, w> < dist[w]) {                    dist[w] = dist[v] + E<v, w> ;                    path[w] = v;                }            }        } // for    }}

在上述代码中v = 未收录顶点中dist最小者; 对于不同的存储方式其算法的复杂度是不一样的。
Dijkstra 复杂度

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

3. 多源最短路径

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

多源最短路径

对于方法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和从ij中间节点均取自集合{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的一条最短路径。

这里写图片描述

所有节点对最短路径问题的一个递归解
因此,我们可以定义一个求最短路径估计的递归公式。设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). 数据结构–陈越、何钦铭

0 0
原创粉丝点击