上线前夜,发现致命bug,情急之下直接把服务器上的组件文件给删了,结果整个项目瘫痪,直接收拾东西回了老家,连夜跑路
刚看到个贴子,说网友上线前夜发现致命bug,情急之下直接把服务器组件删了,结果整个项目瘫痪,第二天收拾行李回老家。哎,这剧情比电视剧都刺激。
我觉得这事吧,说“年轻气盛”其实是真。技术出问题不可怕,可怕的是慌。删文件那一刻,不只是删了项目,也删掉了信任。
网友评论里有的笑他怂,有的替他惋惜,但说到底,这种经历谁年轻时没犯过点傻?只是有的人删错一行代码,有的人删的是整台服务器。
换个角度想,能留下这种教训,其实也是成长。技术能补,心态难练。以后真遇到坑,先稳住手,再找方案。跑路解决不了问题,只会让问题永远跟着你。
总的来说,犯错不可怕,怕的是不敢面对。技术人嘛,摔一跤爬起来继续干,下次就不会再删“整个世界”了。【备注:文末可领最新资料】
算法题:边权重均等查询
假设你在写一个地图、社交关系、迷宫之类的功能,图里每条边的权重都是一样的,比如走一条路都是 1 分钟,或者两个人之间的关系强度都算 1。然后产品跟你说:我要经常查「从 A 点到 B 点的最短距离」,你怎么搞?
这就是典型的“边权重均等查询”问题。
题目大概长这样
有一个无向图(当然你也可以改成有向图),有 n 个点、m 条边,每条边的权重都一样,比如都是 1。然后有 q 次询问,每次给你两个点 u、v,问从 u 到 v 的最短路径长度,不可达就返回 -1。
用公式写就很枯燥,我们用大白话:图里每条边都算一步,问你从一个点走到另一个点,最少要走几步。
很多同学第一反应是“上 Dijkstra 啊”,毕竟最短路径标配,但是这里有个关键点:所有边权重都相同。这时候再上 Dijkstra 就有点杀鸡用牛刀了,还慢。
核心思路就一句话:当它是“无权图”,直接 BFS
因为每条边权重完全一样,所以最短路就等价于“最少边数的路径”。
图论里这就是标准的“无权图最短路”,套路是:从起点做一遍 BFS(广度优先搜索),第一次到达每个点时走的步数,就是从起点到这个点的最短距离。
直观一点想:BFS 是按“层”一圈一圈扩散的:
距离起点 1 步的点会先被访问到 再是 2 步的 再是 3 步的……
因为每条边花费一样,你不可能绕远路更便宜,所以第一次遇到某个点时的层数就是最优答案。
时间复杂度也很好算:对一个起点跑一遍 BFS,复杂度是 O(n + m)。如果所有查询的起点是同一个,比如“从 1 号点到所有点的距离”,你只需要跑一遍 BFS,后面查表就行。
那如果是多次查询怎么办?
这里有几种常见情况,简单提一下思路:
所有查询的起点一样(比如都从 1 出发) 最舒服,预处理: – 从这个起点 BFS 一次,算出 dist 数组 – 每次查询 (1, v) 直接输出 dist[v],O(1)。
图是“树”(n 个点、n-1 条边,且连通) 这时边权都一样,u 到 v 的距离其实就是:
dist(u, v) = depth[u] + depth[v] - 2 * depth[lca(u, v)]只要你预处理出每个点的深度和 LCA(最近公共祖先),每次查询就是 O(1) 或 O(log n)。 这部分细节可以单开一篇,这里先不展开。图是一般图,起点各种各样,查询又特别多 这就没一个完美通吃的方案了,常见做法是: – 观察是否有“热点节点”:比如很多查询的起点重复,那就对这些点做 BFS 预处理; – 或者查询总量不大,用 BFS 一问一算也未必不行。
今天主角还是“最基础 BFS 解无权最短路”,先把这个吃透,再考虑各种优化花样。
Java 实现示例
下面这段代码实现的是: 给定一个无向图,从某个起点 start 做一遍 BFS,返回 start 到所有点的最短距离; 如果某个点不可达,距离就是 -1。
import java.util.*;
publicclassEqualWeightShortestPath{
// n: 点的数量(默认点编号 0 ~ n-1)
// edges: 边列表,每条边是 [u, v]
publicstatic List<List<Integer>> buildGraph(int n, int[][] edges) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
for (int[] e : edges) {
int u = e[0];
int v = e[1];
graph.get(u).add(v);
graph.get(v).add(u); // 如果是有向图,这行删掉
}
return graph;
}
// 从 start 出发的 BFS,返回 dist 数组
// dist[i] = 从 start 到 i 的最少步数,不可达则为 -1
publicstaticint[] bfsShortestPath(List<List<Integer>> graph, int start) {
int n = graph.size();
int[] dist = newint[n];
Arrays.fill(dist, -1); // -1 表示还没到过
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(start);
dist[start] = 0;
while (!queue.isEmpty()) {
int u = queue.poll();
for (int v : graph.get(u)) {
if (dist[v] == -1) { // 第一次到这个点
dist[v] = dist[u] + 1; // 多走一步
queue.offer(v);
}
}
}
return dist;
}
// 小示例:构造一个图,做几次查询
publicstaticvoidmain(String[] args){
int n = 6;
int[][] edges = {
{0, 1},
{0, 2},
{1, 3},
{2, 3},
{3, 4},
{4, 5}
};
List<List<Integer>> graph = buildGraph(n, edges);
int start = 0;
int[] dist = bfsShortestPath(graph, start);
// 查询从 0 到每个点的距离
for (int i = 0; i < n; i++) {
System.out.println("0 -> " + i + " 的最短步数 = " + dist[i]);
}
// 如果你有多次查询 (u, v),并且起点不是固定的,
// 可以在这里根据情况再调 bfsShortestPath(graph, u)。
}
}
这段代码有几个小点注意一下:
我用的是 0 ~ n-1 编号,如果你题目是 1 ~ n,就要稍微减个 1; dist 初始化成 -1,很方便判断哪些点没被访问到; 队列用的是 ArrayDeque,比LinkedList更轻量一点。
小结一下思路
这类“边权重均等查询”问题,其实就是在提醒你:别条件反射用 Dijkstra。
只要边的权重都是一样的(哪怕不是 1,而是都等于 5,其实也一样),最短路就退化成“最少边数”,直接 BFS 就行,代码简单,性能也更好。
等你再遇到树形结构、海量查询的时候,再往上加 LCA、分层图之类的优化,就更好玩了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html