在实际开发中算法不是考试题而是一组解决具体问题的决策规则。同样一批数据排序可以用冒泡也可以用快速排序同一个地图导航可以走 Dijkstra也可以走 A*同一个质检任务可以写固定阈值也可以用 YOLO 做目标检测。算法选型最终会体现在执行时间、内存开销、结果稳定性和维护成本上。下面从分类开始把开发中常见的算法拆开梳理它们解决什么问题、原理是什么、关键参数怎么设置、最容易错在哪里覆盖排序、KMP、Dijkstra、动态规划、模拟退火、粒子群、PID、FOC、反向传播、YOLO、强化学习、BM25 和 RETE 等算法。1. 先建立算法分类观算法解决的是哪几类问题很多人在学算法时把主要精力放在“记住某道题的答案”上结果输入一变就不会做了。更好的办法是先建立一套分类观念拿到问题后先判断它属于哪一类再候选这一类里的典型算法最后结合数据规模和约束条件做验证。算法本质上不是代码片段而是问题类型到解决方案的映射。1.1 算法不是代码而是问题类型的映射举个例子给一组数字排序属于排序类问题候选方案有快速排序、堆排序、归并排序。在一个长文本里查找模式串属于字符串匹配类问题候选方案有 KMP、BM。在带权图中找最短路径属于图论类问题候选方案有 Dijkstra、Bellman-Ford。在有限资源下最大化收益属于优化类问题候选方案有贪心、动态规划、回溯搜索。用历史数据预测未来属于机器学习类问题候选方案有线性模型、决策树、神经网络。这种分类能力比背代码更重要因为真实项目很少直接告诉你“这里应该用 KMP”它只会给你一段日志、一批数据和一组性能指标。1.2 八类常见算法速览算法类别要解决的核心问题典型算法典型场景基础数据结构与排序查找数据如何组织、有序、快速命中快速排序、堆排序、二分查找、哈希表订单列表、TopN、去重字符串匹配在文本中定位模式串KMP、BM文本编辑器、日志匹配图论与路径搜索在关联关系中求最短路径或可达性Dijkstra、A*、DFS/BFS地图导航、任务调度经典优化在有限选择中求最优解贪心、动态规划、回溯剪枝背包、路径规划、排课启发式优化在搜索空间过大时求近似最优解模拟退火、粒子群组合优化、参数调优工程控制与信号处理让设备输出稳定或完成信号变换PID、FOC、重采样、图像增强电机控制、音频处理、ISP机器学习与深度学习从样本中学习规律并泛化反向传播、CNN、YOLO、强化学习分类、检测、自动决策搜索引擎与规则引擎相关性排序和规则匹配BM25、RETE搜索召回、规则风控实际项目往往不是单一算法而是多个算法组合。搜索系统会先用 BM25 召回候选文档再用排序模型精排AGV 导航会先用 A* 搜索路径再用 PID 控制底盘按路径运动。1.3 为什么顺序是“分类 - 候选 - 约束 - 验证”正确做法是先给问题分类再列出候选算法再根据约束条件筛选最后用最小用例验证。约束条件通常包括数据规模是几千条还是几亿条。实时性是离线计算还是在线响应。精确性必须全局最优还是近似解可接受。硬件资源CPU、GPU、内存、显存限制。维护成本这个算法团队是否能长期维护。这套流程可以直接用在技术方案评审里避免一上来就写代码。2. 数据结构与经典算法排序、字符串匹配与快速幂2.1 快速排序平均复杂度要牢记最坏情况也要警惕快速排序的平均时间复杂度是 O(n log n)在大多数语言的内置排序实现中都占据核心地位。它通过分治策略把数组拆成小于基准值和大于等于基准值的两部分再递归排序。public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot partition(arr, left, right); quickSort(arr, left, pivot - 1); quickSort(arr, pivot 1, right); } private static int partition(int[] arr, int left, int right) { int pivot arr[right]; int i left; for (int j left; j right; j) { if (arr[j] pivot) { swap(arr, i, j); i; } } swap(arr, i, right); return i; } private static void swap(int[] arr, int i, int j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; }这段代码里partition每次返回基准值的最终位置。需要注意如果基准值恰好是当前区间的最小值或最大值快速排序会退化成 O(n²)递归深度也会变大。生产级实现一般会采用三数取中、随机基准或双轴快排避免数据分布导致性能退化。2.2 KMP主串指针不回退的字符串匹配KMP 算法的核心是 next 数组。next[i] 表示模式串前 i 个字符组成的子串中最长相等前后缀的长度。发生失配时主串指针不后退只移动模式串指针因此匹配过程整体接近 O(n)。以模式串pabacaba为例手算 next 数组的结果如下i子串最长相等前后缀长度1a02ab03aba14abac05abaca16abacab27abacaba3下面是求解 next 数组的 Java 实现public static int[] buildNext(String p) { int m p.length(); int[] next new int[m]; int j 0; for (int i 1; i m; i) { while (j 0 p.charAt(i) ! p.charAt(j)) { j next[j - 1]; } if (p.charAt(i) p.charAt(j)) { j; } next[i] j; } return next; }这里有一个实际开发中很容易踩的坑不同教材对 next 数组的定义不同。有的定义是“失配时模式串跳转到的位置”有的定义是“最长相等前后缀长度”。如果是后者代码里要从next[j - 1]回退如果是前者回退逻辑会不一样。写代码前一定要先明确定义否则会频繁出现数组越界或死循环。2.3 快速幂用二进制分解压缩幂运算计算a^n最直接的做法是连乘 n 次复杂度 O(n)。快速幂把指数拆成二进制把乘法次数压缩到 O(log n)。public static long fastPow(long a, long n, long mod) { long result 1 % mod; a % mod; while (n 0) { if ((n 1) 1) { result result * a % mod; } a a * a % mod; n 1; } return result; }这段代码的思路是从最低位开始看 n 的二进制。当前位是 1就乘上对应的a的幂每次迭代把a自乘相当于把指数翻倍。这个算法在 RSA 相关计算、大数取模、矩阵快速幂中很常见。常见错误是忘记取模、没有处理n0和mod1的边界情况。3. 图论与路径搜索Dijkstra 与 A*图论问题的核心是“节点 边 权重”常见需求包括最短路径、可达性、拓扑排序、最小生成树等。实际项目里最常用的是路径搜索这里拆解 Dijkstra 和 A* 两个算法。3.1 Dijkstra单源最短路径的经典方案Dijkstra 解决的是单源最短路径问题前提是边权非负。它每次从优先队列中取出当前距离最小的节点然后松弛它的邻接边。使用最小堆实现后复杂度约为 O((VE) log V)。import heapq def dijkstra(graph, start): dist {node: float(inf) for node in graph} dist[start] 0 pq [(0, start)] while pq: d, u heapq.heappop(pq) if d dist[u]: continue for v, w in graph[u]: nd d w if nd dist[v]: dist[v] nd heapq.heappush(pq, (nd, v)) return dist这段代码有两个关键点。第一if d dist[u]: continue用于丢弃优先队列里的过期记录避免同一个节点被反复处理。第二Dijkstra 不能处理负权边。如果图中存在负权边已经出队的节点可能被再次更新得到错误结果此时应该使用 Bellman-Ford 或 SPFA。3.2 A*在 Dijkstra 上引入启发式信息A* 在 Dijkstra 基础上增加启发式函数通过f(n) g(n) h(n)优先扩展“看起来更接近目标”的节点。其中g(n)是从起点到当前节点的实际代价h(n)是当前节点到目标的估计代价。h(n) 必须满足可采纳性否则 A* 不保证找到最优解。A* 常见应用包括 AGV 路径规划、游戏寻路、地图导航辅助搜索。实际工程里地图会被栅格化再结合 JPS、跳点搜索等策略进一步提速。选型时如果希望保证最优路径优先考虑 A*如果更关注实时性和搜索速度可以接受次优路径也可以考虑采样类方法或分层规划。4. 经典优化问题贪心、动态规划与回溯剪枝优化问题通常描述为“在若干可选决策中求目标函数的最大值或最小值”同时受约束限制。根据问题结构可以选择不同的算法。4.1 贪心局部最优不一定全局最优贪心算法每一步选择当前看来最好的选择并且不再回溯。它适合具备“贪心选择性质”的问题比如霍夫曼编码、部分背包、活动安排问题。但在 0/1 背包问题中直接按单位价值贪心往往得不到全局最优解。判断是否能用贪心有一个简单的自检方式能否证明“每一步局部最优最终组合就是全局最优”。如果无法证明说明问题可能具有重叠子结构应该考虑动态规划或搜索。4.2 动态规划用状态转移覆盖重叠子问题动态规划适合具有“重叠子问题”和“最优子结构”的问题。以 0/1 背包为例dp[i][j]表示前 i 个物品装入容量为 j 的背包所能获得的最大价值。def knapsack(weights, values, capacity): n len(weights) dp [[0] * (capacity 1) for _ in range(n 1)] for i in range(1, n 1): w, v weights[i - 1], values[i - 1] for j in range(capacity 1): if j w: dp[i][j] max(dp[i - 1][j], dp[i - 1][j - w] v) else: dp[i][j] dp[i - 1][j] return dp[n][capacity]用一维滚动数组可以压缩空间但遍历容量时必须倒序。如果正序遍历同一个物品会被重复放入结果会变成完全背包。这是动态规划初学者最常犯的错误。4.3 回溯剪枝暴力搜索也能有高质量实践回溯算法在搜索树上遍历所有可能解适合没有高效动态规划解法的组合问题。没有剪枝时组合爆炸会直接拖垮程序。常见剪枝方式包括可行性剪枝当前路径已经不可能满足约束提前返回。最优性剪枝当前路径的代价已经大于已知最优解提前返回。对称性剪枝去掉等价分支减少重复搜索。典型例子是 N 皇后、排列生成、排课问题。剪枝本身是一种工程能力同样的问题剪枝条件写得好运行时间可能相差几个数量级。5. 启发式优化算法模拟退火与粒子群当搜索空间过大、精确算法无法在有限时间内给出最优解时可以使用启发式优化算法。它们的共同特点是不保证全局最优但能快速逼近可用的近似解。5.1 模拟退火用温度控制随机接受差解模拟退火模仿固体退火过程在搜索过程中以一定概率接受比当前解更差的解从而跳出局部最优。温度 T 随时间下降接受较差解的概率通常用 Metropolis 准则计算P(accept worse) exp(-delta / T)其中 delta 是新解与当前解的代价差。温度越高越容易接受差解温度越低算法越趋向收敛。实现时需要关注几个参数初始温度决定前期搜索范围过大会让收敛很慢过小容易过早收敛。降温速率常用T T * alphaalpha 一般取 0.9 到 0.99。终止条件温度降到阈值或连续多次迭代没有改进。5.2 粒子群算法用群体信息更新个体位置粒子群算法模拟鸟群觅食行为。每个粒子有自己的位置和速度每次迭代参考个体历史最优pbest和群体历史最优gbest更新速度v w * v c1 * r1 * (pbest - x) c2 * r2 * (gbest - x) x x v参数含义如下参数作用常见取值w惯性权重控制继承上一时刻速度的程度0.4 到 0.9c1个体学习因子控制向自身经验学习1.5 到 2.0c2社会学习因子控制向群体经验学习1.5 到 2.0r1, r2随机数用于保持种群多样性0 到 1粒子群的常见坑是早熟收敛所有粒子快速聚集到局部最优群体失去了继续探索的能力。缓解方式包括惯性权重线性递减、引入变异机制、或者与局部搜索混合。6. 工程控制与信号处理中的算法PID、FOC、重采样与图像增强算法不只在后台服务里出现。在电机控制、音频处理、摄像头成像等场景中算法直接作用于物理信号稳定性要求更高。6.1 PID 控制比例、积分、微分如何配合PID 是工业控制中最常用的闭环控制算法根据目标值与实际值的偏差e(t)计算控制量u(t) Kp * e(t) Ki * integral(e(t)) Kd * de(t)/dtKp 决定对当前误差的即时反应过大会产生振荡。Ki 消除稳态误差过大会导致超调。Kd 提供阻尼抑制变化过快但对噪声敏感。常见坑是积分饱和。当执行器已经达到输出上限偏差仍然存在积分项会持续累积等偏差反向时输出需要很久才能回落表现为明显超调。工程上需要做积分限幅、输出限幅或抗积分饱和处理。6.2 FOC电机控制里如何把三相电流变成直流量FOCField-Oriented Control常用于无刷电机和永磁同步电机控制。它通过 Clarke 变换把三相电流从 abc 坐标系变换到静止的 alpha/beta 坐标系再用 Park 变换把交流量变换到随转子旋转的 dq 轴上使得电流控制从交流跟踪问题变成直流调节问题之后就可以用 PID 做线性控制。学习 FOC 可以先理解坐标变换的目的它把非线性耦合问题变成线性控制问题。再看 Clarke 变换和 Park 变换矩阵最后在仿真或开发板上跑电流环和速度环。6.3 音频重采样与图像锐化信号处理算法在业务中的落点音频重采样本质是采样率转换。当输入采样率与输出设备采样率不一致时需要插值或抽取。常见方法有最近邻、线性插值、基于多相滤波器的重采样。生产环境中更关注抗混叠滤波和计算开销。图像锐化中拉普拉斯算子基于二阶微分提取图像边缘信息再叠加回原图output original alpha * Laplacian(original)alpha 表示锐化强度。这个算子实现简单但对噪声敏感通常先做平滑再锐化。ISP 链路中 Bayer 到 RGB 的转换常用插值和去马赛克算法是摄像头成像质量的关键环节。7. 机器学习与 AI 方向的核心算法机器学习算法解决的问题是“从样本中学习规律”。这类算法的特点不是一次运行得到结果而是通过迭代训练逐步优化模型参数。7.1 反向传播深度学习模型训练的地基反向传播算法是深度学习模型训练的核心机制。它通过链式法则从损失函数开始从输出层向输入层逐层计算每个参数的梯度再用梯度下降更新参数。训练流程可以概括为前向传播输入 x 经过隐藏层得到预测值 y_pred计算损失 L。反向传播从输出层开始逐层计算损失对权重的梯度。参数更新使用 SGD、Adam 等优化器更新权重。实际工程中PyTorch 和 TensorFlow 会自动完成梯度计算但理解梯度如何流动仍然是排查训练发散、梯度消失、学习率过大等问题的基础。比如 ReLU 激活函数可以缓解梯度消失但学习率过大会导致梯度爆炸。7.2 CNN、3D CNN 与 C3D从图像特征到视频时空特征二维 CNN 用于图像分类和目标检测通过卷积核提取局部特征通过池化降低分辨率最后通过分类器输出结果。3D CNN 把卷积核从二维扩展到三维输入是连续帧组成的视频立方体可以同时建模空间和时间信息常用于行为识别、视频分类等任务。要区分一点3D CNN 和 C3D 不是完全等价的说法。C3D 是 3D 卷积网络的代表结构之一而 2D CNN 加时序建模如 LSTM、Transformer是另一条常见路线。选型时要在计算量、实时性和识别精度之间平衡。7.3 目标检测算法 YOLO把检测当作回归问题YOLO 系列把目标检测建模为一次前向推理直接从图像中回归边界框和类别概率不需要先做区域候选因此特别适合实时检测场景比如工业质检、安防、AGV 避障。使用 YOLO 时要注意训练数据标注质量直接影响准确率。小目标检测需要更高分辨率和合适的数据增强策略。推理设备是 CPU、GPU 还是边缘硬件直接决定选用哪个版本以及是否量化和剪枝。7.4 强化学习、联邦平均与其他前沿方向强化学习的核心是智能体通过与环境交互获得奖励进而学习策略。与监督学习不同强化学习没有现成的“标准答案”只有延迟奖励。经典方法包括 Q-Learning、DQN、PPO 等适用于游戏、机器人控制、推荐系统决策等场景。联邦平均算法用于联邦学习场景多个客户端在本地训练模型只上传模型参数或梯度到中心服务器由服务器做加权平均从而减少原始数据集中传输。实际工程要处理非独立同分布数据、通信开销、隐私保护等问题。前沿方向里PCMCI 用于从观测数据中发现因果关系EVA-02 这类视觉 Transformer 模型在图像分类任务中持续演进。这些算法虽然复杂度更高但基本出发点仍然是“从数据里得到可用的结构和规律”。8. 搜索引擎与规则引擎中的算法BM25 与 RETE搜索和规则匹配在业务系统里非常常见但它们的实现细节容易被忽略。BM25 解决“哪些文档与查询更相关”RETE 解决“规则如何高效匹配”。8.1 BM25相关性排序的经典公式BM25 是文本检索中常用的相关度打分函数综合词频、文档长度、逆文档频率和超参数给出查询词与文档的匹配分。文档 D 对查询 Q 的 BM25 分值可以写成score(D, Q) sum IDF(term) * tf_norm(term, D)其中tf_norm会考虑词频饱和和文档长度归一化。k1控制词频饱和度b控制文档长度的影响程度常见取值为k11.2到2.0b0.75。配合倒排索引BM25 可以在召回阶段快速筛选相关文档再交给精排