Dijkstra’s algorithm使用了广度优先搜索解决非负权图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。举例来说,如果图中的顶点表示城市,而边上的权重表示著城市间开车行经的距离,该算法可以用来找到两个城市之间的最短路径。
其基本原理是:每次新扩展一个距离最短的点,更新与其相邻的点的距离。当所有边权都为正时,由于不会存在一个距离更短的没扩展过的点,所以这个点的距离永远不会再被改变,因而保证了算法的正确性。不过根据这个原理,用Dijkstra求最短路的图不能有负权边,因为扩展到负权边的时候会产生更短的距离,有可能就破坏了已经更新的点距离不会改变的性质。
Dijkstra 算法的基础操作是边的拓展:如果存在一条从 u 到 v 的边,那么从 s 到 v 的最短路径可以通过将边(u, v)添加到尾部来拓展一条从 s 到 v 的路径。这条路径的长度是 d[u] + w(u, v)。如果这个值比目前已知的 d[v] 的值要小,我们可以用新值来替代当前 d[v] 中的值。拓展边的操作一直运行到所有的 d[v] 都代表从 s 到 v 最短路径的花费。这个算法经过组织因而当 d[u] 达到它最终的值的时候每条边(u, v)都只被拓展一次。
不采用最小优先级队列,时间复杂度是O(|V|^2)(其中|V|为图的顶点个数)。通过斐波那契堆实现的迪科斯彻算法时间复杂度是O(|E|+|V|\log|V|) (其中|E|是边数) (Fredman & Tarjan 1984)。对于不含负权的有向图,这是目前已知的最快的单源最短路径算法。
伪代码:
在下面的算法中,u := Extract_Min(Q) 在顶点集合 Q 中搜索有最小的 d[u] 值的顶点 u。这个顶点被从集合 Q 中删除并返回给用户。
function Dijkstra(G, w, s)
for each vertex v in V[G] // 初始化
d[v] := infinity
previous[v] := undefined // 各点的已知最短路径上的前趋都未知
d[s] := 0 // 因为出发点到出发点间不需移动任何距离,所以可以直接将s到s的最小距离设为0
S := empty set
Q := set of all vertices
while Q is not an empty set // Dijkstra演算法主体
u := Extract_Min(Q)
S.append(u)
for each edge outgoing from u as (u,v)
if d[v] > d[u] + w(u,v) // 拓展边(u,v)。w(u,v)为从u到v的路径长度。
d[v] := d[u] + w(u,v) // 更新路径长度到更小的那个和值。
previous[v] := u // 记录前驱结点
如果我们只对在 s 和 t 之间查找一条最短路径的话,我们可以添加条件如果满足 u = t 的话终止程序。
通过推导可知,为了记录最佳路径的轨迹,我们只需记录该路径上每个点的前趋,即可通过迭代来回溯出 s 到 t 的最短路径(当然,使用后继节点来存储亦可。但那需要修改代码):
s := empty sequence
u := t
while defined u
insert u to the beginning of S
u := previous[u] //previous数组即为上文中的p
现在序列 S 就是从 s 到 t 的最短路径的顶点集。
#include <limits>
const int V = 9;
const int INF = numeric_limits<int>::max();
void printSolution(int d[])
{
cout<<"Vertex Distance from Source\n";
for (int i = 0; i < V; i++)
cout<<i<<"\t\t"<<d[i]<<endl;
}
void dijkstra(int graph[V][V], int src)
{
int d[V];
bool include[V];
//初始化
for(int i = 0; i < V; i++)
{
d[i] = INF;
include[i] = false;
}
d[src] = 0;
for(int i = 0; i < V; i++)
{
//选取当前最小距离点
int min_dist = INF;
int min_index = 0;
for(int i = 0; i < V; i++)
{
if(d[i] < min_dist && include[i] == false)
{
min_dist = d[i];
min_index = i;
}
}
//标记所选最小距离点,表示已加入结果集
include[min_index] = true;
//更新所选点相邻点的距离
for(int i = 0; i < V; i++)
if(include[i] == false && graph[min_index][i] && d[min_index] + graph[min_index][i] < d[i])
d[i] = d[min_index] + graph[min_index][i];
}
printSolution(d);
}
int _tmain(int argc, _TCHAR* argv[])
{
int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0},
{4, 0, 8, 0, 0, 0, 0, 11, 0},
{0, 8, 0, 7, 0, 4, 0, 0, 2},
{0, 0, 7, 0, 9, 14, 0, 0, 0},
{0, 0, 0, 9, 0, 10, 0, 0, 0},
{0, 0, 4, 0, 10, 0, 2, 0, 0},
{0, 0, 0, 14, 0, 2, 0, 1, 6},
{8, 11, 0, 0, 0, 0, 1, 0, 7},
{0, 0, 2, 0, 0, 0, 6, 7, 0}
};
dijkstra(graph, 0);
return 0;
}
这是最简单的实现方法,用一个链表或者数组来存储所有顶点的集合 Q,所以搜索 Q 中最小元素的运算(Extract-Min(Q))只需要线性搜索 Q 中的所有元素。这样的话算法的运行时间是 O(n^2)。
对于边数少于 n2 的稀疏图来说,我们可以用邻接表来更有效的实现该算法。同时需要将一个二叉堆或者斐波纳契堆用作优先队列来查找最小的顶点(Extract-Min)。当用到二叉堆的时候,算法所需的时间为O((m + n)log n),斐波纳契堆能稍微提高一些性能,让算法运行时间达到O(m + n log n)。然而,使用斐波纳契堆进行编程,常常会由于算法常数过大而导致速度没有显著提高。
参考:
https://zh.wikipedia.org/wiki/%E6%88%B4%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95
http://www.nocow.cn/index.php/Dijkstra%E7%AE%97%E6%B3%95
http://baike.baidu.com/view/1712262.htm?fromtitle=Dijkstra%E7%AE%97%E6%B3%95&fromid=215612&type=syn
版权声明:本文为博主原创文章,未经博主允许不得转载。
图算法(1):Dijkstra's algorithm