Dijkstra思想
前面我们讲图的遍历 的时候讲过,图的存储主要就是邻接矩阵和邻接表。
我们知道广度优先搜索(BFS)也可以查找最短路径,但是却只能针对无权图。
最短路径: 带权路径长度最短的那条路就是最短路径。
带权有向图的最短路径问题可以分为两类:一是单源最短路径,即求图中某一顶点到其他顶点的最短路径,可以通过经典的Dijkstra
(狄杰斯特拉)算法求解;二是求每对顶点间的最短路径,可通过Floyd
(佛洛依德)算法求解。
Dijkstra
和Floyd
还有一个很大的区别就是Dijkstra
不能求解权值为负的问题,Floyd
可以求解。
这里先简单介绍下Dijkstra
算法思想。
我们计算从顶点1
到其他顶点的最短距离。
开始会选择从以1
开始的边中最短的,也就是1->5
,到了5
发现有5->2,5->3,5->4
可以选,我们该选哪个呢,肯定会选5->4
,因为权值最小,到了4
又有4->1,4->3
可以选因为前面我们1
是顶点了,已经取过了,就不能再取了,那只能选4->3
了,可是我们真的能选吗?现在1->5->4->3
的权值为5+2+6 = 13
可是1->5->2->3
的权值只有5+3+1 = 9
这不是更短吗?
所以这就需要我们时时维护更新到达该点的路径,发现更短的就更新。
我们每轮确定一个顶点的最短路径。
下面我们先采用临接矩阵来实现。
邻接矩阵graph
为:
我们还需要用一个一维数组 dis
来存储 1
号顶点到其余各个顶点的初始路程,如下。
我们将此时 dis 数组中的值称为最短路的“估计值”。
既然是求 1
号顶点到其余各个顶点的最短路程,那就先找一个离 1
号顶点最近的顶点。通过数组 dis
可知当前离 1
号顶点最近是 2
号顶点。当选择了 2
号顶点后,dis[2]
的值就已经从“估计值”变为了“确定值”,即
1
号顶点到 2
号顶点的最短路程就是当前
dis[2]
值。为什么呢?你想啊,目前离 1
号顶点最近的是 2
号顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得
1
号顶点到 2
号顶点的路程进一步缩短了。
1 2 3 4 5 6 7 8 9 10 11 int minDis = inf;for (int j = 1 ; j <= n; j++){ if (visited[j] == 0 && dis[j] < minDis){ minDis = dis[j]; u = j; } } visited[u] = 1 ;
既然选了 2
号顶点,接下来再来看 2
号顶点有哪些出边呢。有 2->3
和
2->4
这两条边。先讨论通过 2->3
这条边能否让 1
号顶点到 3
号顶点的路程变短。也就是说现在来比较 dis[3]
和
dis[2]+graph[2][3]
的大小。其中 dis[3]
表示
1
号顶点到 3
号顶点的路程。dis[2]+graph[2][3]
中 dis[2]
表示
1
号顶点到 2
号顶点的路程,graph[2][3]
表示 2->3
这条边。所以 dis[2]+graph[2][3]
就表示从 1
号顶点先到 2
号顶点,再通过 2->3
这条边,到达 3
号顶点的路程。
我们发现
dis[3]=12,dis[2]+graph[2][3]=1+9=10,dis[3] > dis[2]+graph[2][3]
,因此
dis[3]
要更新为
10
。这个过程有个专业术语叫做“松弛”。即
1
号顶点到 3
号顶点的路程即
dis[3]
,通过2->3
这条边松弛成功。这便是
Dijkstra
算法的主要思想:通过“边”来松弛
1
号顶点到其余各个顶点的路程。
1 2 3 4 5 6 7 8 for (int v = 1 ; v <= n; v++){ if (graph[u][v] < inf){ if (dis[v] > dis[u] + graph[u][v]){ dis[v] = dis[u] + graph[u][v]; } } }
下面同理:
刚才我们对 2
号顶点所有的出边进行了松弛。松弛完毕之后
dis
数组为:
同理:
最终的dis
为
OK,现在来总结一下刚才的算法。算法的基本思想是:每次找到离源点(上面例子的源点就是
1
号顶点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。基本步骤如下:
将所有的顶点分为两部分:已知最短路程的顶点集合 P
和未知最短路径的顶点集合 Q。最开始,已知最短路径的顶点集合 P
中只有源点一个顶点。我们这里用一个visited[i]
数组来记录哪些点在集合
P 中。例如对于某个顶点 i,如果 visited[i]
为 1
则表示这个顶点在集合 P 中,如果 visited[i]
为 0
则表示这个顶点在集合 Q 中。
设置源点 s
到自己的最短路径为 0 即
dis=0
。若存在源点有能直接到达的顶点 i,则把
dis[i]
设为
graph[s][i]
。同时把所有其它(源点不能直接到达的)顶点的最短路径为设为
∞。
在集合 Q 的所有顶点中选择一个离源点 s
最近的顶点
u
(即 dis[u]
最小)加入到集合 P。并考察所有以点
u 为起点的边,对每一条边进行松弛操作。例如存在一条从 u 到 v
的边,那么可以通过将边 u->v 添加到尾部来拓展一条从 s 到 v
的路径,这条路径的长度是
dis[u]+graph[u][v]
。如果这个值比目前已知的
dis[v]
的值要小,我们可以用新值来替代当前dis[v]
中的值。
重复第 3 步,如果集合 Q 为空,算法结束。最终
dis
数组中的值就是源点到所有顶点的最短路径。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <bits/stdc++.h> using namespace std;typedef long long ll;typedef long double ld;typedef pair<int , int > pii;typedef pair<ll, ll> pll;typedef vector<int > vi;int graph[10 ][10 ];int dis[10 ];int visited[10 ];int n,m; const int inf = INT_MAX / 2 ;void init () { for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= n; j++){ if (i == j) graph[i][j] = 0 ; else graph[i][j] = inf; } } } void add (int from, int to, int weight) { graph[from][to] = weight; } void dijkstra (int u) { for (int i = 1 ; i <= n - 1 ; i++){ int minDis = inf; for (int j = 1 ; j <= n; j++){ if (visited[j] == 0 && dis[j] < minDis){ minDis = dis[j]; u = j; } } visited[u] = 1 ; for (int v = 1 ; v <= n; v++){ if (graph[u][v] < inf){ if (dis[v] > dis[u] + graph[u][v]){ dis[v] = dis[u] + graph[u][v]; } } } } } int main () { cin >> n >> m; init (); for (int i = 1 ; i <= m; i++){ int from, to, weight; cin >> from >> to >> weight; add (from, to, weight); } for (int i = 1 ; i <= n; i++){ dis[i] = graph[1 ][i]; } for (int i = 1 ; i<= n; i++){ visited[i] = 0 ; } visited[1 ] = 1 ; dijkstra (1 ); for (int i = 1 ; i <= n; i++){ if (i == n) cout << dis[i] << endl; else cout << dis[i] << " " ; } return 0 ; }
输入:
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
输出:
0 1 8 4 13 17
有 n
个网络节点,标记为 1
到
n
。
给你一个列表 times
,表示信号经过 有向
边的传递时间。 times[i] = (ui, vi, wi)
,其中
ui
是源节点,vi
是目标节点,
wi
是一个信号从源节点传递到目标节点的时间。
现在,从某个节点
K
发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回
-1
。
###示例 1:
输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
输出:2
思想:
方法一:Dijkstra邻接矩阵
负责度为O(N^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Solution {public : const int inf = INT_MAX / 2 ; vector<vector<int > > graph; vector<int > dis; vector<int > visited; int networkDelayTime (vector<vector<int >>& times, int n, int k) { graph.resize (n + 1 , vector <int >(n + 1 , 0 )); dis.resize (n + 1 , 0 ); visited.resize (n + 1 , 0 ); for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= n; j++){ if (i == j) graph[i][j] = 0 ; else graph[i][j] = inf; } } for (vector<int > tmp : times){ int from = tmp[0 ]; int to = tmp[1 ]; int weight = tmp[2 ]; graph[from][to] = weight; } for (int i = 1 ; i <= n; i++){ dis[i] = graph[k][i]; } visited[k] = 1 ; dijkstra (k, n); int res = 0 ; for (int i = 1 ; i <= n; i++){ if (dis[i] >= inf) return -1 ; else res = max (dis[i], res); } return res; } void dijkstra (int u, int n) { for (int i = 1 ; i <= n - 1 ; i++){ int minDis = inf; for (int j = 1 ; j <= n; j++){ if (visited[j] == 0 && dis[j] < minDis){ minDis = dis[j]; u = j; } } visited[u] = 1 ; for (int v = 1 ; v <= n; v++){ if (graph[u][v] < inf){ if (dis[v] > dis[u] + graph[u][v]){ dis[v] = dis[u] + graph[u][v]; } } } } } };
方法二: 邻接矩阵+小根堆
复杂度为O(N^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution {public : const int inf = INT_MAX / 2 ; int networkDelayTime (vector<vector<int >>& times, int n, int k) { vector<vector<pair<int , int > > > edge (n + 1 ); for (vector<int > tmp : times){ edge[tmp[0 ]].push_back (make_pair (tmp[1 ], tmp[2 ])); } vector<int > dist (n + 1 , inf) ; dist[k] = 0 ; priority_queue<pair<int ,int >, vector<pair<int , int > >, greater<>>q; q.emplace (0 , k); while (! q.empty ()){ auto p = q.top (); q.pop (); int curDistFromStart = p.first; int curNodeID = p.second; if (dist[curNodeID] < curDistFromStart){ continue ; } for (auto &neighbor : edge[curNodeID]){ int nextNodeID = neighbor.first; int distToNextNode = dist[curNodeID] + neighbor.second; if (distToNextNode < dist[nextNodeID]){ dist[nextNodeID] = distToNextNode; q.emplace (distToNextNode, nextNodeID); } } } int res = 0 ; for (int i = 1 ; i <= n; i++){ if (dist[i] >= inf) return -1 ; else res = max (res, dist[i]); } return res; } };
方法三:floyd
floyd
时间复杂度:O(n^3)
空间复杂度:O(n^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution {public : vector<vector<int > > graph; const int inf = INT_MAX / 2 ; int networkDelayTime (vector<vector<int >>& times, int n, int k) { graph.resize (n + 1 , vector <int >(n + 1 , 0 )); for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= n; j++){ if (i == j) graph[i][j] = 0 ; else graph[i][j] = inf; } } for (vector<int > tmp : times){ int from = tmp[0 ]; int to = tmp[1 ]; int weight = tmp[2 ]; graph[from][to] = weight; } floyd (n); int res = 0 ; for (int i = 1 ; i <= n; i++){ if (graph[k][i] >= inf) return -1 ; else res = max (res, graph[k][i]); } return res; } void floyd (int n) { for (int p = 1 ; p <= n; p++){ for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= n; j++){ graph[i][j] = min (graph[i][j], graph[i][p] + graph[p][j]); } } } } };