Files
test-repo/docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md
T
sanbuphy df51f84ab5 docs: 重构 README 附录展示 & 新增多个附录交互组件
README 更新:
- 移除顶部 header.png 横幅图片
- 新增「附录知识库」板块,以 3×3 网格展示 9 大知识领域精选内容
- 附录链接指向部署版网站 (datawhalechina.github.io)
- 阶段表格新增「附录」行,突出 80+ 交互式专题
- 章节标题「新手入门 & PM」简化为「零基础入门」
- News 新增 2026-02-25 附录知识库更新条目

新增交互组件:
- 异步任务队列 (async-task-queues) 演示组件
- 文件存储 (file-storage) 演示组件
- 项目架构 (project-architecture) 演示组件
- 限流与背压 (rate-limiting) 演示组件
- 搜索引擎 (search-engines) 演示组件
- 计算机基础: AppLaunch/BiosUefi/OSBoot 等启动流程演示组件

新增附录文档:
- 前端项目架构 (frontend-project-architecture.md)
- 后端项目架构 (backend-project-architecture.md)

内容优化:
- 算法思维、数据结构、编程语言、调试艺术等多篇附录内容更新
- HTML/CSS 布局、请求旅程等前后端文档完善
- 附录索引页 (index.md) 同步更新
2026-02-25 12:22:49 +08:00

