[LeetCode] Network Delay Time 网络延迟时间
There are N network nodes, labelled 1 to N.
Given times, a list of travel times as directededges 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.
Example 1:

Input: times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2
Output: 2
Note:
Nwill be in the range[1, 100].Kwill be in the range[1, N].- The length of
timeswill be in the range[1, 6000]. - All edges
times[i] = (u, v, w)will have1 <= u, v <= Nand0 <= w <= 100.
这道题给了我们一些有向边,又给了一个结点K,问至少需要多少时间才能从K到达任何一个结点。这实际上是一个有向图求最短路径的问题,求出K点到每一个点到最短路径,然后取其中最大的一个就是需要的时间了。可以想成从结点K开始有水流向周围扩散,当水流到达最远的一个结点时,那么其他所有的结点一定已经流过水了。最短路径的常用解法有迪杰斯特拉算法 Dijkstra Algorithm, 弗洛伊德算法 Floyd-Warshall Algorithm, 和贝尔曼福特算法 Bellman-Ford Algorithm,其中,Floyd 算法是多源最短路径,即求任意点到任意点到最短路径,而 Dijkstra 算法和 Bellman-Ford 算法是单源最短路径,即单个点到任意点到最短路径。这里因为起点只有一个K,所以使用单源最短路径就行了。这三种算法还有一点不同,就是 Dijkstra 算法处理有向权重图时,权重必须为正,而另外两种可以处理负权重有向图,但是不能出现负环,所谓负环,就是权重均为负的环。为啥呢,这里要先引入松弛操作 Relaxtion,这是这三个算法的核心思想,当有对边 (u, v) 是结点u到结点v,如果 dist(v) > dist(u) + w(u, v),那么 dist(v) 就可以被更新,这是所有这些的算法的核心操作。Dijkstra 算法是以起点为中心,向外层层扩展,直到扩展到终点为止。根据这特性,用 BFS 来实现时再好不过了,注意 while 循环里的第一层 for 循环,这保证了每一层的结点先被处理完,才会进入进入下一层,这种特性在用 BFS 遍历迷宫统计步数的时候很重要。对于每一个结点,都跟其周围的结点进行 Relaxtion 操作,从而更新周围结点的距离值。为了防止重复比较,需要使用 visited 数组来记录已访问过的结点,最后在所有的最小路径中选最大的返回,注意,如果结果 res 为 INT_MAX,说明有些结点是无法到达的,返回 -1。普通的实现方法的时间复杂度为 O(V2),基于优先队列的实现方法的时间复杂度为 O(E + VlogV),其中V和E分别为结点和边的个数,这里多说一句,Dijkstra 算法这种类贪心算法的机制,使得其无法处理有负权重的最短距离,还好这道题的权重都是正数,参见代码如下:
解法一:
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
int res = ;
vector<vector<int>> edges(, vector<int>(, -));
queue<int> q{{K}};
vector<int> dist(N + , INT_MAX);
dist[K] = ;
for (auto e : times) edges[e[]][e[]] = e[];
while (!q.empty()) {
unordered_set<int> visited;
for (int i = q.size(); i > ; --i) {
int u = q.front(); q.pop();
for (int v = ; v <= ; ++v) {
if (edges[u][v] != - && dist[u] + edges[u][v] < dist[v]) {
if (!visited.count(v)) {
visited.insert(v);
q.push(v);
}
dist[v] = dist[u] + edges[u][v];
}
}
}
}
for (int i = ; i <= N; ++i) {
res = max(res, dist[i]);
}
return res == INT_MAX ? - : res;
}
};
下面来看基于 Bellman-Ford 算法的解法,时间复杂度是 O(VE),V和E分别是结点和边的个数。这种算法是基于 DP 来求全局最优解,原理是对图进行 V - 1 次松弛操作,这里的V是所有结点的个数(为啥是 V-1 次呢,因为最短路径最多只有 V-1 条边,所以只需循环 V-1 次),在重复计算中,使得每个结点的距离被不停的更新,直到获得最小的距离,这种设计方法融合了暴力搜索之美,写法简洁又不失优雅。之前提到了,Bellman-Ford 算法可以处理负权重的情况,但是不能有负环存在,一般形式的写法中最后一部分是检测负环的,如果存在负环则报错。不能有负环原因是,每转一圈,权重和都在减小,可以无限转,那么最后的最小距离都是负无穷,无意义了。没有负环的话,V-1 次循环后各点的最小距离应该已经收敛了,所以在检测负环时,就再循环一次,如果最小距离还能更新的话,就说明存在负环。这道题由于不存在负权重,所以就不检测了,参见代码如下:
解法二:
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
int res = ;
vector<int> dist(N + , INT_MAX);
dist[K] = ;
for (int i = ; i < N; ++i) {
for (auto e : times) {
int u = e[], v = e[], w = e[];
if (dist[u] != INT_MAX && dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
}
}
}
for (int i = ; i <= N; ++i) {
res = max(res, dist[i]);
}
return res == INT_MAX ? - : res;
}
};
下面这种解法是 Bellman Ford 解法的优化版本,由热心网友旅叶提供。之所以能提高运行速度,是因为使用了队列 queue,这样对于每个结点,不用都松弛所有的边,因为大多数的松弛计算都是无用功。优化的方法是,若某个点的 dist 值不变,不去更新它,只有当某个点的 dist 值被更新了,才将其加入 queue,并去更新跟其相连的点,同时还需要加入 HashSet,以免被反复错误更新,这样的时间复杂度可以优化到 O(E+V)。Java 版的代码在评论区三楼,旅叶声称可以 beat 百分之九十多,但博主改写的这个 C++ 版本的却只能 beat 百分之二十多,hmm,因缺斯汀。不过还是要比上面的解法二快很多,博主又仔细看了看,发现很像解法一和解法二的混合版本哈,参见代码如下:
解法三:
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
int res = ;
unordered_map<int, vector<pair<int, int>>> edges;
vector<int> dist(N + , INT_MAX);
queue<int> q{{K}};
dist[K] = ;
for (auto e : times) edges[e[]].push_back({e[], e[]});
while (!q.empty()) {
int u = q.front(); q.pop();
unordered_set<int> visited;
for (auto e : edges[u]) {
int v = e.first, w = e.second;
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (visited.count(v)) continue;
visited.insert(v);
q.push(v);
}
}
}
for (int i = ; i <= N; ++i) {
res = max(res, dist[i]);
}
return res == INT_MAX ? - : res;
}
};
讨论:最后再来说说这个 Floyd 算法,这也是一种经典的动态规划算法,目的是要找结点i到结点j的最短路径。而结点i到结点j的走法就两种可能,一种是直接从结点i到结点j,另一种是经过若干个结点k到达结点j。所以对于每个中间结点k,检查 dist(i, k) + dist(k, j) < dist(i, j) 是否成立,成立的话就松弛它,这样遍历完所有的结点k,dist(i, j) 中就是结点i到结点j的最短距离了。时间复杂度是 O(V3),处处透露着暴力美学。除了这三种算法外,还有一些很类似的优化算法,比如 Bellman-Ford 的优化算法- SPFA 算法,还有融合了 Bellman-Ford 和 Dijkstra 算法的高效的多源最短路径算法- Johnson 算法,这里就不过多赘述了,感兴趣的童鞋可尽情的 Google 之~
Github 同步地址:
https://github.com/grandyang/leetcode/issues/743
参考资料:
https://leetcode.com/problems/network-delay-time/description/
https://leetcode.com/problems/network-delay-time/discuss/109982/C++-Bellman-Ford
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Network Delay Time 网络延迟时间的更多相关文章
- [LeetCode] Network Delay Time 网络延迟时间——最短路算法 Bellman-Ford(DP) 和 dijkstra(本质上就是BFS的迭代变种)
There are N network nodes, labelled 1 to N. Given times, a list of travel times as directed edges ti ...
- [LeetCode] 743. Network Delay Time 网络延迟时间
There are N network nodes, labelled 1 to N. Given times, a list of travel times as directededges tim ...
- 【LeetCode】743. Network Delay Time 解题报告(Python)
[LeetCode]743. Network Delay Time 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: ht ...
- Java实现 LeetCode 743 网络延迟时间(Dijkstra经典例题)
743. 网络延迟时间 有 N 个网络节点,标记为 1 到 N. 给定一个列表 times,表示信号经过有向边的传递时间. times[i] = (u, v, w),其中 u 是源节点,v 是目标节点 ...
- NFS - Network File System网络文件系统
NFS(Network File System/网络文件系统): 设置Linux系统之间的文件共享(Linux与Windows中间文件共享采用SAMBA服务): NFS只是一种文件系统,本身没有传输功 ...
- 以Network Dataset(网络数据集)方式实现的最短路径分析
转自原文 以Network Dataset(网络数据集)方式实现的最短路径分析 构建网络有两种方式,分别是网络数据集NetworkDataset和几何网络Geometric Network,这个网络结 ...
- SDN(Software Defined Network):软件定义网络----转载
SDN(Software Defined Network):软件定义网络 传统的网络转发行为: 1)逐设备单独控制,纯分布式控制. 2)控制面和转发面在同一个设备中,耦合紧密. 管理员无法直接操控转发 ...
- Docker-Bridge Network 03 自定义网络
本节介绍自定义bridge network的自定义网络. 1.前言2.创建自定义网络2.1 创建网络2.2 指定网段创建网络3.创建容器3.1 指定网络创建容器3.2 指定IP创建容器4.通信4.1 ...
- 如何解决ubuntu 12.04重启后出现waiting for network configuration和网络标志消失问题
如何解决ubuntu 12.04重启后出现waiting for network configuration和网络标志消失问题 作为菜鸟的我在学着设置网络后,重启电脑后显示 waiting forne ...
随机推荐
- HA集群heartbeat配置--Nginx
HA即(high available)高可用,又被叫做双机热备,用于关键性业务.简单理解就是,两台机器A和B,正常是A提供服务,B待命限制,当A宕机或服务宕掉,会切换至B机器继续提供服务.常用实现高可 ...
- CSS奇思妙想图形(心形、气泡三角形、切角、梯形、饼图等)
今天看到一篇不错文章,在原来CSS3图形创建基础上扩展了很多. 这里记录总结下 心形 原理:利用 圆形 和 正方形实现 HTML: <div class="heartShaped&qu ...
- RTMP消息详细介绍
本文继上篇简单分析了RTMP协议如何进行通信进一步详细分析RTMP的消息都有哪些,以及这些消息有什么作用. 一.RMTP消息 由上一篇文章可知RTMP 消息有分成两个部分,一个是头部,一个是有效负载. ...
- c语言最后一次作业
1.当初你是如何做出选择计算机专业的决定的? 我再来到大学之前,通过查询和询问,了解到当前计算机行业就业需求量较高,同时我对计算机的几年过去比较高了,在高中时期就有过在大学学习计算机行业的知识与专业的 ...
- git基本语法
基本用法(上) 一.实验说明 本节实验为 Git 入门第一个实验,可以帮助大家熟悉如何创建和使用 git 仓库. 二.git的初始化 在使用git进行代码管理之前,我们首先 ...
- 项目Beta冲刺Day3
项目进展 李明皇 今天解决的进度 完善了程序的运行逻辑(消息提示框等) 明天安排 前后端联动调试 林翔 今天解决的进度 向微信官方申请登录验证session以维护登录态 明天安排 继续完成维护登录态 ...
- 关于GPUImage的导入
对于GPUImage的使用方面,GitHub上已经非常详细了,就不一一赘述了,但是对于项目的导入来说,最好的方式是 1.下载GPUImage并解压 2.打开压缩包后如图 3.打开终端,cd到此目录 4 ...
- WPF自学入门(十)WPF MVVM简单介绍
前面文章中,我们已经知道,WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时,WPF会自动发出通知去更新UI. 我们不管 ...
- redux的知识点
Redux: Redux 是针对 JavaScript应用的可预测状态容器 就是用来管理数据的.stroe 保存数据action领导 下达命令reducer员工 执行命令 下载命令: npm ins ...
- 【转】optach学习
[转自:https://yq.aliyun.com/articles/28007,仅作学习用途] 摘要: Opatch 是oracle公司开发的安装,卸载,检测patch冲突的工具,管理oracle所 ...