题目
LeetCode-网络延迟时间
有 n 个网络节点,标记为 1 到 n。
给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。
现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1 。
示例 1:
输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
输出:2
示例 2:
输入:times = [[1,2,1]], n = 2, k = 1
输出:1
示例 3:
输入:times = [[1,2,1]], n = 2, k = 2
输出:-1
提示:
1 <= k <= n <= 100
1 <= times.length <= 6000
times[i].length == 3
1 <= ui, vi <= n
ui != vi
0 <= wi <= 100
所有 (ui, vi) 对都 互不相同(即,不含重复边)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/network-delay-time
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
Dijkstra+优先队列
有优先队列存储有向边。[1,2]代表着到1这个点的距离是2。g[3]={{1,2}};代表着3到1的距离是2。有数组存储边更省空间。这样存储也是为了优先队列排序后,你知道哪个值对应着哪个点。如果是下面的枚举,就直接用二维数组存储就可以了。
- 时间复杂度:O(m log m),其中 m 是数组 times 的长度。适合稀疏图,顶点多的图。
- 空间复杂度:O(n+m)。
class Solution {
static public int networkDelayTime(int[][] times, int n, int k) {
int[] distances=new int[n+1];
ArrayList<int[]>[] g=new ArrayList[n+1];
for(int i=1;i<n+1;i++)
g[i]=new ArrayList<int[]>();
for(int[] x:times)
g[x[0]].add(new int[] {x[1],x[2]});
Arrays.fill(distances, 300);
distances[k]=0;
PriorityQueue<int[]> dis=new PriorityQueue<int[]>((a,b)->a[1]-b[1]); //优先队列初始化
dis.add(new int[] {k,0}); //将源点先放入队列中,然后通过循环更新周围的点
//上面都是初始化 g中存放这所有边的信息
//模板开始
while(!dis.isEmpty())
{
int[] p=dis.poll();
int index=p[0],p1=p[1]; //到达的点的索引
if(distances[index]<p1) //当前这个边没必要加入
continue;
for(int[] x:g[index]) //更新index周围的点,如果值更小,则更新distances数组
{
int y=x[0]; int d=distances[index]+x[1];
if(distances[y]>d)
{
distances[y]=d;
dis.add(new int[] {y,d});
}
}
}
//下面和模板无关,只是题目输出
distances[0]=-10;
int ans=Arrays.stream(distances).max().getAsInt();
return ans == 300 ? -1 : ans;
}
}
Dijkstra+枚举
这个枚举更适合稠密图,也就是边比较多的图。而且加入了vis数组,这个数组是存放一个点是否已经找到最短路径了。
时间复杂度:O(n2+m),其中 m 是数组times 的长度。边的数量对时间影响还可以
空间复杂度:O(n2),邻接矩阵需占用 O(n2)的空间。
class Solution
{
static public int networkDelayTime(int[][] times, int n, int k) {
int INF=Integer.MAX_VALUE/2; //因为后面会用到填充的值+另一个值,如果是最大值,那么就会变成负数。
boolean[] vis=new boolean[n+1];
int[] distances=new int[n+1];
int[][] g=new int[n+1][n+1];
for(int i=1;i<n+1;i++)
Arrays.fill(g[i], INF);
for(int[] x:times)
g[x[0]][x[1]]=x[2];
Arrays.fill(distances, 300);
distances[k]=0;
//模板开始
for(int num=0;num<n;num++)
{
int X=-1;
for(int i=1;i<n+1;i++)
if(!vis[i]&&(X==-1||distances[i]<distances[X]))
X=i;
vis[X]=true;
int min_val=distances[X];
for(int i=1;i<n+1;i++) //遍历和X有边的点
{
if(g[X][i]==INF) //X和这个点没有边
continue;
//如果比原来值更小,就更新distances数组
distances[i]=Math.min(distances[i],min_val+g[X][i]);
}
}
//模板结束
distances[0]=-10;
int ans=Arrays.stream(distances).max().getAsInt();
return ans == 300 ? -1 : ans;
}
}
Floyd
弗洛伊德是一种动态规划算法,稠密图效果最佳,边权可以是负数。对于稠密图,效率要高于执行Dijkstra算法。不适合大数据量计算
- 时间复杂度:O(n3);
- 空间复杂度:O(n2);
class Solution
{
static public int networkDelayTime(int[][] times, int n, int k) {
int INF=Integer.MAX_VALUE/2;
boolean[] vis=new boolean[n+1];
int[][] distances=new int[n+1][n+1];
for(int i=1;i<n+1;++i)
for(int j=1;j<n+1;++j)
{
if(i==j)
distances[i][j]=0;
else
distances[i][j]=INF;
}
for(int[] x:times)
distances[x[0]][x[1]]=x[2];
//模板开始
for(int p=1;p<n+1;p++)
for(int i=1;i<n+1;i++)
for(int j=1;j<n+1;j++)
distances[i][j]=Math.min(distances[i][j], distances[i][p]+distances[p][j]);
//模板结束
int ans=-1;
for(int i=1;i<n+1;i++)
ans=Math.max(ans, distances[k][i]);
return ans == INF ? -1 : ans;
}
}
SPFA
这里主要很有意思的一点就是邻接表实现用的4个数组。edge数组:edge[i]=5
代表着索引是i的边指向的是5。 head数组:head[i]=4
以点i为起点边的的下标是4。next[i]=4
只的是索引为i的边的下一个边的索引是4。w数组:w[i]=5
指向点i的这个边的权重是5。
- 时间复杂度:O(n*m);
- 空间复杂度:O(m);
class Solution
{
static public int networkDelayTime(int[][] times, int n, int k) {
int INF=Integer.MAX_VALUE/2;
boolean[] vis=new boolean[n+1];
int[] distances=new int[n+1];
int M=6000; //边的数量
int index=0;
int[] head=new int[n+1];int[] edge=new int[M];
int[] w=new int[M];int[] next=new int[M];
Arrays.fill(head, -1);
Arrays.fill(next, -1);
Arrays.fill(distances,INF);
//存图
for(int[] x:times)
{
int x0=x[0];
w[index]=x[2]; //村上这个边的权重
next[index]=head[x0];
head[x0]=index;
edge[index]=x[1];
++index;
}
//模板开始
Deque<Integer> q=new ArrayDeque<Integer>(); //两端都可以插入和删除
q.add(k);
distances[k]=0;
vis[k]=true;
while(!q.isEmpty())
{
int p=q.poll();
vis[p]=false;
for(int i=head[p];i!=-1;i=next[i])
{
int j=edge[i];
if(distances[j]>distances[p]+w[i])
{
distances[j]=distances[p]+w[i];
if(vis[j])
continue;
q.add(j);
vis[j]=true;
}
}
}
//模板结束
distances[0]=-10;
int ans=Arrays.stream(distances).max().getAsInt();
return ans == INF ? -1 : ans;
}
}
几种算法的比较
Dijsktra+堆优化 | Dijsktra+枚举 | Floyd | SPFA | |
---|---|---|---|---|
时间复杂度 | O(m* log m) | O(n2+m) | O(n3) | 最坏O(n*m) |
空间复杂度 | O(n+m) | O(n2) | O(n2) | O(m) |
耗时 | 8s | 3s | 9s | 5s |
内存 | 45.5 Mb | 45.4 Mb | 45.2 Mb | 46.5 Mb |
总结 | 适合稀疏图,不能负值 | 适合稠密图,不能负值 | 适合稠密图,可以负值,时间复杂度太高,三个循环 | 没有限制,可以负值是个不错的算法 |
这道题看起来内存差不多是因为这个图是稠密图,m接近n2。如果没有负值的图,边多用Dijsktra+枚举,顶点多边少用Dijsktra+堆优化。有负值直接SPFA,Floyd实现简单但是时空复杂度都太高了。