315 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 算法思维入门
::: tip 前言
**如何高效地解决问题?** 你可能遇到过这样的困惑:同一个问题,有人写的代码跑几秒就出结果,有人写的跑几分钟还在转。差别往往在于算法。本章带你理解算法的核心思维方式。
:::
**这篇文章会带你学什么?**
学完这章后,你将获得:
- **问题拆解能力**:面对复杂问题,能想到用分治、递归等策略拆解,而不是一上来就写代码
- **效率判断能力**:用大 O 表示法判断两种解法哪个更高效,而不是凭感觉猜测
- **复杂度思维**:写代码前先估算数据规模和时间要求,选择合适的算法级别
- **后续学习基础**:为高级数据结构、分布式系统、机器学习打下基础
| 章节 | 内容 | 核心概念 |
|-----|------|---------|
| **第 1 章** | 二分查找 | 分治思想、O(log n) |
| **第 2 章** | 排序算法 | 冒泡、快排、归并 |
| **第 3 章** | 复杂度分析 | 时间复杂度、空间复杂度 |
---
## 0. 全景图:算法是什么?
想象你要在一本字典里找一个单词:
- **方法一**:从第一页开始,一页一页翻(线性查找)
- **方法二**:根据首字母定位,再二分查找(二分查找)
两种方法都能找到,但效率天差地别。**算法就是解决问题的方法**。
<AlgorithmDemo />
**算法的核心指标:**
| 指标 | 含义 | 为什么重要 |
|------|------|-----------|
| **时间复杂度** | 运行时间随数据量增长的趋势 | 预测大规模数据的性能 |
| **空间复杂度** | 内存占用随数据量增长的趋势 | 评估内存消耗 |
| **正确性** | 是否总能得到正确结果 | 算法的基本要求 |
::: tip 📊 逐行解读这张表
**时间复杂度**:用大 O 表示法描述。O(n) 表示数据量翻倍,时间翻倍;O(n²) 表示数据量翻倍,时间变成 4 倍。
**空间复杂度**:同样用大 O 表示法。有些算法用空间换时间(如哈希表),有些用时间换空间(如压缩算法)。
**正确性**:算法必须对所有可能的输入都能给出正确结果。边界条件(空输入、极大输入)最容易出错。
:::
---
## 1. 二分查找:每次排除一半
### 1.1 二分查找的原理
::: tip 💡 二分查找如何工作?
**前提**:数据必须有序
**过程**
1. 找到中间元素
2. 如果中间元素等于目标,找到!
3. 如果目标小于中间元素,在左半部分继续
4. 如果目标大于中间元素,在右半部分继续
5. 每次排除一半,直到找到或确定不存在
**时间复杂度**O(log n)
**生活类比**:猜数字游戏。我想一个 1-100 的数,你每次猜中间,我告诉你大了还是小了。最多猜 7 次就能猜中(因为 2⁷ = 128 > 100)。
:::
👇 **动手试试看**
下面这个演示展示了二分查找的工作原理,你可以选择顺序查找或二分查找来对比:
<SearchAlgorithmDemo />
### 1.2 为什么二分查找这么快?
| 数据量 | 线性查找 | 二分查找 |
|--------|---------|---------|
| 100 | 100 次 | 7 次 |
| 1,000 | 1,000 次 | 10 次 |
| 1,000,000 | 1,000,000 次 | 20 次 |
| 1,000,000,000 | 1,000,000,000 次 | 30 次 |
::: tip 逐行解读这张表
**第一列(数据量)**:要查找的数据有多少。可以看到数据量从 100 增长到 10 亿(扩大了 1000 万倍!)
**第二列(线性查找)**:最"笨"的方法,从第一个开始一个一个找。查找次数等于数据量,数据量越大,查找次数越多。
**第三列(二分查找)**:聪明的方法,每次排除一半。查找次数只和数据量的对数有关,即使 10 亿数据也只需要 30 次!
**对比结论**:当数据量达到 100 万时,线性查找需要 100 万次,二分查找只需要 20 次——差距达 5 万倍!
:::
::: tip 对数增长的威力
二分查找的时间复杂度是 O(log n),这意味着:
- 10 亿数据,最多查找 30 次
- 1 万亿数据,最多查找 40 次
这就是对数增长的威力——数据量增加 1000 倍,查找次数只增加 10 次。
:::
---
## 2. 排序:将无序变有序
### 2.1 常见排序算法
| 算法 | 时间复杂度 | 特点 | 适用场景 |
|------|-----------|------|---------|
| **冒泡排序** | O(n²) | 简单但慢 | 教学、小数据量 |
| **选择排序** | O(n²) | 简单但慢 | 小数据量 |
| **插入排序** | O(n²) | 对近乎有序的数据快 | 小数据、近乎有序 |
| **快速排序** | O(n log n) | 实际最快 | 通用排序 |
| **归并排序** | O(n log n) | 稳定排序 | 需要稳定性的场景 |
| **堆排序** | O(n log n) | 原地排序 | 内存受限场景 |
::: tip 📊 逐行解读这张表
**冒泡排序**:最基础的排序算法,就像水底的气泡往上冒一样。简单易懂,但速度最慢。适合学习排序思想,不适合实际使用。
**选择排序**:每次选出最小的放到前面。也很简单,但无论数据是否有序都要做同样多的比较。
**插入排序**:像打扑克牌时整理手牌一样。把每个元素插入到前面已经排好序的部分中。对近乎有序的数据效率很高。
**快速排序**:实际开发中最常用的排序。平均情况下最快,但最坏情况(数据已经有序)会退化到 O(n²)。
**归并排序**:采用"分而治之"的思想,总是 O(n log n),但需要额外空间。适合需要稳定排序的场景。
**堆排序**:利用堆这种数据结构的排序,原地排序(不需要额外空间),但实际运行往往比快速排序慢。
:::
### 2.2 为什么快速排序"快"
::: tip 💡 快速排序的原理
**核心思想**:分治法
1. 选一个"基准"元素
2. 把比基准小的放左边,比基准大的放右边
3. 对左右两部分递归排序
4. 合并结果
**为什么快?**
- 每次划分后,基准元素就到了最终位置
- 平均情况下,每次划分大约排除一半元素
- 时间复杂度 O(n log n)
**生活类比**:整理书架。先抽出一本书,把比它薄的放左边,比它厚的放右边。然后对左右两堆分别重复这个过程。
:::
👇 **动手试试看**
下面这个演示展示了排序算法的可视化,你可以生成数组,观察冒泡排序和快速排序的过程对比:
<SortingAlgorithmDemo />
---
## 3. 递归:自己调用自己
### 3.1 递归的本质
::: tip 💡 什么是递归?
**递归**是函数调用自身的编程技巧。
**两个关键要素**
1. **基本情况**:什么时候停止递归?
2. **递归步骤**:如何把问题分解成更小的子问题?
**经典例子:阶乘**
```js
function factorial(n) {
if (n <= 1) return 1 // 基本情况
return n * factorial(n - 1) // 递归步骤
}
```
**生活类比**:俄罗斯套娃。打开一个娃娃,里面是更小的娃娃,直到最小的那个打不开为止。
:::
### 3.2 递归 vs 迭代
| 特性 | 递归 | 迭代(循环) |
|------|------|-------------|
| **代码简洁度** | 通常更简洁 | 可能更复杂 |
| **内存消耗** | 较高(调用栈) | 较低 |
| **性能** | 稍慢(函数调用开销) | 更快 |
| **适用场景** | 树遍历、分治算法 | 简单重复任务 |
::: tip 📊 逐行解读这张表
**代码简洁度**:递归通常只需要几行代码就能表达复杂的逻辑(如遍历树结构),而用循环可能需要更多的变量和嵌套。
**内存消耗**:递归会使用"调用栈"来保存每一层的信息,就像叠盘子一样,每递归一层就多一个盘子。循环则不需要这种开销。
**性能**:每次函数调用都有开销(参数传递、栈操作等),所以递归通常比循环慢一些。
**适用场景**:递归擅长处理本身就是递归结构的问题(如文件树、DOM 树);循环擅长简单的重复操作(如遍历数组)。
:::
::: warning ⚠️ 递归的陷阱
**栈溢出**:递归层次太深,调用栈空间耗尽。
**解决方法**
- 改用迭代
- 使用尾递归优化(某些语言支持)
- 限制递归深度
:::
👇 **动手试试看**
下面这个演示展示了递归的调用过程,观察函数如何自己调用自己:
<RecursiveThinkingDemo />
---
## 4. 贪心算法:每步选最优
### 4.1 贪心的思想
::: tip 💡 什么是贪心算法?
**贪心算法**在每一步都选择当前看起来最优的选择,希望最终得到全局最优解。
**适用条件**
1. **贪心选择性质**:局部最优能导致全局最优
2. **最优子结构**:问题的最优解包含子问题的最优解
**经典例子:硬币找零**
- 目标:用最少的硬币凑出指定金额
- 贪心策略:每次选最大的硬币
- 结果:67 元 = 50 + 10 + 5 + 1 + 15 枚)
**生活类比**:登山时,每次都选最陡的路往上走。虽然不一定能到最高峰,但通常能到不错的位置。
:::
### 4.2 贪心的局限性
::: warning ⚠️ 贪心不一定得到最优解
**反例:硬币找零**
如果硬币面值是 [1, 3, 4],要凑 6 元:
- 贪心:4 + 1 + 1 = 3 枚
- 最优:3 + 3 = 2 枚
贪心算法在这里失败了!
**教训**:贪心算法简单高效,但不总是能得到最优解。使用前要证明问题满足贪心条件。
:::
👇 **动手试试看**
下面这个演示展示了贪心算法的实际效果,你可以尝试不同的硬币组合,观察贪心策略的表现:
<GreedyThinkingDemo />
---
## 5. 算法设计范式
| 范式 | 思想 | 典型算法 | 适用问题 |
|------|------|---------|---------|
| **分治** | 把问题分解成小问题 | 快速排序、归并排序 | 可分解的问题 |
| **贪心** | 每步选最优 | 最小生成树、霍夫曼编码 | 有贪心性质的问题 |
| **动态规划** | 记录子问题的解 | 背包问题、最短路径 | 有重叠子问题 |
| **回溯** | 试错,走不通就回退 | 八皇后、全排列 | 搜索问题 |
::: tip 📊 逐行解读这张表
**分治**:把大问题拆成小问题,分别解决后再合并。就像整理房间,先分成客厅、卧室、厨房分别打扫,最后整体整洁。
**贪心**:每步都选当前最好的,不考虑长远后果。像吃饭时先挑最喜欢吃的菜,可能不是最优的吃法,但速度快。
**动态规划**:记住中间结果,避免重复计算。像记笔记,下次遇到同样问题直接查答案,不用重新推导。
**回溯**:走不通就退回来重试。像走迷宫,此路不通就返回上一个路口尝试别的路。
:::
👇 **动手试试看**
下面这个演示展示了不同算法设计范式的特点和应用场景:
<AlgorithmParadigmDemo />
---
## 6. 总结:算法是解决问题的艺术
让我们用一个比喻总结各种算法思想:
| 思想 | 比喻 | 核心要点 |
|------|------|---------|
| **二分查找** | 猜数字 | 每次排除一半 |
| **排序** | 整理书架 | 建立秩序 |
| **递归** | 俄罗斯套娃 | 化大为小 |
| **贪心** | 登山选路 | 局部最优 |
::: tip 💡 核心启示
**算法的本质是"效率"和"正确性"的平衡。**
- 好的算法能让程序效率提升几个数量级
- 但过度优化可能引入复杂性
- 先保证正确,再追求效率
理解算法思维,比记住具体算法更重要:
- 分治:把大问题分解成小问题
- 贪心:每步选最优
- 动态规划:记录子问题的解
- 回溯:试错,走不通就回退
:::
---
## 延伸阅读
- **算法导论**:系统学习算法的经典教材
- **LeetCode**:通过刷题提升算法能力
- **算法可视化**:直观理解算法执行过程
- **竞赛算法**:学习更高级的算法技巧