☰
算法入门:从复杂度到数据结构,带你搞懂编程的底层逻辑
2026/10/8 2:39:06 网站建设 项目流程

我第一次认真琢磨“算法”这两个字,不是在学校课堂上,而是在一次真实的程序性能调优里。当时有个接口要把几万条记录逐个比对,数据量一上来,响应时间从几十毫秒一路飙到几秒,用户开始投诉,我对着日志一筹莫展。后来把无脑遍历的写法换成带索引和排序的思路,接口直接快了一个数量级。那一刻我才明白,写代码和写好算法的差别,有时候不在语法,而在你选择用什么步骤去解决问题。

这篇笔记是《算法入门》系列的第一篇,专门把最底层的问题聊透:算法到底是什么?它能解决什么问题?适合刚接触编程的大学生、要准备蓝桥杯或LeetCode刷题的人、以及算法工程师面试前想建立整体知识框架的求职者。我会尽量少拽数学公式,多用生活例子和实际代码,讲清楚定义、复杂度、常用算法类型,最后再分享几条我自己踩过坑之后总结出来的学习建议。

1. 先搞清楚:算法到底是个什么东西

1.1 给朋友泡一杯咖啡,就是一套算法

假如你招待朋友,要泡一杯拿铁,你会不会下意识按照某个顺序操作?先称咖啡豆,再研磨,烧水到合适温度,萃取浓缩,打奶泡,最后拉花。这一串动作有明确的先后顺序,有输入(豆子、水、牛奶),有输出(一杯咖啡),有约束(水温不能太高,时间不能太长),也有终止条件(咖啡液接够就停)。如果哪一步乱了或者省略了,成品就会跑偏。

计算机算法本质上也是这么一回事。它是一套“解决问题的操作步骤说明书”,把数据从一端送进去,经过有限步骤的处理,在另一端给出结果。很多初学者一看到“算法”两个字就联想到高深论文,其实你每天在生活里做的决策、排序、查找,都在无意识地使用算法思维。只不过计算机比人更死板,它不会自动补全你没说清楚的步骤,所以我们必须把这套说明书写得非常精确。

1.2 教材定义太抽象,我用“菜谱”来理解

教科书里的标准定义是:算法是对特定问题求解步骤的一种描述,是指令的有限序列。展开来看,它要求解决问题的方法满足有穷性、确定性、可行性和输入输出等条件,这些细节我放到第二部分细说。

我更喜欢的类比是“菜谱”。菜谱规定好食材、火候、调料和出锅时机,程序员写算法,就是在给计算机写菜谱。菜谱好不好,直接决定菜好不好吃;算法好不好,直接决定程序跑得快不快、占的内存多不多。于是就有了一个经典对比:暴力枚举和二分查找。假设有一个按升序排列、长度一万的数组,你想知道目标值在不在里面。暴力枚举从第一个元素挨个往后找,最坏要看一万个元素;二分查找每轮砍掉一半区间,最多只要十四次左右。同一个问题,方案不同,时间开销差了好几个数量级。这就是我们为什么要学算法:不是为了显示自己多有技术含量,而是为了用更少的算力,解决更大规模的问题。

2. 一个合格算法的基本特性,以及怎么判断它好不好

2.1 五个基本特性:有穷、确定、可行、输入、输出

先看有穷性。算法必须保证在执行有限步骤之后结束。你写一个while循环,如果逻辑上永远跳不出去,那它就不是一个合格算法,而是一个故障程序。

再看确定性。算法的每一步都应该是明确的,不能有“随便拿一个合适的元素”“找个合适的人”这种模糊表达。计算机没有任何常识来帮你猜“合适”是什么意思,你必须把规则定义到机器能执行的程度。

可行性指的是每一步都能在有限时间内完成。你不能在算法里写“从全宇宙所有星球里挑一个当结果”,因为这一步谁都做不完。输入输出也容易理解:算法可以没有输入,比如固定计算某个常数,但它必须有输出。哪怕是输出一行日志,也得给外界一个反馈。

这里有个初学者容易混淆的点:算法不等于代码。同一个算法可以用C++写,用Python写,甚至用自然语言描述。代码只是算法的载体之一。所以我在学习时一直提醒自己,我学的是一个思路,而不是某一种语言的固定写法。

2.2 大O记号:给算法的效率贴上标签

