問題:node
There are N
network nodes, labelled 1
to N
.算法
Given times
, a list of travel times as directed edges times[i] = (u, v, w)
, where u
is the source node, v
is the target node, and w
is the time it takes for a signal to travel from source to target.數組
Now, we send a signal from a certain node K
. How long will it take for all nodes to receive the signal? If it is impossible, return -1
.網絡
Note:spa
N
will be in the range [1, 100]
.K
will be in the range [1, N]
.times
will be in the range [1, 6000]
.times[i] = (u, v, w)
will have 1 <= u, v <= N
and 1 <= w <= 100
.解決:code
① 圖的遍歷。給定一張圖中若干節點的連通性信息以及節點間的距離,以後要求咱們判斷針對某個特定節點(第K個節點),其可否和其餘全部結點連通,並求出要到達全部節點須要通過的網絡時延。隊列
通過分析,能夠明確兩個子問題,即:
- 判斷兩個節點之間是否連通
- 求出某個節點到圖中其餘任意可達節點所需的最短路徑get
所以本題的最終目的是求單源最短路徑。it
最短路徑的經常使用解法有迪傑克斯特拉算法Dijkstra Algorithm, 弗洛伊德算法Floyd-Warshall Algorithm, 和貝爾曼福特算法Bellman-Ford Algorithm,其中,Floyd算法是多源最短路徑,即求任意點到任意點到最短路徑,而Dijkstra算法和Bellman-Ford算法是單源最短路徑,即單個點到任意點到最短路徑。io
Dijkstra算法處理有向權重圖時,權重必須爲正,而另外兩種能夠處理負權重有向圖,可是不能出現負環,所謂負環,就是權值總和均爲負的環。
這三個算法的核心思想,當有對邊 (u, v) 是結點u到結點v,若是 dist(v) > dist(u) + w(u, v),那麼 dist(v) 就能夠被更新,這是全部這些的算法的核心操做。
Dijkstra算法是從一個頂點到其他各頂點的最短路徑算法,解決的是有向圖中最短路徑問題。迪傑斯特拉算法主要特色是以起始點爲中心向外層層擴展,直到擴展到終點爲止,是一種廣度優先的搜索方法。
普通的實現方法的時間複雜度爲O(V^2),基於優先隊列的實現方法的時間複雜度爲O(E + VlogV),其中V和E分別爲結點和邊的個數。
dijkstra算法例子:求從結點0到各個結點的最短路徑。
class Solution { //137ms
public int networkDelayTime(int[][] times, int N, int K) {
if (times == null || times.length == 0) return -1;
Map<Integer,Map<Integer,Integer>> path = new HashMap<>();//key爲起始節點,value爲相鄰節點和兩個節點的距離
for (int[] time : times){
Map<Integer,Integer> sourceMap = path.get(time[0]);
if (sourceMap == null){
sourceMap = new HashMap<>();
path.put(time[0],sourceMap);
}
Integer distance = sourceMap.get(time[1]);
if (distance == null || distance > time[2]){
sourceMap.put(time[1],time[2]);
}
}
//使用PriorityQueue獲取絕對距離最短的節點,並計算其與鄰居節點的絕對距離。
Map<Integer,Integer> distanceMap = new HashMap<>();
distanceMap.put(K,0);
PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((i1,i2) -> {return i1[1] - i2[1];});
priorityQueue.offer(new int[]{K,0});
int max = -1;
while (! priorityQueue.isEmpty()){
int[] cur = priorityQueue.poll();
int node = cur[0];
int distance = cur[1];
if (distanceMap.containsKey(node) && distanceMap.get(node) < distance) continue;
Map<Integer,Integer> sourceMap = path.get(node);
if (sourceMap == null) continue;
for (Map.Entry<Integer,Integer> entry : sourceMap.entrySet()){
int absoluteDistance = distance + entry.getValue();
int targetNode = entry.getKey();
if (distanceMap.containsKey(targetNode) && distanceMap.get(targetNode) <= absoluteDistance) continue;
distanceMap.put(targetNode,absoluteDistance);
priorityQueue.offer(new int[]{targetNode,absoluteDistance});
}
}
for (int val : distanceMap.values()){
if (val > max){
max = val;
}
}
return distanceMap.size() == N ? max : -1;
}
}
② 使用floyd-warshall算法獲得全部的最短路徑,而後選擇從節點K開始的路徑,這樣更容易(但效率更低)。時間複雜度爲O(n^3),空間複雜度爲O(n^2)。
1. 時間複雜度的對比
2.
3. 例如:
D0表示圖的鄰接矩陣的表示,D1由上面的公式求得,該公式與迪傑斯特拉算法同樣。
P數組表示當前節點的前驅節點。
class Solution { //67ms
public int networkDelayTime(int[][] times, int N, int K) {
int maxDistance = 100 * 100;//兩個節點之間的最短距離
int[][] distance = new int[N][N];
for (int i = 0;i < N;i ++){//初始化全部節點之間的距離爲最遠距離
Arrays.fill(distance[i],maxDistance);
}
//處理圖的信息,將能夠到達的點之間的路徑距離存入表中
for (int[] time : times){
distance[time[0] - 1][time[1] - 1] = time[2];
}
for (int i = 0;i < N;i ++){
distance[i][i] = 0;
}
//使用弗洛伊德算法遍歷圖,更新節點的路徑距離
for (int k = 0;k < N;k ++){
for (int i = 0;i < N;i ++){
for (int j = 0;j < N;j ++){
distance[i][j] = Math.min(distance[i][j],distance[i][k] + distance[k][j]);
}
}
}
//尋找節點K到最遠節點的最短距離
int res = Integer.MIN_VALUE;
for (int i = 0;i < N;i ++){
if (distance[K - 1][i] >= maxDistance) return -1;//節點不可達
res = Math.max(res,distance[K - 1][i]);
}
return res;
}
}
③ 使用Bellman-Ford算法,時間複雜度爲O(VE),其中V爲圖中節點數,E爲圖中邊數;空間複雜度爲O(V)。
Bellman-Ford算法從源點逐次繞過其餘節點,以縮短到達終點的最短路徑長度。
dist數組的遞推公式:
class Solution { //74ms public int networkDelayTime(int[][] times, int N, int K) { int maxDistance = 100 * 100;//兩個節點之間的最短距離 int[] distance = new int[N]; Arrays.fill(distance,maxDistance);//初始化全部節點之間的距離爲最遠距離 distance[K - 1] = 0;//第K個點做爲起點 for (int i = 1;i < N;i ++) { for (int[] time : times) {//使用Bellman-Ford計算最短路徑 int u = time[0] - 1; int v = time[1] - 1; int w = time[2]; distance[v] = Math.min(distance[v], distance[u] + w); } } int res = 0; for (int i = 0;i < distance.length;i ++){//尋找最遠的路徑 res = Math.max(res,distance[i]); } return res == maxDistance ? -1 : res; } }