题目
一个局域网内有很多台电脑,分别标注为 0 ~ N-1 的数字。相连接的电脑距离不一样,所以感染时间不一样,感染时间用 t 表示。
其中网络内一台电脑被病毒感染,求其感染网络内所有的电脑最少需要多长时间。如果最后有电脑不会感染,则返回-1。
给定一个数组 times 表示一台电脑把相邻电脑感染所用的时间。
如图:path[i] = {i, j, t} 表示:电脑 i->j,电脑 i 上的病毒感染 j,需要时间 t。
输入描述
第一行输入一个整数N ,表示局域网内电脑个数 N ,1 ≤ N ≤ 200 ;
第二行输入一个整数M ,表示有 M 条网络连接;
接下来M行 ,每行输入为 i , j , t 。表示电脑 i 感染电脑j 需要时间 t 。(1 ≤ i , j ≤ N)
最后一行为病毒所在的电脑编号。
输出描述
输出最少需要多少时间才能感染全部电脑,如果不存在输出 -1
用例
| 输入 | 输出 | 说明 |
|---|---|---|
| 4 3 2 1 1 2 3 1 3 4 1 2 | 2 | 第一个参数:局域网内电脑个数N,1 ≤ N ≤ 200; 第二个参数:总共多少条网络连接 第三个 2 1 1 表示2->1时间为1 第六行:表示病毒最开始所在电脑号2 |
思考
求病毒在局域网内传播感染多台电脑的最短时间。电脑看成无向图中的节点,电脑 i 感染 电脑 j 所需时间 t 看成边的权重,求单源最短路径问题,即从病毒源开始访问所有节点的最短路径集合中求最大值就是题目答案。权重时间是大于 0 的,可以用 Dijkstra 算法求解。
算法过程
Dijkstra 算法过程描述
Dijkstra 算法是一种用于计算带权有向图中单源最短路径的经典算法,适用于边权非负的情况。以下是针对 "局域网病毒感染时间" 问题的算法详细步骤:
算法核心思想
从初始感染源出发,逐步扩展到所有可达节点,每次选择当前已知距离最小的节点进行处理,确保每个节点的最短路径被最早确定。
具体步骤
1. 输入处理与图构建
-
读取输入参数:获取电脑数量
N、网络连接数M、每条连接的感染时间t,以及初始感染源电脑编号。 -
构建邻接表:将输入转换为图的邻接表表示,其中每个节点
i对应一个列表,列表中存储所有从i出发的边(j, t),表示感染从i到j需要时间t。
2. 初始化距离数组
-
创建一个数组
dist,其中dist[j]表示从初始感染源到节点j的最短感染时间。 -
初始时,
dist[初始节点] = 0,其余节点的dist值设为无穷大(表示尚未可达)。
3. 优先队列初始化
-
使用优先队列(最小堆)存储待处理的节点,初始时队列中仅包含初始感染源节点,优先级为其距离值(0)。
4. 循环处理优先队列
-
取出当前最小距离节点:从优先队列中取出距离值最小的节点
u。 -
检查是否已处理:若当前取出的距离值大于
dist[u],说明该节点已被处理过,跳过。 -
遍历邻居节点:对于节点
u的每个邻居j,检查是否可以通过u缩短到j的距离:-
若
dist[j] > dist[u] + t(u,j),则更新dist[j] = dist[u] + t(u,j),并将j加入优先队列。
-
5. 结果判断
-
遍历所有节点的
dist值:-
若存在节点的
dist值仍为无穷大,说明无法感染所有电脑,返回-1。 -
否则,返回
dist数组中的最大值,即感染所有电脑所需的最少时间。
-
参考代码一(朴素版本)
function solution() {
const n = parseInt(readline());
const m = parseInt(readline());
// 邻接矩阵 or 邻接表?这里用邻接表更省空间
const adj = Array.from({ length: n }, () => []);
for (let i = 0; i < m; i++) {
const [u, v, t] = readline().split(' ').map(Number);
adj[u - 1].push([v - 1, t]); // 转为 0-based
}
const start = parseInt(readline()) - 1;
// 初始化
const dist = Array(n).fill(Infinity);
const visited = Array(n).fill(false);
dist[start] = 0;
// 主循环:执行 n 次
for (let iter = 0; iter < n; iter++) {
// Step 1: 找未访问中 dist 最小的节点 u
let u = -1;
let minDist = Infinity;
for (let i = 0; i < n; i++) {
if (!visited[i] && dist[i] < minDist) {
minDist = dist[i];
u = i;
}
}
// 如果找不到(剩余点不可达),提前退出
if (u === -1) break;
// 标记为已访问
visited[u] = true;
// Step 2: 用 u 松弛所有邻居
for (const [v, w] of adj[u]) {
if (!visited[v] && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
}
}
}
const maxTime = Math.max(...dist);
console.log(maxTime === Infinity ? -1 : maxTime);
}
const cases = [
`4
3
2 1 1
2 3 1
3 4 1
2`
];
let caseIndex = 0;
let lineIndex = 0;
const readline = (function () {
let lines = [];
return function () {
if (lineIndex === 0) {
lines = cases[caseIndex]
.trim()
.split("\n")
.map((line) => line.trim());
}
return lines[lineIndex++];
};
})();
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
});
参考代码二(最小堆)
// 手写最小堆(元素为 [distance, node])
class MinHeap {
constructor() {
this.heap = [];
}
push(val) {
this.heap.push(val);
this._bubbleUp(this.heap.length - 1);
}
pop() {
if (this.heap.length === 0) return null;
if (this.heap.length === 1) return this.heap.pop();
const min = this.heap[0];
this.heap[0] = this.heap.pop();
this._bubbleDown(0);
return min;
}
_bubbleUp(idx) {
while (idx > 0) {
const parent = Math.floor((idx - 1) / 2);
if (this.heap[idx][0] >= this.heap[parent][0]) break;
[this.heap[idx], this.heap[parent]] = [this.heap[parent], this.heap[idx]];
idx = parent;
}
}
_bubbleDown(idx) {
const n = this.heap.length;
while (true) {
let smallest = idx;
const left = 2 * idx + 1;
const right = 2 * idx + 2;
if (left < n && this.heap[left][0] < this.heap[smallest][0]) {
smallest = left;
}
if (right < n && this.heap[right][0] < this.heap[smallest][0]) {
smallest = right;
}
if (smallest === idx) break;
[this.heap[idx], this.heap[smallest]] = [this.heap[smallest], this.heap[idx]];
idx = smallest;
}
}
isEmpty() {
return this.heap.length === 0;
}
}
function solution() {
const n = parseInt(readline());
const m = parseInt(readline());
// 构建邻接表(0-based)
const adj = Array.from({ length: n }, () => []);
for (let i = 0; i < m; i++) {
const [u, v, t] = readline().split(' ').map(Number);
adj[u - 1].push([v - 1, t]);
}
const start = parseInt(readline()) - 1;
// Dijkstra 初始化
const dist = Array(n).fill(Infinity);
dist[start] = 0;
// 使用最小堆优化
const heap = new MinHeap();
heap.push([0, start]);
while (!heap.isEmpty()) {
const [currentDist, u] = heap.pop();
// 跳过过期条目(已有更短路径)
if (currentDist > dist[u]) continue;
// 松弛相邻节点
for (const [v, t] of adj[u]) {
const newDist = dist[u] + t;
if (newDist < dist[v]) {
dist[v] = newDist;
heap.push([newDist, v]);
}
}
}
const maxTime = Math.max(...dist);
console.log(maxTime === Infinity ? -1 : maxTime);
}
const cases = [
`4
3
2 1 1
2 3 1
3 4 1
2`
];
let caseIndex = 0;
let lineIndex = 0;
const readline = (function () {
let lines = [];
return function () {
if (lineIndex === 0) {
lines = cases[caseIndex]
.trim()
.split("\n")
.map((line) => line.trim());
}
return lines[lineIndex++];
};
})();
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
});
⚙️ 两种实现方式对比
| 特性 | 朴素 Dijkstra | 最小堆优化 Dijkstra |
|---|---|---|
| 数据结构 | 数组 + visited 标记 | 最小堆(优先队列) |
| 找最小节点方式 | 每次遍历所有节点 O(N) | 堆顶 O(1) 弹出 |
| 时间复杂度 | O(N²) | O((N + M) log N) |
| 空间复杂度 | O(N + M) | O(N + M) |
| 适用场景 | N ≤ 1000,稠密图(M ≈ N²) | N 较大,稀疏图(M ≈ N) |
| 代码复杂度 | ⭐ 简单 | ⭐⭐ 需实现堆 |
| 本题推荐 | ✅ 更优(N ≤ 200) | 可用,但无必要 |
2363

被折叠的 条评论
为什么被折叠?