评判一个算法好坏,最核心的两个指标是时间复杂度和空间复杂度,它们都用大O记号来表示,比如O(1)、O(n)、O(n^2)、O(n log n)。

怎么理解大O?它不关心你的机器快不快,只关心随着输入规模n的增大,算法耗时增长的趋势是什么样的。访问数组下标是O(1),因为不管数据多大,一次定位就搞定;顺序查找是O(n),数据翻倍,耗时大约也翻倍;双重循环嵌套里常见的暴力算法是O(n^2),数据一变大,耗时增长得相当肉眼可见;归并排序、堆排序这种高效算法普遍在O(n log n),听起来多了个log,但和O(n^2)差了十万八千里。

我建议把复杂度理解成“增长曲线”,而不是去背一串公式。面试算法岗的人十有八九会问:这两个方案都能出结果,为什么这个更好?你只要能把复杂度这个底层逻辑讲清楚,就已经赢过很多只会背代码的人。

常见的复杂度从优到劣大概是:

  • O(1):恒定时间,比如哈希表查找
  • O(log n):对数时间,比如二分查找
  • O(n):线性时间,比如顺序遍历
  • O(n log n):接近线性但是带一点对数开销,比如快排、归并
  • O(n^2):平方级,比如两层循环的暴力解法
  • O(2^n)、O(n!):指数级和阶乘级,n稍微一大就会爆炸

2.3 动手算一遍:从顺序查找到二分查找

来看一个最简单的例子。有一个长度为n的有序数组,我们要判断某个值是否存在。顺序查找的代码是这样:

def linear_search(arr, target): for i in range(len(arr)): if arr[i] == target: return i return -1

最坏情况下,目标在数组末尾或者不存在,循环要执行n次,复杂度就是O(n)。

如果数组有序,用二分查找:

def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1

每轮循环都会把搜索区间缩小一半。最多需要执行多少次?就是log2(n)次,所以复杂度是O(log n)。当n等于一百万时,顺序查找最坏要看一百万个位置,二分查找大约只需要二十次。在实际工程里,这二十次和一百万次的差距,就是一次快速响应和一则用户投诉的差距。

于是复杂度不只是考试概念,更是工程里的金钱和时间。很多系统优化本质上都是把高复杂度的操作想办法降一个等级,比如加缓存、加索引、改数据结构。想清楚这一步,你就已经算正式踏进算法大门了。

3. 算法世界的常用套路:查找、排序、搜索与图论

3.1 查找算法:从线性遍历到哈希表、再到KMP

查找是最基础、出现频率最高的问题类型。顺序查找最简单但性能一般,二分查找对数据有序有要求,哈希查找则是工程里最常用的方案,因为它能利用哈希表把查找时间压到接近O(1)。想想手机通讯录里的号码:你按名字直接翻到那一页,而不是从第一个联系人挨个往下读。哈希函数就是帮你快速定位“那一页”的算法。

字符串匹配里的KMP算法也是经典中的经典。比如在一篇长文章中找一个关键词,朴素做法是从每个字符位置开始逐个向后比对,文章长度n、关键词长度m,最坏复杂度是O(n*m)。KMP的核心思想是利用已经匹配过的信息,让主串的指针不回头,把匹配过程优化到O(n+m)级别。很多人在KMP的next数组推导上翻过车,我的建议是先亲手拿纸笔演算几次前缀后缀的匹配过程,再去背代码模板,否则很容易越看越晕。

查找问题并不只在数组和字符串里出现。在树结构里查找,如果树是平衡的,复杂度能做到O(log n);在红黑树、B树这种工程结构里,查找、插入、删除都能保持稳定性能。这也是为什么数据库索引偏爱B+树。数据结构与算法从来不分家,这一层联系在第四章我会专门展开讲。

3.2 排序算法:冒泡、归并、快排、堆排序怎么选

排序是每个入门者都绕不开的练兵场。冒泡排序大概是很多人写出来的第一段排序代码:每一轮把相邻元素两两比较,大的往后挪,像气泡一样冒出水面。代码好写,但复杂度是O(n^2),数据一多就会很吃力。我见过不少人拿冒泡去排序几十万元素然后抱怨运行慢,其实不是语言问题,是排序选型问题。

