如题:
分析思路:
-
从实例的途中就可以看出这是一个有向带权图,每条边的权重表示从一个节点发送信号到另一个节点所需要的时间。
-
题目要我们返回从某一个节点开始发送信号,需要多久才能使所有节点都收到这个信号,这里有两层含义:
- 第一层:如果当前节点到另一个节点有两条路径,这两条路径都能到达,我们应该选取花费时间更少的那条
- 第二层:需要保证所有节点都能收到该信号,因此所需要的时间应为当前节点到其他节点的需要花费时间中需要时间最久的那个时间,这样才能保证在这个时间内所有节点都能收到该信号
-
因此,第一个问题是如何得到当前节点到另一个节点的所有路径中花费时间最短的路径?
- 这个问题我们可以用
dijkstra
算法解决,这个算法就是解决最短路径问题的。 - 注意
dijkstra
算法的使用条件:加权有向图,没有负权重,因此当前问题满足条件。
- 这个问题我们可以用
-
第二个问题是如何知道到哪个节点所需要的 最短路径时间 是最长的?
- 我们可以在
dijkstra
算法计算每一个节点的耗费时间的同时利用一个数组记录当前节点到每一个节点所需要的最短时间。
- 我们可以在
代码:
dijkstra
算法实质上是在BFS
层次遍历图的过程中计算最短路径的,因此我们需要构建图,这里用邻接表法构建图。dijkstra
需要一个数组记录开始结点到其他所有节点的最短路径长度,我们可以这样实现:- 初始化一个
distFromStart
,记录的值初始化为无穷大(因为我们要缩小这个值)。 - 在遍历图的过程中找到当前结点的所有邻居节点,然后计算从开始结点到当前结点的路径长度加上当前节点到邻居节点的权重,即当前路径到该达邻居节点的路径长度,判断这个长度和
distFromStart
数组记录的长度的大小,distFromStart
数组始终记录更小的那个值。- 上述条件中我们可以知道的是当前结点到邻居节点的权重,那么我们如何知道开始节点到当前节点的路径长度?
- 我们可以用一个类来记录这个信息,这个类有属性
id
表示节点编号,属性distFromStart
表示开始结点到当前结点的路径长度。在BFS
过程中我们用这个类的对象来代表每一个节点,加入到队列中遍历。
- 我们可以用一个类来记录这个信息,这个类有属性
- 上述条件中我们可以知道的是当前结点到邻居节点的权重,那么我们如何知道开始节点到当前节点的路径长度?
- 这样我们在遍历的时候弹出队列中的节点就可以得到开始结点到当前结点的路径长度了,在利用建立的邻接表可以根据这个节点的
id
找到它的每一个邻居,由此可以完成判断。
- 初始化一个
class Solution {
//表里每一个节点的类型是int[],该数组长度为2,0索引存放目的节点,1索引存放到目的节点的权重。
List<int[]>[] graph;
//实时记录每个结点到起始结点的最短距离,在每次寻找邻居节点的时候用来判断当前路径走这条路是否可行
int[] distFromStart;
public int networkDelayTime(int[][] times, int n, int k) {
//用邻接表法建图
built(times, n);
//用dijkstra计算每个结点和起始结点的最短路径
dijkstra(graph, k);
//找最大时间,代表所有结点收到信号的最短时间
int maxTime = 0;
//从1开始,结点编号没有0
for (int i = 1; i < distFromStart.length; i++) {
//如果长度还是101则表示路径长度没有更新,即表示这个节点无法到达,直接返回-1
if (distFromStart[i] == 101) return -1;
if (distFromStart[i] > maxTime) maxTime = distFromStart[i];
}
return maxTime;
}
void built(int[][] times, int n) {
//由于节点的编号是从1开始的,所以初始化大小n+1
graph = new ArrayList[n + 1];
for (int i = 0; i < n + 1; i++) {
graph[i] = new ArrayList<>();
}
for (int[] time : times) {
int start = time[0];
int to = time[1];
int weight = time[2];
graph[start].add(new int[]{to, weight});
}
}
//记录每个几点的基本信息,即编号和与起始结点的路径长度
static class Node {
int id;
int distFromStart;
public Node(int id, int distFromStart) {
this.id = id;
this.distFromStart = distFromStart;
}
}
public void dijkstra(List<int[]>[] graph, int start) {
//优先权队列对 从开始结点到当前结点的距离distFromStart 从小到大排序
PriorityQueue<Node> priorityQueue = new PriorityQueue<>((a, b) -> a.distFromStart - b.distFromStart);
distFromStart = new int[graph.length];
//权重最大为100,初始化为一个大于它的数
Arrays.fill(distFromStart, 101);
distFromStart[start] = 0;
priorityQueue.offer(new Node(start, 0));
while (!priorityQueue.isEmpty()) {
Node Node = priorityQueue.poll();
int curNodeID = Node.id;//当前结点id
int curDistFromStart = Node.distFromStart;//当前路径下开始节点到当前结点的路径长度
if (curDistFromStart > distFromStart[curNodeID]) {
// 表示已经有一条更短的路径到达 curNode 节点了
continue;
}
for (int[] neighbor : graph[curNodeID]) {
int weight = neighbor[1];
int nextNodeID = neighbor[0];
if (curDistFromStart + weight < distFromStart[nextNodeID]) {
distFromStart[nextNodeID] = curDistFromStart + weight;
priorityQueue.offer(new Node(nextNodeID, curDistFromStart + weight));
}
}
}
}
}
- 上面我们使用优先权队列保存每个而不是普通队列,这样效率更高。因为我们要找的是开始节点到当前节点的最短路径,在找这条最短路径的过程中我们如果能够保证每次都从更短的子路径开始找,则最终的找到的路径是最短路径的概率会更高。这是一种贪心策略。
- 在
BFS
遍历图的时候我们不需要使用visited
数组保证我们不走回头路,因为我们只有在找到更短路径的时候才回把节点加入队列中,如果走回头路的话这个路径肯定比数组中记录的更长。