算法简介
Dijkstra算法是一种计算最短路径的算法,对于给定的一个有权重的图,Dijkstra算法可以计算出任一点到其他各点的最短路径。
Dijkstra‘s algorithm是一种greedy algorithm。贪婪算法就是在算法的每一个阶段都取最大值。一般情况下,贪婪算法能取得较好的效果。但是贪婪算法是有其缺陷的,也就是说能达到局部最优,而未必能达到全局最优,举个例子,假设要兑换15分的硬币,而硬币有12分的,10分的,5分的,1分的,那么根据贪婪算法,最终的兑换结果为1个12分的,3个一分的;而最优结果应该是一个10分的,一个5分的。(这里我们定义最优为硬币个数最少)。
Dijkstra‘s algorithm的步骤是:
对于每一个阶段,Dijkstra‘s algorithm会在所有未处理的点中选取一个最小距离的点v,然后把给定点s到v的路径定义为最小路径。
算法图解原理
假设我们有如下的一个图
我们需要知道v1到其他所有点的最短花费,那么要怎么做呢?
我们需要定义一个distance[]数组用来记录最短的花费
定义一个visited[]数组来记录是否访问过节点
首先选择v1点,然后遍历v1的所有子节点,这里有v2和v4,在distance里面记录他们的距离
【0,2,+oo,1,+oo,+oo,+oo】
【1,0,0,0,0,0,0】
然后选择未遍历的最近的点,就是v4,然后遍历v4的子节点,这里有v3,v6,v7,v5。我们更新v1到这些点的距离
【0,2,3,1,3,9,5】
【1,0,0,1,0,0,0】
然后从未遍历的点中,选择距离最短的点,那就是v2点,然后用v2点来更新最短距离,看v2的子节点v4,v5。然后更新这两个节点的最小距离
我们发现通过v2点的距离分别为5和12,这大于已有的最短距离,因此不更新distance中的数据
【0,2,3,1,3,9,5】
【1,1,0,1,0,0,0】
然后选择下一个节点v3,它的子节点v1和v6,v1不需要更新,那我们看v6,通过v3点的距离为3 + 5 = 8 < 9,因此需要更新v6的最短距离
【0,2,3,1,3,8,5】
【1,1,1,1,0,0,0】
然后选择下一个节点v5,它的子节点为v7,通过v5的距离 3 + 6 = 9 > 5,因此不更新distance中的最短距离
【0,2,3,1,3,8,5】
【1,1,1,1,1,0,0】
然后选择下一个节点v7,它的子节点为v6,通过v7到v6的距离为 5 + 1 = 6 < 8,因此更新distance中的最短距离
【0,2,3,1,3,6,5】
【1,1,1,1,1,0,1】
最后剩下一个节点v6,他没有子节点,结束。
算法实现
首先定义数据结构,以点为中心
1
2
3
4vector<vector<vector<int>>> graphs;
graphs[0]表示点0对应的边信息
graphs[0][0]表示点0对应的第一条边的信息
graphs[0][0]包含三个元素,分别为启示点,终点,以及权重。再定义一个distance的数组,因为每次要取最小的值,因此最好用priority_queue。
还要定义一个结构用来对应节点和最小的值,以及是否被访问过
1
2
3
4
5struct Node {
int nodeId;
int distance;
bool isVisited;
}
下面我们来看具体的实现:
1 | typedef struct Node { |
例题分析
743. 网络延迟时间
这个问题就是一个标准的Dijkstra算法的应用。在这道题目中,我们需要计算出起始节点到所有节点的距离,然后从结果中选择最大值即可。
1 | typedef struct Node { |
1631. 最小体力消耗路径
1514. 概率最大的路径
这道题目初看好像跟Dijkstra关系不大,首先这是个无向图,其次这次是各个路径相乘,而且是最大值。实际上这也是可以用Dijkstra算法的,只不过是要做一定的修改变通。
- 首先无向图实际上就是一个双向的有向图罢了
- 其次每次更新的时候,把加法换做乘法即可,同时大值才更新
1 | typedef struct Node { |
扩展
从上面的算法可以看出,Dijkstra算法适合于计算权重为正的有向图。
- 那么如果权重为负呢?Dijkstra算法无法直接解决权重为负的图,但是我们可以为所以的权重都加一个正数,让权重变为正,就可以解决了
- 如果是无权重图呢?更简单了,那就相当于所有的权重均为1。
因此对于有向图,一般都可以通过Dijkstra算法来解决最短路径问题