归并排序的思路是“分而治之”:不断把数组拆成两半,分别排好序,再合并起来,时间复杂度稳定在O(n log n),缺点是合并时需要额外O(n)空间。快速排序同样基于分治,平均复杂度O(n log n),思路是选一个基准,小的放左边,大的放右边,再递归处理两侧。它还有各种优化版本,比如随机选基准来避免最坏情况。堆排序利用堆这种数据结构维护最大或最小元素,同样在O(n log n)级别,而且不需要太多额外内存。

我给初学者的建议是:先把插入排序和冒泡排序写熟,理解它们为什么慢;再手写归并和快排,体会递归和分治思想;最后接触堆排序,因为堆的概念后续在优先队列、Top K问题里会反复出现。

这里给出一个具体的练法:拿一个长度为8的乱序数组,手动跑一遍快速排序。每选完一轮基准,在纸上画出左右两个区间,再递归地重复。“放到左边的总是比基准小,放到右边的总是比基准大”这句话,只有当你亲眼在纸上看见它,才算真正理解。面试手写快排时,只要脑海里的这幅图景清晰,手指自然能跟上,而不是死记硬背某个模板。

3.3 暴力枚举、回溯与剪枝:以八皇后为例

暴力枚举是一种最朴素也最容易被想到的方法:把所有可能性列出来,再一一验证。比如找出数组里所有和为某个值的组合,最简单的方案就是多重循环。但暴力枚举的代价是复杂度爆炸,如果每个位置有k种选择,要处理n个位置,复杂度就是k的n次方,n稍微一大,机器根本算不完。

这时候就要用回溯加剪枝。以八皇后问题为例,要在8×8的棋盘上放8个皇后,要求任意两个皇后不能在同一行、同一列、同一对角线上。暴力方案是把所有摆放组合都生成再过滤,数量级大得离谱;回溯方案是逐行放皇后,每次落子前检查当前是否冲突,冲突就回退到上一行重新试。剪枝则是在一条路径注定无解时提前砍掉,不再继续深入,从而大幅减少搜索分支。

写八皇后这类题很容易让人体会到“搜索树”这个抽象概念。它不要求你真的画出一棵树的图片,但心里要清楚:每一步尝试会展开哪些分支,哪些分支已经被证明是死路,剪枝就是把这些死路提前堵上。一旦想通这一点,后续学习深度优先搜索、广度优先搜索,乃至更高级的启发式搜索,都会顺很多。

3.4 图论与更广阔的算法地图

图论是算法领域里非常庞大的一块。最短路径有Dijkstra和A*,最小生成树有Prim和Kruskal,强连通分量有Tarjan,二分图匹配有匈牙利算法。不管你是准备蓝桥杯还是算法工程师面试,这些名词迟早会撞见。

A*算法适合做路径规划,它在普通广度优先搜索之外加了一个启发式函数,优先探索“看起来离终点更近”的方向,所以效率和效果都比盲目搜索好。匈牙利算法解决的是匹配问题,比如有n个任务和n个人,每个人擅长其中一些任务,如何安排才能使总完成对数最多。这类思路在调度系统、推荐系统里都很常见。

再往学术和前沿看,还有粒子群算法、模拟退火这类启发式优化算法,以及Transformer、深度强化学习算法如MADDPG等基于神经网络的复杂系统。它们的本质也都是:设定目标,遵守约束,不断逼近更优解。名字确实吓人,但底层思维是一脉相承的。入门阶段不需要被这些名词劝退,基础数据结构、搜索、分治这些地基打牢之后,再往前看会轻松很多。

4. 算法和数据结构为什么总是成对出现

4.1 数据结构是骨架,算法是灵魂

《数据结构与算法》这门课之所以总把两个词绑在一起,是因为它们谁也离不开谁。一个更直白的理解是:数据结构管“数据怎么存放”,算法管“数据怎么使用”。存的方式,直接决定了用的效率。

同样是查一个学生信息,如果所有学生都堆在一个无序数组里,你只能线性遍历,O(n)级别;如果维护一个哈希表,就可以接近O(1)定位;如果存成一棵二叉搜索树,还能支持有序区间的范围查询。数据库系统、缓存系统、搜索引擎的每一个设计,背后都是这种“存”与“用”的取舍。

