Dijkstra算法是典型的 单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。
算法思路
Dijkstra算法是一种类似于贪心的算法,步骤如下:
-
首先指定一个节点,比如说V0,要计算V0到其他节点的最短路径。
-
设置两个集合S和U:
S中存放已找到的最短路径的顶点,初始时,集合S中只有一个顶点,即V0。
U中存放当前还未找到最短路径的点,初始时,集合U中存放除了V0以外的所有点。
-
在U中选取一个距离V0最近的点,并认为那个点到V0的距离为最短路径,将选中的点(设为V1)放入集合S中。
-
把V1当做中间节点,更新集合U中的各顶点的距离(遍历V1的出点):
if ( 'V1 到 Vn 的距离' + 'V1 到 V0 距离' < 'V0 到 Vn 的距离' )
,则更新U。 -
重复第3、第4步骤,直到所有节点都包含在S中,则遍历结束。
此处有一张帮助理解的动态图:
算法分步图解
若存在一个有权图如下图所示:
- 首先我们选定节点A作为原点,并初始化S和U两个集合:
-
执行上述第3、4步骤:
- 在U中选取一个距离1号节点最近的点:3号节点,并认为3号节点到1号节点的距离为最短路径,将3号节点点放入集合S中。
- 把3号节点当做中间节点,更新集合U中的各顶点的距离(遍历3号节点的出点),并更新U。
-
接着继续在U中选择距离1号节点最近的节点:2号节点。将2号节点放入S集合中,并更新U集合中的值。由于此时以2号节点为中转点并不能减少U中其他节点到1号节点的距离,所以U中的值不会改变。
-
继续重复上述步骤:将4号节点选入S中。
-
执行到最后一步是,由于U中只有5号节点,所以5号节点理所当然被选进S集合,此时算法结束:
算法实现
dist[i]表示当前找到的从源点v0出发到终点vi的最短路径的长度,初始化时,dist[i] = edge[v0][i]
S[i]为0表示顶点vi是否被加入到集合S中,初始化时S[v0]=1,其余为0
path[i]表示v0到vi的最短路径上顶点vi的前一个顶点序号。采用“倒向追踪”方法,确定v0到vi的最短路径上的每个顶点
初始化:dist[k] = edge[v0][k]v0是源点S[v0]=1
递推:
u = min{dist[t]}s[vt] = 0;
u表示当前T集合中dist数组元素值最小的顶点的序号,以后u加入集合S。
dist[k] = min(dist[k], dist[u] + edge[u][k])S[vk] = 0;
代码实现
void Dijkstra(Vertex s)
{
while(1){
V = 未收录顶点中dist最小者;
if(不存在这样的V)
break;
collected[V] = true; //true表示收录进S集合
for (V的每个邻接点W)
if(collected[W] == false)
if(dist[V]+E<v,w> < dist[W]){ //更新U集合
dist[W] = dist[V] + E<v,w>;
path[W] = V; //记录路径
}
}
}
算法分析
Dijkstra有一个不足之处,那就是此算法不能计算负权边,要解决负权边,需要用到bellman-ford算法。
如上图,如果用Dijkstra算法的话就会出错:从1号节点开始,第一步dist[2] = 7, dist[3] = 5;在其中找出最小的边是dist[3] = 5;然后更新dist[2] = 0,最终得到dist[2] = 0,dist[3] = 5,而实际上dist[3] = 2;所以如果图中含有负权值,Dijkstra失效。
这个算法的复杂度是O(n2),空间复杂度也是n2,但通过优先队列优化,可将复杂度将至O(nlog(n))。