50天算法学习复盘:从冒泡排序到BEVFusion,真正难的是这两件事
1. 为什么会有更弱智的算法学习这个系列Day 50一个挺微妙的节点。说长不长毕竟网上那些30天精通算法的标题党都敢把周期压到一个月说短也不短能连续折腾50天还没放弃说明这事儿确实有点上瘾。先说说这个系列名字的由来。更弱智三个字不是自谦也不是标题党而是我对这个系列内容定位的准确描述。市面上讲算法的资料大致分两类一类是教科书和论文风格严谨但劝退适合有数学基础的人慢慢啃另一类是竞赛选手和面试经验帖动不动就这题显然可以二分这复杂度不过就是nlogn对初学者来说那个显然一点都显然。我这个系列想做的是第三种把所有能省略的推导都补上把所有显然都掰开揉碎用最笨、最啰嗦、最不讲效率的方式把算法背后的直觉讲清楚。50天下来我梳理了一遍自己到底学了什么从基础的排序检索到字符串匹配、图论、动态规划再到机器学习那边的一堆经典算法名字这些后面细说整体走了一遍。这个过程最大的感受是算法学习真正难的不是某个具体算法的代码怎么写而是我怎么知道该用哪个算法以及我理解了但为什么写不出来这两个问题。今天这篇Day 50算是一个阶段性的复盘把我踩过的坑、想明白的事、以及那些在热词里反复出现的算法到底是怎么回事一次性做个总结。如果你也是那种看懂了十篇文章但自己动手就卡壳的算法学习者这个系列应该对你胃口。如果你还在纠结要不要学算法、学到什么程度这篇也能给你一个参考坐标系——毕竟一个从零开始还自称弱智的人走到第50天本身就是个信号这事儿没那么可怕。2. 50天算法路线的全景拆解从冒泡排序到BEVFusion热词背后其实是张知识地图打开任何一个算法相关的热搜榜或者关键词列表你会看到一个很奇妙的景象冒泡排序、KMP、PID、匈牙利算法、粒子群、深度学习、BEVFusion……这些东西看起来八竿子打不着好像有什么就热什么。但如果你真的系统学过一遍你会发现它们背后其实有一条清晰的学习路径而那些热度词恰好落在路径的不同节点上。我把自己50天走过的路线整理成了一张表也把热词里的高频内容对应了上去学习阶段时间范围核心内容对应热搜关键词基础篇Day 1-10数组、链表、栈、队列、递归、排序与检索冒泡排序算法c , 归并排序算法, 堆排序算法, 二分查找算法字符串篇Day 11-18字符串匹配与哈希kmp算法, 数据结构与算法图论篇Day 19-30BFS/DFS、最短路径、二分图dijkstra算法, 匈牙利算法, 算法流程图动态规划篇Day 31-40背包、序列DP、区间DP、剪枝贪心算法, 剪枝算法数学与编码篇Day 41-45质因数分解、MD5/CRC原理、随机森林2.3 分解质因数的最优算法, md5算法详细完整过程并举例, modbus crc算法, 随机森林回归算法控制与AI启蒙篇Day 46-50PID、粒子群、强化学习、BEVFusion初步接触pid算法, 粒子群算法原理, ppo算法, balm2算法, bevfusion算法解析这张表不是说我50天就把所有东西学完了而是说我把这些听起来很吓人的概念都从一个完全不懂的人的角度去啃了一遍不求精通但求理解它解决什么问题、核心思路是什么。有意思的是当你把这些算法放在一起看会发现一个规律绝大多数算法问题的本质都是在约束条件下寻找最优或可行的解。比如说冒泡排序和堆排序约束是只能通过比较和交换来调整顺序目标是让序列有序KMP算法约束是主串只能扫一遍目标是快速找到模式串的位置Dijkstra约束是边权非负目标是找到单源最短路径匈牙利算法约束是一个点只能匹配一次目标是找到最大匹配数PID算法约束是系统有惯性且存在误差目标是让输出稳定在目标值粒子群、PPO这类约束是搜索空间巨大且没有解析解目标是在可行时间内找到足够好的解就连BEVFusion这种自动驾驶的感知算法本质也是在多传感器数据对齐的约束下找到对周围环境最准确的三维理解。所以热词表看着杂乱其实是把计算科学几大门类的代表性问题都点了一遍。你把每一类问题挑一个代表作学明白再看其他算法时就会有一种哦原来你也是这个套路的感觉。后面的章节里我挑几个最典型的例子把我弱智版的理解方式分享出来。3. 几个典型算法的弱智版理解KMP、Dijkstra、PID到底各自在干嘛这个系列的核心卖点就是用最笨的方式讲清聪明人的想法。我挑几个热词里出现频率高、又容易让人望而生畏的算法说说我自己的理解路径。这些理解不一定严谨到能写论文但能让你在第五天的时候就不怕它们。3.1 KMP算法不是真的聪明只是吃过亏之后记住了教训KMP是字符串匹配里最常被拿出来讲的算法。网上各种next数组求法的教学视频动不动就一屏幕的推导公式我当初看了三遍都没记住。后来我换了个角度理解你让一个普通人去在一篇文章里找一个词他傻乎乎的做法是一个字一个字往后挪着比比到一半发现不对退回下一位重新比。这个做法叫暴力匹配简单但浪费时间——明明前面已经比过的字符退了重来就白比了。KMP做的事情特别朴素既然前面已经比对过一段我就把这段里能复用的信息记住下次不用退回开头而是跳到该跳的位置。那个next数组存的就是匹配失败后该往哪跳的备忘录。说白了它不是天才般地规避了重复劳动而是老老实实地承认了我会失败但我选择带着记忆失败。代码逻辑其实也不复杂void getNext(const string p, vectorint next) { int n p.size(); next.resize(n, 0); int j 0; for (int i 1; i n; i) { while (j 0 p[i] ! p[j]) j next[j - 1]; if (p[i] p[j]) j; next[i] j; } }对比暴力匹配O(m*n)的最坏复杂度KMP是O(mn)。当文本很长、模式串反复匹配失败时这个差别是数量级的。你不需要背这个代码只需要理解失败后跳回到前缀的某个位置这个思想。很多初学KMP的人都卡在next数组上我的经验是先别管最长相等前后缀这个名字直接拿个例子比如把aabaaf这样的短串手动推一遍next数组比看十篇推导都管用。3.2 Dijkstra算法贪心加松弛本质上是我每次都挑眼下最好的路先走Dijkstra是单源最短路径里最经典的算法也是热词榜上的常客。它的思路用一个日常例子解释最清楚你在一座陌生的城市里要从起点去所有其他地点且每条路的距离都是已知的。正常人会怎么做先看看起点直接能到哪儿挑一条最近的路走过去到那儿之后再看看从新位置出发能不能让某些点变得更近。如果能就更新不能就继续挑最近但还没处理过的点。这个走到一个新地方试着看看其他点能不能因为经过这里变得更近的操作专业名词叫松弛。Dijkstra的效率秘密就在于每次都优先处理当前已知最近的未访问点这用堆来加速所以一旦某个点的最短距离被确认它就再也不会被更新了。初学这个算法最常犯的错是松弛的时候忘了检查这个点是不是已经确认过最短路径。如果不管三七二十一都去松弛结果就乱了。另一个容易搞混的地方是和BFS的区别BFS是层层推进解决无权图最短路径Dijkstra解决的是带权图所以需要优先级队列来模拟先走短边的贪心策略。这两个模型别搞混Dijkstra就好懂多了。一个经典的Dijkstra实现堆优化版大概是这样的void dijkstra(vectorvectorpairint, int graph, int src) { int n graph.size(); vectorint dist(n, INT_MAX); dist[src] 0; priority_queuepairint, int, vectorpairint, int, greater pq; pq.push({0, src}); while (!pq.empty()) { auto [d, u] pq.top(); pq.pop(); if (d dist[u]) continue; for (auto [v, w] : graph[u]) { if (dist[u] w dist[v]) { dist[v] dist[u] w; pq.push({dist[v], v}); } } } }那段if (d dist[u]) continue;我一开始觉得多余。后来想明白了同一个点可能被push进堆多次只有最小的那次有效其余的都是过期的直接跳过。这个细节看起来小但不加的话性能会退化得很夸张。3.3 PID算法不需要懂控制论也能理解一个从空调到自动驾驶都靠它吃饭的算法PID可能是热词列表里和我前面讲的所有数据结构算法区别最大的一类——它根本不是用来处理静态数据的而是处理动态系统的。为什么工业界每天有无数设备依赖PID因为大部分控制系统面临的都是同一个问题想让输出稳定在目标值但系统有惯性而且不断受到外部干扰。拿空调举例。设定26度屋里现在30度你不能一直在最大功率制冷否则到了26度还会继续降温这就是过冲。也不能功率太小半天降不下来。怎么办看三样信息现在的温差多大——这对应P比例项温差是在快速缩小还是在扩大——这对应D微分项过去累积的误差是不是一直没消掉——这对应I积分项。把这三个量按一定系数加起来得到控制量就是PID。写成公式是u(t) Kp * e(t) Ki * ∫e(τ)dτ Kd * de(t)/dt你不需要会解微分方程只需要知道一个传感器测误差一个执行器输出控制量中间用一个三合一的公式算一算系统就能稳定下来。调整Kp、Ki、Kd三个系数让系统不震荡、不迟缓、稳态误差小是PID调参的核心工作也是最考验经验的地方。我试着用Python写过一个最简PID模拟对一个一阶惯性系统做温度控制过程大概就是下面这样def pid_control(setpoint, current, dt, kp, ki, kd): global integral, prev_error error setpoint - current integral error * dt derivative (error - prev_error) / dt prev_error error output kp * error ki * integral kd * derivative return output这段代码也就是不到十行核心就一个公式。但真正让它听起来高级的是背后的控制律设计和调参经验。热词榜里还有增量式PID算法本质上就是把PID输出的增量拿出来做运算好处是执行器只需要知道比上次多输出多少即可不容易产生大的冲击干扰。理解了标准PID增量式很容易就能看懂。3.4 贪心、动归、剪枝这三位其实是同一类问题的三种态度热词里贪心算法剪枝算法和DP动态规划经常被放在一起因为这仨解决的是同一类问题——状态空间很大要找最优解。它们的区别其实挺像人生的几种策略贪心每步都选眼下最好的不回头。 简单高效但只有在局部最优等于全局最优的问题上才成立比如区间调度、找零钱这类。动态规划我不急着选先把所有可能的状态都存下来再用状态转移方程一步步推。 代价是要额外空间好处是能保证全局最优。剪枝知道有些分支肯定不行直接不走了。 它通常不是独立算法而是和DFS、回溯结合的加速技巧。我见过很多初学者包括我自己一开始分不清应该用贪心还是动归。一个粗略的判断方法是如果每一步的选择不受前面选择的影响且局部最优能推导到全局最优那贪心大概率可行如果每一步都依赖于历史状态且要枚举各种组合那多半要走DP的路子。剪枝算法这个词的热度很大程度上来自竞赛题和面试题里的回溯剪枝套路。我之前在系列Day 20里写过一个典型的案例在一个N×N棋盘上放N个皇后使它们互不攻击。纯暴力的搜索空间是N的N次方量级而加上不能同行、同列、同斜线的约束剪枝之后N8时只需搜几十个分支。这就是剪枝的威力——不是让你跑更快而是让你少跑路。4. 算法学习的两个分水岭从看得懂到写得出从写得出到用得对50天里我最大的收获不是学会了多少算法本身而是搞明白了算法学习里两个真正卡住大多数人的分水岭。所有搜索热词里那些算法基础课本pdfpython如何写算法算法设计与分析背后其实都是大家卡在这两个点上。4.1 第一个分水岭从看懂了到能默写出来很多人有这种体验上课或看文章的时候跟着代码一行一行读觉得逻辑清楚、理所当然。但合上资料让自己从零实现一遍就大脑空白。这种现象简直太正常了。因为你读书时是在解码编译器帮你把逻辑推演的过程走了到你写的时候你要做的是编码所有细节都得自己兜住。我的实战方法很简单看三遍写一遍讲一遍。第一遍读代码理解结构和每一行的目的第二遍只读注释不看实现尽力回忆这是干什么的第三遍看核心图示比如KMP的匹配示意图、Dijkstra的松弛过程图不碰任何代码然后关掉所有资料自己在编辑器里写一遍最后用教给别人的方式把你刚写的算法用文字说一遍能说清楚就说明真懂了。这套流程看起来很笨甚至有些慢但它是我试过的最有效的把理解转化为肌肉记忆的方式。我在Day 10的归并排序、Day 14的KMP、Day 28的Dijkstra上各做了一次之后遇到类似结构比如快速排序和二分查找共用模板时上手速度快了很多。还有一个经验是不要只背模板代码要背边界条件和为什么。比如二分查找里while (left right)和while (left right)有什么区别Dijkstra里堆里保存的是{dist, node}还是{node, dist}归并排序的合并函数里temp数组的索引怎么对应回原数组这些看起来微小的细节才是一个人真正能够独立实现和看了答案会说原来如此之间的差距所在。4.2 第二个分水岭从会写算法到能选对算法会写的下一步是用得对。热词里有个问题特别典型指出以下算法中的错误和低效之处并将它改写为一个既正确又高效的算法。这种题目考察的就是选择与优化。50天学习后我的体会是选算法的本质是看两个维度——数据规模和数据特征。数据规模小比如n≤100O(n^2)甚至O(n^3)的算法也毫无压力这时候暴力、DP、Floyd都能用没必要硬套更复杂的优化数据规模大比如n在10^5到10^7之间就得考虑O(nlogn)、O(n)甚至O(logn)的算法排序、哈希、二分、KMP这些就派上用场数据有特殊结构比如有序、单调、无环、稀疏就优先考虑对应专长算法有序用二分、单调栈优化、无环图用拓扑、稀疏图用邻接表堆优化的Dijkstra。另外一个被很多人忽略的点是先看你的终止条件是什么再倒推算法。想找一个答案就二分找边界想找所有解就DFS/回溯找最优就DP状态设计或贪心找最短路径就BFS/Dijkstra判断有没有环就看DFS的visited状态是否重复访问。把目标反过来写很多选择题会瞬间变得清楚。还有一类热词比如机器学习算法深度学习算法随机森林回归算法它们本质上是数据驱动的参数拟合不是传统意义上的确定性算法。这类算法选型时除了看数据规模和特征还要看你手里的数据是不是足够、特征维度高不高、是否需要解释性。随机森林适合表格型数据且能抗过拟合深度学习适合图像、语音这类高维非结构化数据贝叶斯适合小样本且需要概率解释的场景。这一类内容我在Day 46以后才正式碰但提前理解原理模型再去看代码实现就会轻松很多。4.3 一个绕不开的工具哈希与信息编码类算法热词列表里还有一类看起来和算法题关系不大的内容比如MD5算法详细完整过程并举例MODBUS CRC算法粒子群算法原理。这些名字频繁被搜是因为它们直接出现在工程实践里而不是面试卷子上。MD5和CRC这类算法的核心其实是把任意长度的数据压缩映射成固定长度的一段摘要用途是用一个小指纹表示大文件。MD5今天已经不推荐用于安全敏感场景因为碰撞但在校验文件完整性时仍然大量出现。而MODBUS通信里的CRC16是为了检测数据传输过程中是否发生了比特翻转。理解这些算法的关键不在于能把它的迭代过程完整背下来而在于明白哈希指纹和差错校验这两个概念在工程系统中的作用。粒子群算法PSO则是另一个方向的代表——它是启发式优化算法灵感来自鸟群觅食。它和PPO近端策略优化强化学习里非常火的算法之一之间共享一个思想在巨大的解空间中用一群候选解互相协作、不断更新位置直到收敛到一个足够好的解。只不过粒子群没有环境反馈和策略网络相对更容易上手。我从贪心穷举思路跳到用随机搜索群体协作逼近最优的思路时花了不少时间适应但适应之后再看PPO那种智能体与环境交互、从回报信号中学习策略的范式就觉得没那么不可理解了。5. 踩坑实录50天里最典型的5个学习误区和我的纠正方法学算法的过程从来不是线性的磕磕绊绊里踩的坑才让人记忆最深。这里总结5个我真实踩过、也看到很多初学者反复踩的坑可能比任何教程都有参考价值。5.1 过度迷恋最优解忽略能跑的解法刚开始学习时我一度陷入看到题目就想一上来就写最优解的状态。比如排序题上来就想写快排或堆排结果边界条件调试半天字符串匹配题总想直接套KMP却连暴力匹配的代码都没写过一遍。这是个极其常见的误区。后来我给自己立了个规矩先写暴力再优化。暴力的正确性容易验证而且写暴力本身就在加深对问题的理解。你只有在明确知道暴力解法哪里慢之后才能理解优化算法到底优化了什么。快排为什么优于冒泡因为它每次partitio都能把问题规模减半KMP为什么优于暴力因为它避免重复比对。这些为什么比代码本身值钱。5.2 刷题只看不写混了个眼熟这个坑更隐蔽看讲解时的顿悟感会让人误以为已经掌握了。实际上顿悟只是理解的起点离会写还隔着十万八千里。我前20天几乎每天都能看懂好几道题的题解但一到白板默写就啥也写不出来。纠正方式我在4.1已经提过看完三遍之后必须动手默写写错没关系对照答案查漏补缺隔天再默写一遍。重复两三遍之后算法就会从别人的思路变成自己的思维习惯。5.3 忽视语言底层和数据结构基础C的容器实现Python的垃圾回收机制哈希表如何扩容这些和算法看似无关实则强相关。我在Day 25做一次迷宫BFS时随手在Python里用列表模拟队列结果大数据量测试时性能差得离谱。换用collections.deque之后性能直接提升了一个数量级。数据结构与算法的关系本来就是一体的算法是在数据结构上运行的逻辑数据结构是算法效率的载体。热词里数据结构与算法一直被当成一门课来搜说明大家都知道它重要但落实到代码里却很少有人认真感受选对数据结构带来的体验差异。多看看底层原理对写算法的长期帮助远大于多刷几道题。5.4 只顾理论推导不和可视化/实例结合很多算法比如图论的Dijkstra、匈牙利算法或者MD5的四轮迭代只看公式推导真的很难建立直觉。我的做法是找个具体的例子用手工或者在线可视化工具一步步走一遍。比如匈牙利算法的增广路概念一开始完全不知道在说什么直到我拿一个三男三女的配对问题名字就叫A/B/C和X/Y/Z把寻找增广路的过程从头到尾画出来才理解了已匹配边-未匹配边交替出现到底是怎么回事。图论算法用可视化走一遍效果比读十篇文字解析都强像数据结构和算法可视化这类网站是学图论时最有价值的辅助工具。5.5 把学习搞成了收集资料永远在明天开始最常见的坑是花大量时间找算法基础课本pdfacwing算法基础课Java实现的数据挖掘十大算法代码以为收藏了就等于学会了。实际上资料永远找不完而你的时间每分每秒都在流逝。选择一个固定教材和课程比如一本入门书加一个OJ平台从第一页开始读、第一道题开始做比囤积一百个链接管用一百倍。Day 50回看时我最大的感悟是行动本身会生成方向和路径而准备和规划不会。你不需要等到把整个知识体系都弄明白了才开始相反是先学一个、再学下一个的过程慢慢建立出了属于你自己的知识体系。6. 50天之后我给同样从零起步的人一份不焦虑清单写到最后我想把Day 50当成一个路标给准备开始或者正在坚持的人一份实用清单。它不是一个30天精通式的承诺而是一份50天里我自己验证过、筛选过的参考路径。第一个建议把学完换成用一次。每个算法的学习目标不要设成我理解了而设成我能用它解决一道题目/一个实际问题。做不出来那道题说明理解有缺口做得出来才是真的学到了。第二个建议每天只学一个核心概念但把它写透。一天搞定10个算法名字不如一天吃透一个算法并完成相关的3道变式题。我Day 1-10的效率其实很低但正因为每天只磨一个点后面的综合内容才没有塌方。第三个建议学一个新算法时要刻意和旧知识联系。归并排序的分治思想后面在快速排序、线段树里反复出现BFS的队列思路后面在拓扑排序、最短路里也用得上。你会发现算法世界很小知识之间是互相复用的。第四个建议允许自己忘但准备一个复盘本。我在Day 38回过头看KMP时发现自己已经忘了不少细节。这不是坏事第二次捡起来时理解比第一次深得多。关键是每次忘了之后能快速定位到自己的笔记或原文重新建立连接。所以笔记一定要留而且最好用自己的话写不要直接复制别人的总结。第五个建议别怕别人都懂就我不懂。那些搜索热词里看起来很难的算法比如BALM2、BEVFusion、混合整数线性规划它们的背后也是一个一个基本概念搭起来的BEVFusion的第一步就是把不同传感器的数据转换到同一个鸟瞰视野坐标系里本质上就是坐标系变换加上特征融合混合整数线性规划的求解器里依然在用分支定界和割平面这些基础思想。没有高不可攀的算法只有还没拆解到位的定义。Day 50到了但算法学习的路肯定还很长。连续50天保持一个学习习惯真正的奖励不是我学了50天这个数字而是在第51天时看到一个新算法名字你不会再恐慌而是会自然地想这东西的输入是什么、输出是什么、约束是什么、它和我知道的哪个算法是亲戚。这种不慌的感觉比任何知识点的细节都重要。与所有还在弱智学习路上折腾的人共勉。