摘自计蒜客:http://www.jisuanke.com/course/35/7557
先来看这样一个问题:有n座城市,已知任意两个座城市之间的距离,现在要分别求出城市A到其他n-1座城市的最短路径,
也就是求所经过的距离和的最小值。
这是一个经典的单源最短路问题,即求一起点到其余各个顶点的最短路径问题。
首先,我们可以把该场景看成是一个带权图,把n个城市看成n个顶点,把两座城市之间的距离看成是两个顶点之间的边权值,
这样问题就转化成了求顶点A到其余n-1个顶点的最短路径。
Dijkstra算法是最常见的求解单源最短路问题的算法。
先来看看 Dijkstra 算法的具体过程:
定义带权图 G 所有顶点的集合为 V,接着我们再定义已确定最短路径的顶点集合为 U,初始集合 U 为空。接着执行以下操作:
-
首先我们将起点 x 加入集合 U,并在数组 A 中记录起点 x 到各个点的最短路径(如果顶点到起点 x 有直接相连的边,则最短路径为边权值,否则为一个极大值)。
-
从数组 A 中选择一个距离起点 x 最近的、且不属于集合 U 的顶点 v(如果有多个顶点 v,任选其一即可),将顶点 v 加入集合 U,并更新所有与顶点 v 相连的顶点到起点x 的最短路径。
-
重复第二步操作,直至集合 U 等于集合 V。
算法结束,数组 A 记录了起点 x 到其余 n−1 个点的最短路径。
下面我们用一个小例子来模拟下 Dijkstra 算法吧。
通过模拟,最终我们可以得到起点 a 到各点的最短路径分别为:
l:5(a−>l)
e:8(a−>l−>e)
r:7(a−>l−>r)
b:9(a−>l−>e−>b)
仔细分析算法,我们可以发现,Dijkstra 算法和前面讲解的 Prim 算法很相像,都是从一个点开始,每次确定一个点并完成更新,重复操作直至 n 个点都确定为止。Dijkstra 算法的时间复杂度为 O(V2+E),V 为顶点总个数,E 为总边数。如果利用堆进行优化,可以将时间复杂度优化 O(VlogV+E),是最坏情况下最优的单源最短路算法。
需要注意的是,Dijkstra 不适用于有边权为负数的情况哦,否则会影响算法的正确性。
算法实现:计算一个带权无向图中从顶点0出发,到所有顶点的最短路径长度。
比如下图:
在这个图中,从a出发到e的最短路a->l->e,长度是8;到b的最短路是a->l->e->b,长度为9;
代码如下:
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Edge {
int vertex, weight;
};
class Graph {
private:
int n;
vector<Edge> * edges;
bool * visited;
public:
int * dist;
Graph (int input_n) {
n = input_n;
edges = new vector<Edge>[n];
dist = new int[n];
visited = new bool[n];
memset(visited, 0, n);
memset(dist, 0x3f, n * sizeof(int));
}
~Graph() {
delete[] dist;
delete[] edges;
delete[] visited;
}
void insert(int x, int y, int weight) {
edges[x].push_back(Edge{y, weight});
edges[y].push_back(Edge{x, weight});
}
void dijkstra(int v) {
//[1]初始化
dist[v] = 0;
//[2]n次循环,每次找出从源点出发最短路径最小的未访问的顶点,标记为已访问,之后更新
//和这个顶点相邻的所有顶点的最短路。
for(int i=0; i<n; i++) {
int min_dist = INF, min_vertex;
//[3]找出所有未访问顶点中从源点出发最短路径最小的,并将其保存。
for(int j=0; j<n; ++j) {
//[4]如果顶点j未访问且最短路径比min_dist记录的更小,那么就用源点到顶点j的最短路径更新
//min_dist,然后将顶点编号保存到min_vertex中。
if(!visited[j] && dist[j] < min_dist) {
min_dist = dist[j];
min_vertex = j;
}
}
//[5]将min_vertex点的访问标志置为1
visited[min_vertex] = 1;
//[6]松弛操作:对于已经找到的当前最近顶点min_vertex,如果源点到它的最短路径长度
//min_dist加上和min_vertex相邻边的边权小于源点到该边另一端的最短路径,那么就
//更新另一端的最短路径,也就是dist数组中对应的值。
for(Edge &j:edges[min_vertex]) {
if(min_dist+j.weight < dist[j.vertex]) {
dist[j.vertex] = min_dist + j.weight;
}
}
}
}
};
int main() {
int n, m;
cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
g.insert(a, b, c);
}
g.dijkstra(0);
for (int i = 0; i < n; i++) {
cout << i << ": " << g.dist[i] << endl;
}
return 0;
}