常见的数据结构有数组、链表、栈、队列、哈希表、树、堆、图。数组和链表是基础容器,栈适合“后进先出”的场景,例如函数调用栈;队列适合“先进先出”,例如广度优先搜索的节点待处理列表;图结构几乎能建模所有带关系的问题,比如社交网络、交通网络。

4.2 举一个具体例子:BFS为什么必须配队列

很多初学者在写广度优先搜索时,总纠结“为什么要用队列”。因为BFS的语义就是一层一层往外扩,先遇到什么节点就先处理什么节点,这正好对应队列的“先进先出”。如果你手滑用了栈,就变成了深度优先搜索,先一头扎到底再回头,整个搜索路径全乱套。

递归也很有趣,它本质上是在隐式使用系统栈。函数一层层调用下去,再一层层返回,这和手动维护一个栈来写深度优先搜索是同一件事。学树和图的遍历时,我会建议同时写递归版本和显式栈版本,这样你对“栈”“队列”和“遍历顺序”这三者的关系会有非常直观的感受。

4.3 哈希表为什么能变快:空间换时间

哈希表看起来像魔法,往里面存一个键值对,再取某个键时仿佛瞬间就能拿到。本质上是拿空间换时间:通过一个哈希函数把键映射到数组下标,把“查找某个键”变成“访问某个下标”,所以才能那么快。

但哈希函数不可能保证每个键都映射到完全不同的位置,一旦多个键落进同一个槽位,就产生哈希冲突。常见解决办法有链地址法,把所有冲突的元素串成链表;也有开放地址法,继续探测下一个空位。理解了这个设计,你会发现每个优秀方案背后都藏着取舍:内存用多了,冲突处理复杂了,但在大多数场景下,这仍然是一笔划算的买卖。

5. 光知道基础还不够:不同领域的算法长什么样

5.1 传统领域的算法:图像、滤波、控制与硬件仿真

算法不止存在于竞赛和面试题里,工业界和学术界的算法形态更加多样。图像处理里,灰度图像二值化是一个特别经典的课题,其中又以大津法Otsu最具代表性。它的核心思想是自动寻找一个阈值,让分割出来的前景和背景类间方差最大,用户不用手工调阈值,机器自己算出一个相对合理的分界。在Halcon、OpenCV里做检测项目的人,可能还经常用到均值滤波、中值滤波、高斯滤波来去噪。选哪种滤波,要看图像里是椒盐噪声还是高斯噪声,选错了等于白做。

控制领域也有自己的算法。比如太阳能电池板最大功率点跟踪的MPPT算法,常见的有扰动观察法、电导增量法,本质是不断调整工作电压,让输出功率沿着上升方向逼近最大值,也可以看作一个连续优化问题。姿态解算领域有Mahony算法,能把陀螺仪、加速度计、磁力计的数据融合成稳定的姿态,做四轴飞行器的人应该都打过交道。这类算法不依赖厚重的深度学习框架,但在工程落地时非常吃采样率和参数调优的经验。

电路仿真里还有SPICE算法,它本质上是一套基于电路方程组求解的方法,在芯片验证领域几乎是标配。如果你去嵌入式或硬件公司面试,对方很可能不考LeetCode,而是问你信号干扰怎么滤除、采样数据怎么融合,这时候你能否把算法的底层思想迁移过来,就是拉开差距的关键。

5.2 现代AI领域的算法:从Transformer到深度强化学习

深度学习的流行让“算法”这个词的外延扩大了不少。图像分类、目标检测、语义分割这些任务背后都是复杂网络结构和损失函数的设计。Transformer已经成为自然语言处理和视觉任务里最常见的网络架构,它的注意力机制本质上也是在“全局范围内筛选重要信息”。语义分割算法里的DBNet,则通过可微分二值化把文本检测变成像素级分割问题,比传统回归框方法更高效。

深度强化学习算法在近几年也很热,比如MADDPG,适合多智能体协作环境,像多无人机编队、机器人协作。这些算法构建在大量线性代数、概率论和梯度计算之上,但底层仍然遵循“状态、动作、奖励、策略”这个框架。如果连最基础的搜索和动态规划都还没站稳,直接冲进深度强化学习,大概率会看不懂代码里每一步参数更新到底在做什么。

数据挖掘领域的HDBSCAN聚类算法也很值得了解,它能在有噪声的样本空间里自动识别不同密度的簇。它有一个关键参数叫“最小簇大小”,怎么选需要结合数据分布反复试。算法工程师面试中,聚类、降维、特征选择这类问题经常被拿来考察你对数据形态的敏感度。

