leetcode743-网络延迟时间

585 阅读4分钟

题目描述

有 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->Msi表示 已知最短距离的集合 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
        }