单源最短路径问题
给定一个带权有向图 G=(V,E) ,其中每条边的权是一个非负实数。另外,还给定 V 中的一个顶点,称为源。现在我们要计算从源到所有其他各顶点的最短路径长度。这里的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。
前面Bellman-Ford最短路径算法讲了单源最短路径的Bellman-Ford算法(动态规划算法)。这里介绍另外一个更常见的算法Dijkstra算法。
Dijkstra算法和 最小生成树Prim算法最小生成树算法非常类似,大家可以先熟悉下个算法。两个算法都是基于贪心算法。虽然Dijkstra算法相对来说比Bellman-Ford 算法更快,但是不适用于有负权值边的图,贪心算法决定了它的目光短浅。而Bellman-Ford 算法从全局考虑,可以检测到有负权值的回路。
这里模仿MST(Minimum Spanning Tree)的Prim算法,我们创建一个SPT(最短路径树),最初只包含源点。我们维护两个集合,一组已经包含在SPT(最短路径树)中的顶点S集合,另一组是未包含在SPT内的顶点T集合。每次从T集合中选择到S集合路径最短的那个点,并加入到集合S中,并把这个点从集合T删除。直到T集合为空为止。
T集合用最小优先队列Q存储,该队列包含所有属于V-S的节点(这些节点尚未确定最短路径的权),且以d值为关键字排列各节点。
初始时,Q包含了除源点src以外的其他节点,这些节点的d值为无穷大。源点src进入S之后,d[src]=0。算法反复从Q中取出d值最小的节点u,u属于V-S,把u插入集合S中,并对u的所有出边进行松弛,这一过程直到Q队列为空为止。
举例
如下图所示的图:
S集合最初为空,然后选取源点0,S集合为 {0},源点到其它所有点的距离为 {0, INF, INF, INF, INF, INF, INF, INF} 。图中蓝色表示 SPT,迭代的过程如下:
最终得到 SPT(最短路径树) 如下:
注意点
The code calculates shortest distance, but doesn’t calculate the path information. We can create a parent array, update the parent array when distance is updated (like prim’s implementation) and use it show the shortest path from source to different vertices.
The code is for undirected graph, same dijekstra function can be used for directed graphs also.
The code finds shortest distances from source to all vertices. If we are interested only in shortest distance from source to a single target, we can break the for loop when the picked minimum distance vertex is equal to target (Step 3.a of algorithm).
Time Complexity of the implementation is O(V^2). If the input graph is represented using adjacency list, it can be reduced to O(E log V) with the help of binary heap. We will soon be discussing O(E Log V) algorithm as a separate post.
Dijkstra’s algorithm doesn’t work for graphs with negative weight edges. For graphs with negative weight edges, Bellman–Ford algorithm can be used, we will soon be discussing it as a separate post.
时间复杂度分析
算法的执行速度取决于优先队列Q的数据结构。有三种数据结构可供选择。
代码
/*------------------------------------- * 日期:2015-04-23 * 作者:SJF0115 * 题目: Dijkstra算法(单源最短路径) * 博客: ------------------------------------*/
#include <iostream>
#include <climits>
#include <vector>
using namespace std;
//从未包含在SPT的集合T中,选取一个到S集合的最短距离的顶点
int GetMinVertex(int dist[], bool visited[],int v) {
int min = INT_MAX;
int index;
for(int i = 0;i < v;++i){
// 没访问过且距SPT最短的顶点
if(!visited[i] && dist[i] < min){
min = dist[i];
index = i;
}//if
}//for
return index;
}
// 打印结果
void Print(int dist[],int n){
for(int i = 0;i < n;++i){
cout<<"距离顶点"<<i<<"最短距离->"<<dist[i]<<endl;
}//for
}
//source 代表源点
void Dijkstra(vector<vector<int> > graph,int src) {
// 顶点个数
int v = graph.size();
// dist[i]表示从源点到顶点i的距离
int dist[v];
// visited[i]=true 如果顶点i包含在SPT中
bool visited[v];
// 初始化 0代表不可达
for(int i = 0;i < v;++i){
dist[i] = (graph[src][i] == 0 ? INT_MAX:graph[src][i]);
visited[i] = false;
}//for
dist[src] = 0;
visited[src] = true;
// 迭代V-1次,因此不用计算源点了,还剩下V-1个需要计算的顶点。
for(int i = 1;i < v;++i){
// T集合中到S集合距离最短的顶点
int u = GetMinVertex(dist,visited,v);
// 加入SPT中
visited[u] = true;
// 更新T集合中顶点到S集合顶点的距离
for (int j = 0;j < v;++j){
if (!visited[j] && graph[u][j] && dist[u] != INT_MAX
&& dist[u] + graph[u][j] < dist[j]){
dist[j] = dist[u] + graph[u][j];
}//if
}//for
}//for
// 打印结果
Print(dist,v);
}
int main() {
vector<vector<int> > graph =
{
{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;
}
转载于:Dijkstra最短路径算法[贪心]