隐私保护方面还有差分隐私算法,通过在查询输出上加入经过校准的噪声,让外界无法根据结果反推出某个人的具体数据。这在大数据统计和推荐系统中越来越重要,也说明算法不只是“算得快”,还可以“算得安全”。

5.3 从热搜词看大家都在学什么

我顺手整理了一些关于“算法”的搜索热词,能大致看出学习群体的需求分布。一类是基础面试向,比如数据结构与算法、算法工程师面试、蓝桥杯算法题、LeetCode必刷题,说明很多人还在为笔试和面试做准备。另一类是具体算法向,比如KMP、堆排序、归并排序、A*、匈牙利算法、Tarjan,说明大家学完基础后,会开始深挖经典算法。还有一类是领域应用向,比如图像二值化、MPPT、Mahony、深度强化学习,说明算法学习正从纯刷题走向业务落地。

这张图景对新手很有参考价值。只想应付面试,那就重点攻排序、查找、二叉树、动态规划和图论;做图像或嵌入式,就要掌握相应领域里流传多年仍然有效的经典算法;做AI应用,那么深度学习、Transformer、强化学习才是真正的主战场。把需求定位清楚,学习路线就不会漫无目的。

6. 新手最常踩的坑,以及我的实操建议

6.1 别背代码,学着“模拟执行”

我见过太多人把算法学习变成背题:背快排写法、背KMP模板、背动态规划的状态转移方程。结果换个变体就卡住,面试一深问就露馅。

我自己学算法时最受益的方法,是拿纸笔把每一步循环画出来。以二分查找为例,拿一个长度为10的数组,手动写下来left、right、mid每一步的变化,比较中间值和目标值的大小关系,直到两个指针交错。这个过程看起来慢,但一次亲手模拟胜过背十遍代码。一旦你把人脑手算的过程想明白了,代码就只是这个手算过程的形式化翻译而已。

6.2 从两本书、一条主线同时推进

如果目标就是蓝桥杯或者LeetCode,我的建议是先花一到两周过完基础数据结构,再按专题刷题。排序、二分、双指针、栈与队列、树、回溯、贪心、动态规划、图论,一个专题一个专题来。初期不要追求题量,每天认真吃透两三道题,远比刷十道然后忘光要有意义。

如果平时做的是纯业务开发,也别觉得算法无用。缓存失效、布隆过滤器、倒排索引、一致性哈希,这些听起来花哨的工程方案,背后全是算法和数据结构的实际应用。多了解一点,系统设计时手里就多几个选项,也不容易被各种概念牵着鼻子走。

6.3 常见问题速查表

问题可能的坑建议
看不懂网上题解跳过了推导过程先弄清思路,再独立实现一遍
写出的代码超时一直用暴力枚举,复杂度太高分析数据规模,换成二分、哈希或剪枝思路
排序代码背不下来死背某个语言实现先理解“比较与交换”,再手写通用逻辑
学习动力不足没有明确目标定一个具体目标,比如蓝桥杯省赛拿奖,LeetCode每日两题
面试讲不清复杂度只会写不会说每道题写完口头复述一遍时间和空间复杂度
理论和业务脱节只刷题不看应用试着把学到的算法用到真实业务代码里做优化

我在带新人时经常说一句话:算法学习像练肌肉,不是看几集视频就能长出来的,必须自己反复做动作,感受每一块肌肉的发力。一道题写三遍,每一遍换个理解角度,效果比抄十遍别人的答案都好。那些现在看着吓人的算法名词,过几个月回头看,大概率会变成一句“哦,原来就是那么回事”。

我个人还有一个私藏的小习惯,每学完一类算法,我会用大白话写一段几十字的笔记,把自己当成本类算法完全没学过的路人,试着解释这个问题到底在解决什么、步骤是什么、为什么这么解。写完再回去读,思路会清楚一大截。这个习惯陪我从入门走到面试,也推荐你试试。下一期我会接着写递归与分治,题目不难,但坑不少,到时候再分享几个我亲身踩过的调试疑难。学算法这件事,入门最怕的从来不是难度,而是被名词吓住,只要你亲手写出来第一个像样的搜索树,心里那点畏难情绪就全散了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询