题目描述
有 N 个网络节点,标记为 1 到 N。
给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v, w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。
现在,我们从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。
思考
首先我们需要弄清楚这道题要的是什么。
看示例的输出,我们知道,从K出发的信号是并行的, 也就是说从节点 K出发的信号,在到达节点 A的过程种,并不影响信号向节点 B传递 (不然的话,示例的最短时间应该是3)。
然后我们要算最短时间,那么我们需要知道的是从 K到其他节点的时间,然后从其中找到最长的一个,比如说是 M, 当从 K出发的信号到 M的时候,其他所有的节点必然是已经到达了。
因为从 K到其他的节点的方式可能有多种, 我们需要记录到其他的节点的最短时间 t, 因为在t时间的时候,信号已经到达了该节点,比其他路径更快。
最后 我们明确一下目标,计算从节点 K到其他所有节点的最短时间time, 比较所有的 time,取最长的一个即是我们想要的答案。
问题的核心就是 求节点K到其他任意节点的最短时间。
我们可以使用迪杰斯特拉(Dijkstra)算法。 下面我们先来介绍一下这个算法(耐心看下去,其实思想很简单)。
迪杰斯特拉(Dijkstra)算法
迪杰斯特拉算法的一般思想是 以当前已知最短距离的可达点为起始点,遍历它们所能到达的节点,计算这些可达点到源点K的距离,取其中最短的一个,则为该可达点到源点的最短距离。 然后重复上述过程。
注意 距离不可以为负数是该算法的前提!。
有点拗口,我们细化一下。
假设
S已知最短距离的可达点及到达点K的距离集合 ,V为未知最短距离的点的集合。
以示例为例子
times = [
[2,1,1],
[2,3,1],
[3,4,1]
],
N = 4, K = 2
初始值
S={
2: 0 // key 表示可达的节点 value表示距离
}
V={1, 3, 4}
第一次
遍历 S中的节点 我们发现 2可以到达V的节点有 1,3, 两者到源点 K的距离为
[2, 1] = 1
[2, 3] = 1
我们取节点3 (两者相同,取哪个均可)加入到 S中。
此时
S={
2: 0,
3: 1,
}
V={1, 4}
第二次
遍历 S中的节点, 可到达 V的节点有 1, 4。 两者到源点 K的距离为
[2, 1] = 1
[3, 4] = 2
取节点 1加入 S中
S={
2: 0,
3: 1,
1: 1
}
V={ 4}
第三次
只有 4 ,
S = {
2: 0,
3: 1,
1: 1,
4: 2
}
V={}
遍历结束。
这里面你可能会有个疑问, 为啥每次遍历出来的最短距离,即为该可达点到源点的最短距离。
证明
我们可以来做个假设, 比如说点 M, 存在一条路径,即k->s1->s2->....->x->M。
si表示 已知最短距离的集合 S中的点, x表示不在 S中的点, 那么我们可以知道的是
从 K到点 x的距离一定会短于从 K到点 M的距离。那么在找到M之前, x一定是位于集合 S中了, 因此假设是错误的。
现在思想和算法我们都了解了 下面看一下怎么实现吧。
代码
方法一
function dijkstra(times, N, K) {
let timeNode = {}; // 存储所有节点和节点的可达点即距离
let S = new Map(); // 已知最短距离的点 和 最短距离 集合
S.set(K, 0);
for (let i = 0; i < times.length; i++) {
let from = times[i][0]; // 信号出发点
let to = times[i][1]; // 信号到达点
let time = times[i][2]; // 从出发点到到达点所消耗时间
if (!timeNode[from]) {
timeNode[from] = {};
}
timeNode[from][to] = time;
}
// console.warn(timeNode, Object.keys(S));
minTimeNode();
function minTimeNode() {
let minTime = -1;
let minNode = -1;
for(let [key, value] of S) {
for(let j in timeNode[key]) {
let node = parseInt(j);
if(S.has(node)) { // 如果该可达点已经存在于S 则说明已求出最短距离了, 不用执行该节点
continue;
}
let tempTime = timeNode[key][j] + value; // 节点node到源点K的距离
if (minTime === -1 || minTime >= tempTime) {
minTime = tempTime;
minNode = node;
}
}
}
if (minTime === -1) { // 说明存在闭环 或者所有节点已经遍历完成 跳出递归
return
}
S.set(minNode, minTime); // 将已计算得到的最小距离的点加入集合S
minTimeNode();
}
let maxTime = -1;
if (S.size < N) { // 说明存在某点 从源点无法到达
return -1
}
for (let [key, value] of S) {
if (maxTime < value){
maxTime = value;
}
}
return maxTime
}
方法二
在方法一中 我们做了多次的重复计算,可以用一个集合V存储我们计算的数据,这样每次只要计算最新加入的节点及该点的可达距离, 然后遍历集合 V,取出最小的那个点,然后从 V中删除即可
function dijkstra(times, N, K) {
let timeNode = {}; // 存储所有节点和节点的可达点即距离
let S = new Map(); // 已知最短距离的点 和 最短距离 集合
S.set(K, 0);
let V = new Map(); // 未知最短距离的点即计算出的临时距离存储
for (let i = 0; i < times.length; i++) {
let from = times[i][0]; // 信号出发点
let to = times[i][1]; // 信号到达点
let time = times[i][2]; // 从出发点到到达点所消耗时间
if (!timeNode[from]) {
timeNode[from] = {};
}
timeNode[from][to] = time;
}
// console.warn(timeNode, Object.keys(S));
minTimeNode(K, 0);
function minTimeNode(newAddNode, newTime) {
let minTime = -1;
let minNode = -1;
for (let i in timeNode[newAddNode]) {
let node = parseInt(i);
if (S.has(node)) {
continue;
}
let tempTime = newTime + timeNode[newAddNode][node];
if (!V.has(node)) { // 如果V中没有该点的距离 则加入 如果有 则比较跟之前距离的大小
V.set(node, tempTime);
} else if (V.get(node) > tempTime) {
V.set(node, tempTime);
}
}
for (let [key, value] of V) { // 遍历取出最小的距离
if (minTime === -1 || value < minTime) {
minTime = value;
minNode = key;
}
}
if (minTime == -1) { // -1 表明存在闭环或者全部已经计算完成
return;
}
V.delete(minNode); // 删除已经计算出最短距离的点
S.set(minNode, minTime);
minTimeNode(minNode, minTime);
}
let maxTime = -1;
if (S.size < N) { // 说明存在某点 从源点无法到达
return -1
}
for (let [key, value] of S) {
if (maxTime < value){
maxTime = value;
}
}
return maxTime
}