算法与数据结构概览
回到计算机科学的源头,去看看人们最初用机器解决现实问题时,到底遇到了什么困境。
一句话概括:计算机只能做简单的存取和运算,而现实世界的问题极其复杂。为了让笨拙的机器在有限的时间和空间里处理海量信息,人类必须精心设计数据的组织方式(数据结构 Data Structures)和计算的执行步骤(算法 Algorithms)。
下边我分三个部分展开:诞生的背景、解决了什么问题,以及为什么非它们不可。
快速导航¶
- 数学基础:初高中核心知识点 - 复杂度分析、排列组合、概率论;§3.1 指数/对数重点
- 数组:历史渊源与内存模型 - 数组诞生背景、Day 1 基础
- 双指针:具体场景与推演 - 对撞/快慢/归并;含「SQL 里看不见的双指针」
- 哈希表:键值映射与 O(1) 查找 - 抽象问题、冲突、与树/数组对比
- 树:二叉树、BST 与 B+ 树 - 遍历与递归、数据库索引、对照 DDIA
- 数据结构与算法 7 天冲刺 - 高频算法、代码模板
推荐阅读(按你当前路径)¶
| 优先级 | 书名 | 适合阶段 | 说明 |
|---|---|---|---|
| ⭐⭐⭐ | 《数据密集型应用系统设计》(DDIA)Kleppmann | 数据工程 / 存储引擎 | 与 ddia-framework 配套;你已在读 |
| ⭐⭐⭐ | 《算法》第 4 版 Sedgewick(有中译本) | 面试算法 | 比 CLRS 好上手;树、排序、图够用 |
| ⭐⭐ | 《labuladong 的算法小抄》 | 面试刷题 | 双指针、二叉树、DP 模板;中文 |
| ⭐⭐ | 《什么是数学》 Courant & Robbins | 数学直觉 | 指数/对数/函数为何存在,通俗经典 |
| ⭐ | 《e 的故事》(e: The Story of a Number)Maor | 数学史 | 复利、纳皮尔、欧拉与 e/对数;补 §3.1 历史 |
| ⭐ | 《算法导论》 CLRS | 查阅字典 | 严谨但厚;不适合第一遍通读,当参考 |
数据工程延伸(计费/数仓方向):《数据仓库工具箱》(Kimball)— 建模与分层,配合 dbt。
电子书 / 免费:DDIA 作者官网章节笔记;LeetCode 题解;你仓库里 数学基础、树 专题已够当「精读笔记」。
青少年 / 历史案例串讲(小学高年级~中学,偏乐趣、防钻牛角)¶
| 适读 | 书名 | 特点 | 和你笔记的衔接 |
|---|---|---|---|
| 小学高年级+ | 《数理化通俗演义》 梁衡 | 真人物、真故事 演义笔法串起数理化史;最好读的「中国版科学史小说」 | 中国算学、近代西学东渐、科学人物 |
| 初中+ | 《数学趣味》(及续集)刘薰宇 | 民国经典科普,短故事、趣题,极薄、极好翻 | 培养「数学好玩」,非系统史 |
| 初中+ | 《数学演义》 张景中 | 院士写的短章,一个问题一段史 | 和 §3.1「函数从何而来」同味 |
| 初中~高中 | 《从一到无穷大》 伽莫夫 | 故事+思想实验,不枯燥;不全是编年史但经典 | 指数、无穷、维度直觉 |
| 初中+ | 《数学的故事》(英 The Story of Mathematics,多译本) | 按历史阶段讲数学发展 | 纳皮尔、欧拉、牛顿一条线 |
| 初中+ | 《e 的故事》 Maor | 单线深讲自然常数 | 直接接 数学基础·纳皮尔/欧拉 |
| 小学+ | 《可怕的科学:迷人的数学》(Horrible Science 系) | 英式幽默、插图多,当零食读 | 轻量入门,不要求连贯 |
| 高中+(人物传) | 《数学大师》 E.T. Bell(中译本) | 名人传记串历史;部分轶事已被考证质疑,当故事别当史料 | 阿基米德、高斯、伽罗瓦等 |
读法建议:这类书用 调味料(每次 10~20 分钟一章),配合 克制深挖;主菜仍是面试/DDIA 胜利条件。
一、诞生的背景:从"能算"到"算得快、算得巧"¶
1. 硬件的硬约束(Hardware Constraints)¶
最早的数字计算机(如 ENIAC)造价昂贵,内存(Memory)小得可怜(可能只有几千字节),运算速度按毫秒计。人想用它算弹道(Trajectory Calculation)、破译密码(Cryptography)、做人口普查统计……但数据量动辄成千上万条。
直接把现实世界的数据"原样"塞进机器里,要么内存爆炸,要么算到天荒地老。这种极端的资源匮乏(Resource Scarcity),逼着人们必须琢磨数据怎样存储最省空间(Space Efficiency)、任务怎样执行最省步骤(Time Efficiency)。
2. 问题从数值走向非数值(From Numerical to Non-numerical Problems)¶
早期编程主要解数值方程(Numerical Equations)。可到了商业数据处理(Business Data Processing)、文本索引(Text Indexing)、排课表(Scheduling)、网络路由(Network Routing)这些问题时,它们涉及的不再是连续的数字,而是离散的对象和对象之间的关系(Discrete Objects and Their Relationships):谁的优先级更高(Priority)?A和B是否连通(Connectivity)?如何最快找到某个名字(Searching)?
这就需要新的表达方式——比如把数据组织成列表(List)、树(Tree)、图(Graph),并为这些结构设计专门的查找、插入、排序步骤。于是"数据结构"作为独立概念浮现出来。
3. 软件危机的刺痛(Software Crisis)¶
60年代,软件规模急剧膨胀,程序动辄数万行,到处是重复、低效且充满 bug 的代码。人们意识到:不能每次都重新发明轮子(Reinvent the Wheel),必须把常见的数据组织方式和处理手段抽象出来,形成独立于具体问题的通用模块(Reusable Components)。
1968年,高德纳(Donald Knuth)开始撰写《计算机程序设计艺术》(The Art of Computer Programming),系统整理了所有这些"轮子";70年代,尼可拉斯·维尔特(Niklaus Wirth)提出著名的公式——"程序 = 数据结构 + 算法"(Programs = Data Structures + Algorithms)。学科由此成型。
4. 常见疑问:牛顿时代为啥不想「数组、哈希、链表、树」?¶
短答:他们不是笨,而是还没有「可随机寻址的大块内存 + 每秒百万次查表」这台机器;数组/哈希/链表/树是 20 世纪中叶为程序在硅片上跑 才成形的抽象,不是牛顿那代人数学里的待发现定理。
| 现代结构 | 牛顿时代在干什么(cousin) | 为何没长成 ADT |
|---|---|---|
| 数组 | 对数表、三角函数表、差分表(牛顿插值);矩阵思想在发展 | 数据在纸上/人脑,没有 base + i×size 的连续地址内存 |
| 哈希 | 字典按部首、图书馆索引、键→页码 | 没有 O(1) 内存寻址;哈希函数 + 冲突处理要等计算机符号表、数据库(约 1950s 起) |
| 链表 | 链条、家谱、上一条/下一条记录 的手工账本 | 没有 指针/链接字段 的存储模型;插入删行是文书问题,不是纳秒级分配 |
| 树 | 家谱、物种分类(林奈稍晚)、逻辑分类 | 图论树19 世纪才有组合对象;BST/B+ 为磁盘/内存搜索是计算机时代问题 |
他们在想什么:行星运动、光学、流数(微积分)、用公式描述连续世界——要的是 位置、速度、力随时间变,不是「一百万条计费记录在内存里按 key 查」。
时间线(极简):
牛顿(17–18C) → 连续数学、表格式插值
电子计算机(1940s+) → 内存地址、程序可改链接
1950s–70s → 符号表、文件系统、数据库 → 链表、哈希、B 树/B+ 树 成为教科书语言
所以:不是「没想过组织数据」,而是问题域和介质不同;你今天学的结构,是 算筹→纸笔→硅片 之后,为 离散、海量、反复寻址 定制的轮子。
二、解决了什么问题?¶
数据结构与算法联合解决了三个核心问题:
1. 数据的高效表示与存取(Efficient Data Representation and Access)¶
举个例子:你有100万条员工记录,每天要按工号查几十万次。如果数据只是杂乱堆在一起,每次查找都要从头扫到尾,一天下来机器啥都别干了。
数据结构给出了专门化的容器(Specialized Containers):用数组(Array)可以随机访问(Random Access),用哈希表(Hash Table)可以接近瞬间找到,用 B+ 树(B+ Tree)可以同时兼顾范围查询(Range Query)和磁盘读写特性(Disk I/O Optimization)。数据结构的存在,让你能根据"读多写少"(Read-heavy)、"频繁插入"(Frequent Insertion)、"需要排序"(Sorting Required)等真实需求,选择最合适的存储方案。
2. 计算的步数与资源可控(Computational Complexity and Resource Control)¶
算法回答的是"为达到目的,最少需要多少步,占用多少额外空间"。
比如同一个排序任务(Sorting),冒泡排序(Bubble Sort)要比较约 n²/2 次,归并排序(Merge Sort)只需约 n log n 次。当 n=100 万时,两者耗时相差数万倍——这就是"算法"存在的意义。不做复杂度分析(Complexity Analysis),程序可能在小数据测试时很好,一上线就崩溃。
关键概念: - 时间复杂度(Time Complexity):算法执行步骤随输入规模的增长速率,用大O表示法(Big O Notation)描述 - 空间复杂度(Space Complexity):算法执行过程中额外占用的内存空间 - 最坏情况(Worst Case) vs 平均情况(Average Case) vs 最好情况(Best Case)
3. 复杂问题的分解与抽象(Problem Decomposition and Abstraction)¶
计算机网络怎么找到最短路径(Shortest Path)?操作系统的进程怎么调度不死锁(Deadlock-free Scheduling)?编译器的表达式如何解析(Expression Parsing)?
这些都需要 图算法(Graph Algorithms,如 Dijkstra、拓扑排序 Topological Sort)、队列与栈(Queue & Stack)、树与堆(Tree & Heap) 等经典组合。数据结构与算法就像一门"通用语言"(Universal Language),让不同领域的工程师能复用相同的思维工具,而不是对着每个问题凭空发明新解法。
三、为什么不是别的?(为什么非得是"数据结构+算法"这条路)¶
这其实是在问:能不能靠别的方法省掉这些繁琐的设计?比如依赖硬件进步、依赖编译器、依赖人工智能……为什么不行?
1. 硬件进步不是万能药(Hardware Advancement Is Not a Panacea)¶
摩尔定律(Moore's Law)让机器越来越快,但问题规模的增长往往是指数级的(Exponential Growth)。密码破解(Password Cracking)、气象模拟(Weather Simulation)、DNA 序列比对(DNA Sequence Alignment),算力需求远超硬件增幅。一个 O(n!) 的穷举算法(Brute-force Algorithm),即使计算机快1亿倍,也挡不住 n 从20变成30。
算法优化的倍率动辄成千上万,这是在工程设计上一劳永逸地"省时间",而不是跟硬件比拼蛮力(Brute Force)。
2. 编程语言只是语法,不负责"策略"(Programming Languages Provide Syntax, Not Strategy)¶
Python、Java 都提供了列表、字典等现成数据结构,似乎不需要自己学了?
错。 语言只提供积木(Building Blocks),选哪块积木、怎么搭,取决于你对底层实现的理解。用 List 做成员检查是 O(n),用 Set 是 O(1);递归(Recursion)写得不好会爆栈(Stack Overflow)。不懂数据结构与算法,你只能用语言默认的构建块拼出低效的系统,而不知道为什么慢。
3. 库函数无法覆盖所有真实场景(Standard Libraries Cannot Cover All Real-world Scenarios)¶
STL、标准库(Standard Library)确实封装了常用结构,可现实问题千奇百怪:
- 数据库索引(Database Indexing)为什么用 B+ 树而不用红黑树(Red-Black Tree)?
- 推荐系统里如何用图存放十亿节点并快速游走(Graph Traversal)?
- 流式数据(Streaming Data)怎样用极小内存求 top K?
现成的函数调用不了这些复杂组合,你必须理解背后的原理,才能定制出全新的结构(比如 LSM 树 LSM-Tree、跳表 Skip List、布隆过滤器 Bloom Filter)。数据结构与算法教的是创造轮子的能力(Ability to Build Wheels),而不仅仅是调用轮子。
4. 人工智能与统计方法也无法取代(AI and Statistical Methods Cannot Replace Fundamentals)¶
现在流行机器学习(Machine Learning),是不是就不用学算法了?
恰恰相反。训练神经网络(Neural Network Training)需要高效的矩阵运算和自动微分(Automatic Differentiation,计算图算法 Computation Graph);在嵌入式设备上部署模型需要压缩、剪枝(Model Compression & Pruning,用到动态规划、贪心等);哪怕大语言模型(LLM)内部,注意力机制的优化(Attention Mechanism Optimization)也离不开对时间复杂度与显存带宽(Memory Bandwidth)的精细控制。算法换了新装,但核心思想(Core Ideas)从未过时。
5. 说到底,是冯·诺依曼体系决定的(Ultimately Dictated by Von Neumann Architecture)¶
只要计算机还是"存储程序、顺序执行指令"的冯·诺依曼结构(Von Neumann Architecture),任何计算在底层都必然体现为:数据在内存中的布局(Data Layout in Memory) + 对数据的访存和运算序列(Memory Access and Operation Sequence)。
数据结构与算法正是对这两个底层事实的最高抽象——它是计算机的"思维方式"本身(Way of Thinking for Computers)。换了别的路径(比如纯靠直觉、靠自然语言描述),机器根本听不懂,也无法保证正确性(Correctness)和效率(Efficiency)。
小结¶
数据结构与算法的诞生,不是因为某个聪明人突发奇想,而是计算机有限能力与无限计算需求之间矛盾的必然产物。
它本质上是人类为了驾驭"计算"这个工具,所形成的一套最朴素、最强大的组织经验——把数据的形态和计算的步奏精雕细琢,让冷冰冰的机器迸发出解决现实难题的巨大能量。至今,它仍然是每个计算系统"对错快慢"的终极裁判(The Ultimate Arbiter of Correctness and Performance)。
附录:核心术语中英对照表¶
| 中文 | 英文 | 说明 |
|---|---|---|
| 数据结构 | Data Structures | 数据的组织方式 |
| 算法 | Algorithms | 解决问题的步骤序列 |
| 时间复杂度 | Time Complexity | 算法执行时间随输入规模的增长速率 |
| 空间复杂度 | Space Complexity | 算法占用内存随输入规模的增长速率 |
| 大O表示法 | Big O Notation | 描述算法复杂度的数学符号 |
| 数组 | Array | 连续内存存储的数据结构 |
| 链表 | Linked List | 通过指针连接的链式存储结构 |
| 哈希表 | Hash Table | 通过哈希函数实现快速查找的结构 |
| 栈 | Stack | LIFO(后进先出)结构 |
| 队列 | Queue | FIFO(先进先出)结构 |
| 二叉树 | Binary Tree | 每个节点最多两个子节点的树形结构 |
| 图 | Graph | 表达多对多关系的非线性结构 |
| 堆 | Heap | 快速获取最值的完全二叉树 |
| 排序 | Sorting | 将数据按特定顺序排列的算法 |
| 查找 | Searching | 在数据集中定位目标元素的算法 |
| 递归 | Recursion | 函数调用自身的编程技巧 |
| 分治法 | Divide and Conquer | 将问题分解为子问题递归求解 |
| 动态规划 | Dynamic Programming | 通过记忆化避免重复计算子问题 |
| 贪心算法 | Greedy Algorithm | 每步选择局部最优解 |
| 回溯法 | Backtracking | 系统枚举所有可能解的DFS策略 |
| 深度优先搜索 | DFS (Depth-First Search) | 优先深入探索的图遍历算法 |
| 广度优先搜索 | BFS (Breadth-First Search) | 按层遍历的图遍历算法 |
| 摩尔定律 | Moore's Law | 集成电路上晶体管数量每两年翻倍的规律 |
| 冯·诺依曼架构 | Von Neumann Architecture | 存储程序的计算机体系结构 |