熬了我两天的一个题,可算是写出来了
题目
有 N 个网络节点,标记为 1 到 N。
给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v, w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。
现在,我们向当前的节点 K 发送了一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。
注意:
N 的范围在 [1, 100] 之间。
K 的范围在 [1, N] 之间。
times 的长度在 [1, 6000] 之间。
所有的边 times[i] = (u, v, w) 都有 1 <= u, v <= N 且 1 <= w <= 100。
分析
这道题用Dijkstra算法,用来求最短路径, 而且我还把最小生成数和单源最短路径搞混了 😦 。。。。。
单源最短路径,那单源是啥,按本题目来说,单源指的是从k节点到其它所有节点,单源最短路径就是从k节点到其它所有节点的最短路径啦。
这个题的大概思路就是,我们找到k节点到其它所有n-1个节点的最短路径,之后在最短路径里面挑出来一个最长路径值,即我们这道题的答案。当然如果我们的k节点和某一个节点x并不能连通,那么题目要求我们返回-1。
好啦大概思路很清楚了,接下来就是写代码啦,我在写代码的时候有两个错,导致我一直提交不成功,第一个错还是比较严重的。还是先说用dijkstra来求最短路径吧。
我们从 节点k 出发,向各个节点发送信号,我们把和节点k相连的各个节点及其之间的距离记录下来,记录下来总得有个地方存把,那就存到优先队列pq里面吧。既然是存入了优先队列里面,肯定会有一个大小顺序,我们按照距离的大小来排放,因为是要取小的距离,所以按照距离从小到大排放,这里存入优先队列的原因是,我每次在取节点的时候,都是要先取距离最小的边。别忘了我们在查看k节点的时候还要保存一下距离,即 k 到某节点的最小距离。
现在我们的队列pq不是空的了,里面有了一些节点,同时我们把k节点也查看完毕了,接下来就要继续查看下一个节点。那么这么多节点,我要查看哪一个,查看同k几点相连的,且距离最小的,也就是保存在队列pq中的第一个节点。
取出队列pq中的第一个节点,然后查看这个节点相连的临节点及其距离,将其放入队列pq中,同时将其距离更新保存,(为什么说是更新保存,因为可能当前节点的临节点m它同时也是上次k节点的临节点,这时候需要比较一下,这两个距离,哪个更加小)。
上面的节点查看完了,那就继续从队列pq中取出节点查看,(要注意的是,我们每次从队列中取出节点的时候,需要将其标记一下,以免之后重复查看),我们从pq中取出节点,还需要判断一下,这个节点我们是否已经查看过了,如果查看过了就继续取下一个,直到取到没有查看过的节点,之后就重复上面的步骤 存入队列,更新距离。
直到我们的队列为空。当我们队列为空证明我们已经查看完了所有的节点,当我们查看过的这些节点数量正好=N,证明k到每个节点都可以连通,我们就可以选出最短距离中最大的距离返回。 如果!=N那么证明我们的节点k和其中的某些节点无论怎样都不能连通,即不能使所有节点收到信号,返回-1。
代码
本来也想偷懒在网上找到解题报告代码抄一下,结果没有找到看着舒服的代码,还是自己憋了两天,写出来的这些,,,很是乱七八糟的代码, 不过还好通过了吖,还是很开心的 😃
class Solution {
public static int networkDelayTime(int[][] times, int N, int K) {
int n = N; int k = K;int max = Integer.MAX_VALUE;
int[] distance = new int[n+1]; // 保存k点到各个点的最小距离
Arrays.fill(distance , -1); // k点到各点距离初始化为-1 不要是0啊 因为题中数据有距离为0的
int[][] graph = new int[n+1][n+1]; // 图结构
// 初始化图, 每个点到每个点的距离初始为无穷大, 之后遍历times数组,在graph中保存i节点到j节点的距离
for (int i = 0; i < n + 1; i++) Arrays.fill(graph[i],max);
for(int[] time : times)graph[time[0]][time[1]] = time[2];
List<Integer> al = new ArrayList<Integer>(); //保存已经选择过了的节点
// 优先队列, 维持所选边按距离从小到大的排列 每次取都先取出边最小的
PriorityQueue<Integer[]> pq = new PriorityQueue<Integer[]>(new Comparator<Integer[]>() {
@Override
public int compare(Integer[] o1, Integer[] o2) {
return o1[1] - o2[1];
}
});
// 优先队列初始化, 先把k点放入al集合中, 然后将和k节点相连的节点和距离一同记录
al.add(k);
putToPQ(graph,al,pq,distance,k);
while(pq.size() != 0) {
//取出堆顶, 堆顶的距离一定是最小的,同时需要判断堆顶的节点是否已经查看过了
int index = pq.poll()[0];
boolean flag = true;
while (al.contains(index)) {// 当堆顶节点已经被查看过了,就绕过去
if (pq.size() == 0) { // 防止队列已经为空
flag = false;
break;
}
index = pq.poll()[0];
}
if (flag) al.add(index);
// 查看新的节点,并将其临节点及距离一同更新 放入队列。
putToPQ(graph, al, pq, distance, index);
}
// 当最后的al集合包含全部数字 也就是al.size=n时, 证明有解,找出其中最长距离即可 否则无解-1
if (al.size() == n){
int result = 0;
for(int i : distance)result = Math.max(result,i);
return result;
}
return -1;
}
// 查看index节点, 将其临节点同其距离放入队列中,更新启距离。
public static void putToPQ(int[][] graph, List<Integer> al, PriorityQueue<Integer[]> pq, int[] distance, int index){
for (int i = 0; i < graph.length; i++) {
if (graph[index][i] != Integer.MAX_VALUE && !al.contains(i)) {
Integer[] temp = new Integer[2];
temp[0] = i;
if (distance[index] == -1) temp[1] = graph[index][i];
else if (distance[i] == -1 ) temp[1] = graph[index][i]+distance[index];
else temp[1] = Math.min(distance[i],graph[index][i]+distance[index]);
distance[i] = temp[1];
pq.add(temp);
}
}
}
}
注意
这里来说我上面犯的俩错误,
- 第一个 我第一次写代码的时候并没有意识到,我每次取节点的时候要取距离最小的,也就是我没有意识要用优先队列,看别人代码很是不理解为什么他们要用优先队列??? 之后坚持写自己的错误代码。。。。。最后是因为我又看了一遍单源最短路径算法,然后我终于意识到,我有多傻。。。
- 第二个 就是我们上面会有一个数组distance,用来保存k节点到各个节点的最短路径,它肯定会有初值的是吧,如果我们不做特殊指定的话,数组的初值就是0对吧,想想也没有什么毛病,但是!! 测试数据中,有两点之间距离为0的情况。。。。。。。这也让我很是傻眼,面对着我代码,心里其实挺崩溃,我该怎么改,哪儿有错嘛,最后还是仔细看了下测试数据(那组测试数据相当长。。。)将初值改为了-1。 ok 通过啦。