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) 同步更新
This commit is contained in:
@@ -69,6 +69,11 @@
|
||||
**生活类比**:猜数字游戏。我想一个 1-100 的数,你每次猜中间,我告诉你大了还是小了。最多猜 7 次就能猜中(因为 2⁷ = 128 > 100)。
|
||||
:::
|
||||
|
||||
👇 **动手试试看**:
|
||||
下面这个演示展示了二分查找的工作原理,你可以选择顺序查找或二分查找来对比:
|
||||
|
||||
<SearchAlgorithmDemo />
|
||||
|
||||
### 1.2 为什么二分查找这么快?
|
||||
|
||||
| 数据量 | 线性查找 | 二分查找 |
|
||||
@@ -78,7 +83,17 @@
|
||||
| 1,000,000 | 1,000,000 次 | 20 次 |
|
||||
| 1,000,000,000 | 1,000,000,000 次 | 30 次 |
|
||||
|
||||
::: tip 💡 对数增长的威力
|
||||
::: tip � 逐行解读这张表
|
||||
**第一列(数据量)**:要查找的数据有多少。可以看到数据量从 100 增长到 10 亿(扩大了 1000 万倍!)
|
||||
|
||||
**第二列(线性查找)**:最"笨"的方法,从第一个开始一个一个找。查找次数等于数据量,数据量越大,查找次数越多。
|
||||
|
||||
**第三列(二分查找)**:聪明的方法,每次排除一半。查找次数只和数据量的对数有关,即使 10 亿数据也只需要 30 次!
|
||||
|
||||
**对比结论**:当数据量达到 100 万时,线性查找需要 100 万次,二分查找只需要 20 次——差距达 5 万倍!
|
||||
:::
|
||||
|
||||
::: tip � 对数增长的威力
|
||||
二分查找的时间复杂度是 O(log n),这意味着:
|
||||
|
||||
- 10 亿数据,最多查找 30 次
|
||||
@@ -102,6 +117,20 @@
|
||||
| **归并排序** | O(n log n) | 稳定排序 | 需要稳定性的场景 |
|
||||
| **堆排序** | O(n log n) | 原地排序 | 内存受限场景 |
|
||||
|
||||
::: tip 📊 逐行解读这张表
|
||||
**冒泡排序**:最基础的排序算法,就像水底的气泡往上冒一样。简单易懂,但速度最慢。适合学习排序思想,不适合实际使用。
|
||||
|
||||
**选择排序**:每次选出最小的放到前面。也很简单,但无论数据是否有序都要做同样多的比较。
|
||||
|
||||
**插入排序**:像打扑克牌时整理手牌一样。把每个元素插入到前面已经排好序的部分中。对近乎有序的数据效率很高。
|
||||
|
||||
**快速排序**:实际开发中最常用的排序。平均情况下最快,但最坏情况(数据已经有序)会退化到 O(n²)。
|
||||
|
||||
**归并排序**:采用"分而治之"的思想,总是 O(n log n),但需要额外空间。适合需要稳定排序的场景。
|
||||
|
||||
**堆排序**:利用堆这种数据结构的排序,原地排序(不需要额外空间),但实际运行往往比快速排序慢。
|
||||
:::
|
||||
|
||||
### 2.2 为什么快速排序"快"?
|
||||
|
||||
::: tip 💡 快速排序的原理
|
||||
@@ -120,6 +149,11 @@
|
||||
**生活类比**:整理书架。先抽出一本书,把比它薄的放左边,比它厚的放右边。然后对左右两堆分别重复这个过程。
|
||||
:::
|
||||
|
||||
👇 **动手试试看**:
|
||||
下面这个演示展示了排序算法的可视化,你可以生成数组,观察冒泡排序和快速排序的过程对比:
|
||||
|
||||
<SortingAlgorithmDemo />
|
||||
|
||||
---
|
||||
|
||||
## 3. 递归:自己调用自己
|
||||
@@ -153,6 +187,16 @@ function factorial(n) {
|
||||
| **性能** | 稍慢(函数调用开销) | 更快 |
|
||||
| **适用场景** | 树遍历、分治算法 | 简单重复任务 |
|
||||
|
||||
::: tip 📊 逐行解读这张表
|
||||
**代码简洁度**:递归通常只需要几行代码就能表达复杂的逻辑(如遍历树结构),而用循环可能需要更多的变量和嵌套。
|
||||
|
||||
**内存消耗**:递归会使用"调用栈"来保存每一层的信息,就像叠盘子一样,每递归一层就多一个盘子。循环则不需要这种开销。
|
||||
|
||||
**性能**:每次函数调用都有开销(参数传递、栈操作等),所以递归通常比循环慢一些。
|
||||
|
||||
**适用场景**:递归擅长处理本身就是递归结构的问题(如文件树、DOM 树);循环擅长简单的重复操作(如遍历数组)。
|
||||
:::
|
||||
|
||||
::: warning ⚠️ 递归的陷阱
|
||||
**栈溢出**:递归层次太深,调用栈空间耗尽。
|
||||
|
||||
@@ -162,6 +206,11 @@ function factorial(n) {
|
||||
- 限制递归深度
|
||||
:::
|
||||
|
||||
👇 **动手试试看**:
|
||||
下面这个演示展示了递归的调用过程,观察函数如何自己调用自己:
|
||||
|
||||
<RecursiveThinkingDemo />
|
||||
|
||||
---
|
||||
|
||||
## 4. 贪心算法:每步选最优
|
||||
@@ -197,6 +246,11 @@ function factorial(n) {
|
||||
**教训**:贪心算法简单高效,但不总是能得到最优解。使用前要证明问题满足贪心条件。
|
||||
:::
|
||||
|
||||
👇 **动手试试看**:
|
||||
下面这个演示展示了贪心算法的实际效果,你可以尝试不同的硬币组合,观察贪心策略的表现:
|
||||
|
||||
<GreedyThinkingDemo />
|
||||
|
||||
---
|
||||
|
||||
## 5. 算法设计范式
|
||||
@@ -208,6 +262,21 @@ function factorial(n) {
|
||||
| **动态规划** | 记录子问题的解 | 背包问题、最短路径 | 有重叠子问题 |
|
||||
| **回溯** | 试错,走不通就回退 | 八皇后、全排列 | 搜索问题 |
|
||||
|
||||
::: tip 📊 逐行解读这张表
|
||||
**分治**:把大问题拆成小问题,分别解决后再合并。就像整理房间,先分成客厅、卧室、厨房分别打扫,最后整体整洁。
|
||||
|
||||
**贪心**:每步都选当前最好的,不考虑长远后果。像吃饭时先挑最喜欢吃的菜,可能不是最优的吃法,但速度快。
|
||||
|
||||
**动态规划**:记住中间结果,避免重复计算。像记笔记,下次遇到同样问题直接查答案,不用重新推导。
|
||||
|
||||
**回溯**:走不通就退回来重试。像走迷宫,此路不通就返回上一个路口尝试别的路。
|
||||
:::
|
||||
|
||||
👇 **动手试试看**:
|
||||
下面这个演示展示了不同算法设计范式的特点和应用场景:
|
||||
|
||||
<AlgorithmParadigmDemo />
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结:算法是解决问题的艺术
|
||||
|
||||
@@ -1,230 +1,293 @@
|
||||
# 数据结构
|
||||
|
||||
::: tip 前言
|
||||
**如何高效地组织和存储数据?** 你可能遇到过这样的困惑:为什么有些程序处理几万条数据很快,有些处理几百条就卡住了?答案往往在于数据结构的选择。本章带你理解常见数据结构的特点和适用场景。
|
||||
**程序 = 数据结构 + 算法。** 前面我们学了 CPU 如何执行指令、操作系统如何管理资源。但程序要处理的核心对象是**数据**——用户信息、商品列表、社交关系……这些数据怎么在内存里组织,直接决定了程序的快慢。你可能遇到过这样的困惑:为什么有些程序处理几万条数据很快,有些处理几百条就卡住了?答案往往就在于**数据结构的选择**。
|
||||
:::
|
||||
|
||||
**这篇文章会带你学什么?**
|
||||
|
||||
学完这章后,你将获得:
|
||||
|
||||
- **选型决策能力**:知道什么时候用数组快速访问,什么时候用链表灵活插入
|
||||
- **性能分析视角**:能判断性能问题是数据结构选择不当,还是算法效率低下
|
||||
- **直觉判断力**:看到一个需求,脑子里自动浮现该用什么数据结构
|
||||
- **性能分析视角**:能判断性能瓶颈是数据结构选错了,还是算法效率低
|
||||
- **权衡思维**:理解"空间换时间"与"时间换空间",知道没有完美的数据结构
|
||||
- **后续学习基础**:为数据库、缓存系统、搜索引擎等技术打下基础
|
||||
- **代码阅读能力**:看到 HashMap、Stack、Queue 这些词不再陌生
|
||||
- **后续学习基础**:为数据库索引、缓存系统、搜索引擎等技术打下基础
|
||||
|
||||
| 章节 | 内容 | 核心概念 |
|
||||
|-----|------|---------|
|
||||
| **第 1 章** | 线性结构 | 数组、链表、栈、队列 |
|
||||
| **第 2 章** | 哈希结构 | 哈希表、冲突处理 |
|
||||
| **第 3 章** | 树形结构 | 二叉树、B树、堆 |
|
||||
| **第 4 章** | 图结构 | 有向图、无向图、遍历算法 |
|
||||
| **第 1 章** | 全景图 | 四大类数据结构、分类标准 |
|
||||
| **第 2 章** | 线性结构 | 数组、链表、栈、队列 |
|
||||
| **第 3 章** | 哈希表 | 哈希函数、冲突处理、O(1) 查找 |
|
||||
| **第 4 章** | 树形结构 | 二叉树、文件系统树、DOM 树 |
|
||||
| **第 5 章** | 图结构 | 有向图、无向图、遍历算法 |
|
||||
| **第 6 章** | 性能对比 | 时间复杂度、空间复杂度 |
|
||||
| **第 7 章** | 选型指南 | 场景分析、决策流程 |
|
||||
|
||||
---
|
||||
|
||||
## 0. 全景图:数据结构是什么?
|
||||
## 1. 全景图:数据结构是什么?
|
||||
|
||||
想象你要整理一堆书:
|
||||
|
||||
- **堆在地上**:找书要一本本翻(链表)
|
||||
- **按编号放书架**:直接去对应位置拿(数组)
|
||||
- **按类别分柜子**:先找柜子再找书(哈希表)
|
||||
- **按书名排序**:二分查找,每次排除一半(树)
|
||||
- **堆在地上**:找书要一本本翻——这就是最原始的存储
|
||||
- **按编号放书架**:直接去对应位置拿——这就是**数组**
|
||||
- **按类别分柜子**:先确定柜子再找书——这就是**哈希表**
|
||||
- **按书名排序放多层架**:每次排除一半——这就是**树**
|
||||
|
||||
不同的整理方式,找书的效率天差地别。**数据结构就是数据的"整理方式"**。
|
||||
不同的整理方式,找书的效率天差地别。**数据结构就是数据的"整理方式"**——它决定了数据怎么存、怎么找、怎么改。
|
||||
|
||||
<DataStructureDemo />
|
||||
<DataStructureOverviewDemo />
|
||||
|
||||
**常见数据结构分类:**
|
||||
所有数据结构可以归为四大类:
|
||||
|
||||
| 类型 | 特点 | 典型代表 | 适用场景 |
|
||||
|------|------|---------|---------|
|
||||
| **线性结构** | 数据排成一排 | 数组、链表、栈、队列 | 顺序处理、历史记录 |
|
||||
| **哈希结构** | 键值对映射 | 哈希表 | 快速查找、缓存 |
|
||||
| **树形结构** | 层次关系 | 二叉树、B树 | 排序、搜索、文件系统 |
|
||||
| **图结构** | 网状关系 | 有向图、无向图 | 社交网络、路径规划 |
|
||||
| 类型 | 数据关系 | 典型代表 | 生活类比 |
|
||||
|------|---------|---------|---------|
|
||||
| **线性结构** | 一对一,排成一排 | 数组、链表、栈、队列 | 火车车厢、排队队伍 |
|
||||
| **哈希结构** | 键→值映射 | 哈希表、字典、集合 | 图书馆索引卡片 |
|
||||
| **树形结构** | 一对多,层级关系 | 二叉树、B树、堆 | 家族族谱、文件夹 |
|
||||
| **图结构** | 多对多,网状关系 | 有向图、无向图 | 地铁线路图、社交网络 |
|
||||
|
||||
::: tip 📊 逐行解读这张表
|
||||
**线性结构**:最简单的数据组织方式,数据一个接一个排列。数组适合随机访问,链表适合频繁插入删除。
|
||||
|
||||
**哈希结构**:用"键"直接找到"值",查找速度最快。但需要处理"冲突"问题(两个键映射到同一位置)。
|
||||
|
||||
**树形结构**:有层次关系的数据。二叉搜索树适合排序和搜索,B树适合磁盘存储(数据库索引)。
|
||||
|
||||
**图结构**:最复杂的结构,表示任意的关系网络。社交网络、地图导航都用图来建模。
|
||||
::: tip 为什么要学这么多种?
|
||||
因为**没有万能的数据结构**。每种结构都是在"查找速度"、"插入速度"、"内存占用"之间做权衡。就像你不会用书包装家具,也不会用卡车送一封信——选对工具,事半功倍。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. 线性结构:最基础的组织方式
|
||||
## 2. 线性结构:最基础的组织方式
|
||||
|
||||
### 1.1 数组:连续存储
|
||||
线性结构是最直觉的数据组织方式——数据一个接一个排列,就像火车车厢。但"怎么连接"和"从哪端操作"的不同,产生了四种变体,各有各的绝活。
|
||||
|
||||
::: tip 💡 数组的特点
|
||||
**数组**是一块连续的内存空间,每个元素大小相同。
|
||||
<LinearStructuresDemo />
|
||||
|
||||
**优点**:
|
||||
- 随机访问快:`arr[100]` 直接计算地址,O(1)
|
||||
- 缓存友好:连续存储,CPU 缓存命中率高
|
||||
### 2.1 数组 vs 链表:两种截然不同的存储方式
|
||||
|
||||
**缺点**:
|
||||
- 插入删除慢:需要移动后面所有元素,O(n)
|
||||
- 大小固定:需要预先分配空间
|
||||
数组和链表是最基础的两种线性结构,它们的核心区别在于**内存布局**:
|
||||
|
||||
**生活类比**:一排编号的储物柜,每个柜子大小相同。找第 10 号柜子直接去,但要在中间插入一个柜子,后面的都要往后挪。
|
||||
| 对比维度 | 数组 | 链表 |
|
||||
|---------|------|------|
|
||||
| **内存布局** | 连续的一整块 | 散落在各处,用指针串起来 |
|
||||
| **访问第 n 个** | 直接算地址,O(1) | 从头一个个找,O(n) |
|
||||
| **中间插入** | 后面的都要挪,O(n) | 改两个指针就行,O(1) |
|
||||
| **大小** | 创建时就固定了 | 随时可以增长 |
|
||||
| **生活类比** | 一排编号储物柜 | 寻宝游戏的线索链 |
|
||||
|
||||
::: tip 什么时候用数组?什么时候用链表?
|
||||
- **数据量已知、频繁按位置访问** → 数组(比如学生成绩表、像素矩阵)
|
||||
- **数据量未知、频繁插入删除** → 链表(比如播放列表、撤销历史)
|
||||
- **不确定?** → 先用数组。大多数场景下,数组的缓存友好性带来的性能优势更大
|
||||
:::
|
||||
|
||||
### 1.2 链表:节点相连
|
||||
### 2.2 栈和队列:加了"规矩"的线性结构
|
||||
|
||||
::: tip 💡 链表的特点
|
||||
**链表**由一系列节点组成,每个节点包含数据和指向下一个节点的指针。
|
||||
栈和队列本质上就是数组或链表,只是**限制了操作方式**。看起来功能变少了,但正是这种限制让它们有了明确的用途:
|
||||
|
||||
**优点**:
|
||||
- 插入删除快:只需修改指针,O(1)
|
||||
- 大小灵活:可以动态增长
|
||||
| 结构 | 规则 | 操作 | 类比 | 你写的代码里在哪? |
|
||||
|------|------|------|------|-----------------|
|
||||
| **栈** | 后进先出 (LIFO) | push / pop | 一摞盘子 | 函数调用栈、浏览器后退、Ctrl+Z 撤销 |
|
||||
| **队列** | 先进先出 (FIFO) | enqueue / dequeue | 排队买票 | 任务调度、消息队列、打印队列 |
|
||||
|
||||
**缺点**:
|
||||
- 访问慢:要从头开始遍历,O(n)
|
||||
- 额外空间:每个节点需要存储指针
|
||||
|
||||
**生活类比**:寻宝游戏,每个线索指向下一个地点。要找第 10 个线索,必须从第 1 个开始一步步找。
|
||||
:::
|
||||
|
||||
### 1.3 栈和队列:受限的线性结构
|
||||
|
||||
| 结构 | 规则 | 操作 | 类比 | 应用 |
|
||||
|------|------|------|------|------|
|
||||
| **栈** | 后进先出 (LIFO) | push/pop | 一摞盘子 | 函数调用、撤销操作 |
|
||||
| **队列** | 先进先出 (FIFO) | enqueue/dequeue | 排队买票 | 任务调度、消息队列 |
|
||||
|
||||
::: tip 💡 为什么要有"受限"的结构?
|
||||
栈和队列看起来比数组、链表功能少,但正是这种"限制"让它们有明确的用途:
|
||||
|
||||
- **栈**:函数调用时,最后调用的函数最先返回
|
||||
- **队列**:任务调度时,先来的任务先处理
|
||||
|
||||
限制带来简洁,简洁带来高效。
|
||||
::: tip 为什么"限制"反而是好事?
|
||||
想象一个只有"放盘子"和"拿盘子"两个操作的栈——你永远不会拿错顺序。**限制带来确定性,确定性带来可靠性。** 函数调用栈就是靠"后进先出"保证最后调用的函数最先返回,如果允许随意访问中间的函数,程序就乱套了。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 哈希表:最快的查找
|
||||
## 3. 哈希表:最快的查找
|
||||
|
||||
### 2.1 哈希表原理
|
||||
线性结构的查找都不够快——数组要遍历 O(n),即使排好序用二分查找也要 O(log n)。有没有一种结构能做到 **O(1) 直接找到**?有,就是哈希表。
|
||||
|
||||
::: tip 💡 哈希表如何工作?
|
||||
**哈希表**通过"哈希函数"把键映射到数组索引。
|
||||
<HashTableDemo />
|
||||
|
||||
**工作流程**:
|
||||
1. 输入键(如 "apple")
|
||||
2. 哈希函数计算:`hash("apple") = 3`
|
||||
3. 直接去数组索引 3 的位置找
|
||||
### 3.1 哈希表的核心思想
|
||||
|
||||
**冲突处理**:
|
||||
- 两个不同的键可能映射到同一索引
|
||||
- 解决方法:链地址法(同一位置用链表存储多个值)
|
||||
哈希表的原理其实很简单:
|
||||
|
||||
**生活类比**:图书馆按书名首字母分柜子。"Apple" 开头的书都放 A 柜,"Banana" 开头的放 B 柜。找书时先确定柜子,再在柜子里找。
|
||||
:::
|
||||
1. 你给一个**键**(比如 "apple")
|
||||
2. **哈希函数**把键算成一个数字(比如 `hash("apple") = 3`)
|
||||
3. 直接去数组的第 3 个位置找——不用遍历,一步到位
|
||||
|
||||
### 2.2 哈希表的时间复杂度
|
||||
这就像图书馆的索引系统:你不用在一排排书架上找,查索引卡片就能直接定位到书的位置。
|
||||
|
||||
| 操作 | 平均情况 | 最坏情况 |
|
||||
|------|---------|---------|
|
||||
### 3.2 哈希冲突:两个键撞车了怎么办?
|
||||
|
||||
两个不同的键可能算出同一个索引——这叫**哈希冲突**。就像两本书的索引号相同,都指向同一个位置。
|
||||
|
||||
| 解决方法 | 原理 | 类比 |
|
||||
|---------|------|------|
|
||||
| **链地址法** | 同一位置用链表存多个值 | 同一个柜子里放多本书 |
|
||||
| **开放寻址法** | 冲突了就往后找空位 | 柜子满了就放隔壁柜子 |
|
||||
|
||||
### 3.3 哈希表的性能
|
||||
|
||||
| 操作 | 平均情况 | 最坏情况(全部冲突) |
|
||||
|------|---------|-------------------|
|
||||
| **查找** | O(1) | O(n) |
|
||||
| **插入** | O(1) | O(n) |
|
||||
| **删除** | O(1) | O(n) |
|
||||
|
||||
::: warning ⚠️ 什么时候会退化?
|
||||
当所有键都映射到同一个索引时,哈希表退化为链表,所有操作变成 O(n)。
|
||||
::: warning 什么时候会退化?
|
||||
当所有键都映射到同一个索引时,哈希表退化为链表,所有操作变成 O(n)。避免方法:选择好的哈希函数 + 动态扩容(负载因子超过阈值时扩容)。
|
||||
:::
|
||||
|
||||
**避免方法**:
|
||||
- 选择好的哈希函数
|
||||
- 动态扩容(负载因子超过阈值时扩容)
|
||||
::: tip 哈希表在你的代码里无处不在
|
||||
- JavaScript 的 `{}` 对象和 `Map` → 哈希表
|
||||
- Python 的 `dict` → 哈希表
|
||||
- Java 的 `HashMap` → 哈希表
|
||||
- 数据库的索引 → 底层也用哈希
|
||||
|
||||
你每次写 `user["name"]` 或 `map.get("key")`,背后都是哈希表在工作。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 树:层次结构
|
||||
## 4. 树形结构:层级关系的表达
|
||||
|
||||
### 3.1 二叉搜索树
|
||||
哈希表查找快,但数据是无序的。如果你需要**既能快速查找,又能保持数据有序**,就需要树形结构了。
|
||||
|
||||
树的核心特征:每个节点可以有多个"孩子",但只有一个"父亲"(根节点除外)。这种一对多的层级关系,在现实中随处可见。
|
||||
|
||||
<TreeStructureDemo />
|
||||
|
||||
### 4.1 二叉搜索树:有序的树
|
||||
|
||||
二叉搜索树有一个简单但强大的规则:**左小右大**。
|
||||
|
||||
::: tip 💡 二叉搜索树的规则
|
||||
**二叉搜索树**是一种特殊的二叉树:
|
||||
- 左子树的所有值 < 根节点
|
||||
- 右子树的所有值 > 根节点
|
||||
|
||||
**查找过程**:
|
||||
1. 从根节点开始
|
||||
2. 如果目标值 < 当前值,往左走
|
||||
3. 如果目标值 > 当前值,往右走
|
||||
4. 每次比较排除一半节点
|
||||
查找时,每次比较都能排除一半节点,时间复杂度 O(log n)。就像猜数字游戏——"比 50 大还是小?""大。""比 75 大还是小?"——每次排除一半。
|
||||
|
||||
**时间复杂度**:O(log n),但最坏情况(变成链表)是 O(n)
|
||||
:::
|
||||
### 4.2 平衡树:防止退化
|
||||
|
||||
### 3.2 平衡树
|
||||
二叉搜索树有个问题:如果数据按顺序插入(1, 2, 3, 4, 5),树会退化成一条链,查找变回 O(n)。平衡树通过自动调整结构来避免这个问题:
|
||||
|
||||
为了防止二叉搜索树退化,引入了**平衡树**:
|
||||
| 类型 | 平衡策略 | 特点 | 典型应用 |
|
||||
|------|---------|------|---------|
|
||||
| **AVL 树** | 严格平衡(高度差 ≤ 1) | 查找最快,插入删除稍慢 | 需要频繁查找的场景 |
|
||||
| **红黑树** | 近似平衡 | 综合性能好 | Java TreeMap、Linux 内核 |
|
||||
| **B 树** | 多路平衡,一个节点存多个值 | 减少磁盘 I/O | 数据库索引 |
|
||||
|
||||
| 类型 | 平衡方式 | 特点 |
|
||||
|------|---------|------|
|
||||
| **AVL 树** | 严格平衡(高度差 ≤ 1) | 查找最快,插入删除稍慢 |
|
||||
| **红黑树** | 近似平衡 | 综合性能好,应用最广 |
|
||||
| **B 树** | 多路平衡 | 适合磁盘存储,数据库索引 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 如何选择数据结构?
|
||||
|
||||
| 需求 | 推荐结构 | 原因 |
|
||||
|------|---------|------|
|
||||
| **快速随机访问** | 数组 | O(1) 索引访问 |
|
||||
| **频繁插入删除** | 链表 | O(1) 插入删除 |
|
||||
| **快速查找** | 哈希表 | O(1) 平均查找 |
|
||||
| **有序数据** | 平衡树 | O(log n) 查找,保持有序 |
|
||||
| **最近使用** | 栈 | LIFO 特性 |
|
||||
| **任务排队** | 队列 | FIFO 特性 |
|
||||
|
||||
::: tip 💡 选择数据结构的心法
|
||||
**没有最好的数据结构,只有最合适的数据结构。**
|
||||
|
||||
选择时要考虑:
|
||||
1. **主要操作是什么?** 查找?插入?删除?
|
||||
2. **数据量多大?** 小数据量差别不大,大数据量要慎重
|
||||
3. **数据有序吗?** 有序数据可以用二分查找
|
||||
4. **内存限制?** 某些结构需要额外空间
|
||||
::: tip 树在你的代码里在哪?
|
||||
- **文件系统**:文件夹嵌套就是树结构
|
||||
- **HTML DOM**:`<html>` → `<body>` → `<div>` → `<p>` 就是一棵树
|
||||
- **数据库索引**:B+ 树让百万级数据的查找只需要 3-4 次磁盘读取
|
||||
- **JSON/XML**:嵌套的数据格式本质上就是树
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5. 总结:数据结构是程序的基础
|
||||
## 5. 图结构:复杂关系的网络
|
||||
|
||||
让我们用一个比喻总结各种数据结构:
|
||||
树只能表示"一对多"的层级关系。但现实中很多关系是"多对多"的——你的朋友也有朋友,城市之间有多条路可以走。这种**任意节点之间都可能有连接**的结构,就是图。
|
||||
|
||||
| 结构 | 比喻 | 核心特点 |
|
||||
|------|------|---------|
|
||||
| **数组** | 编号储物柜 | 访问快,插入慢 |
|
||||
| **链表** | 寻宝线索 | 插入快,访问慢 |
|
||||
| **栈** | 一摞盘子 | 后进先出 |
|
||||
| **队列** | 排队队伍 | 先进先出 |
|
||||
| **哈希表** | 分类柜子 | 查找最快 |
|
||||
| **树** | 家族族谱 | 层次结构 |
|
||||
<GraphStructureDemo />
|
||||
|
||||
::: tip 💡 核心启示
|
||||
**数据结构决定了程序的效率上限。**
|
||||
### 5.1 图的三种形态
|
||||
|
||||
- 选对数据结构,问题迎刃而解
|
||||
- 选错数据结构,再好的算法也无济于事
|
||||
| 类型 | 特点 | 类比 | 典型应用 |
|
||||
|------|------|------|---------|
|
||||
| **无向图** | 边没有方向,A→B 等于 B→A | 微信好友(互相的) | 社交网络、通信网络 |
|
||||
| **有向图** | 边有方向,A→B 不等于 B→A | 微博关注(单向的) | 网页链接、依赖关系 |
|
||||
| **带权图** | 边有权重(距离、费用等) | 城市间的公路(有里程数) | 地图导航、最短路径 |
|
||||
|
||||
理解数据结构,就是理解"如何高效地组织数据"。这是每个程序员的基本功。
|
||||
### 5.2 图的遍历
|
||||
|
||||
图的遍历比线性结构复杂,因为可能有环(A→B→C→A),需要记录"已访问"的节点:
|
||||
|
||||
| 遍历方式 | 策略 | 类比 | 适用场景 |
|
||||
|---------|------|------|---------|
|
||||
| **BFS(广度优先)** | 先访问所有邻居,再访问邻居的邻居 | 水波纹扩散 | 最短路径、层级遍历 |
|
||||
| **DFS(深度优先)** | 一条路走到底,走不通再回头 | 走迷宫 | 路径搜索、连通性判断 |
|
||||
|
||||
::: tip 图在现实中的应用
|
||||
- **地图导航**:城市是节点,道路是边,导航就是在图上找最短路径
|
||||
- **社交网络**:用户是节点,关注/好友是边,"你可能认识的人"就是图算法推荐的
|
||||
- **包管理器**:npm/pip 的依赖关系就是有向图,`npm install` 就是在做图的拓扑排序
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能对比:一张表看清所有数据结构
|
||||
|
||||
学了这么多数据结构,它们的性能到底差多少?下面这个交互式对比能帮你建立直觉:
|
||||
|
||||
<DataStructureDemo />
|
||||
|
||||
**核心性能对比表:**
|
||||
|
||||
| 数据结构 | 访问 | 查找 | 插入 | 删除 | 空间 |
|
||||
|---------|------|------|------|------|------|
|
||||
| **数组** | O(1) | O(n) | O(n) | O(n) | O(n) |
|
||||
| **链表** | O(n) | O(n) | O(1) | O(1) | O(n) |
|
||||
| **栈/队列** | O(n) | O(n) | O(1) | O(1) | O(n) |
|
||||
| **哈希表** | — | O(1) | O(1) | O(1) | O(n) |
|
||||
| **二叉搜索树** | — | O(log n) | O(log n) | O(log n) | O(n) |
|
||||
| **图** | — | O(V+E) | O(1) | O(E) | O(V+E) |
|
||||
|
||||
::: tip 怎么读这张表?
|
||||
- **O(1)**:不管数据量多大,操作时间恒定——最快
|
||||
- **O(log n)**:数据量翻倍,时间只多一步——很快
|
||||
- **O(n)**:数据量翻倍,时间也翻倍——一般
|
||||
- **O(V+E)**:取决于节点数和边数——图的特殊表示
|
||||
|
||||
注意:这些都是**平均情况**。最坏情况下,哈希表会退化到 O(n),二叉搜索树也会退化到 O(n)。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 7. 选型指南:该用哪种数据结构?
|
||||
|
||||
学了这么多数据结构,面对实际需求时该怎么选?关键是**从需求出发**,问自己几个问题:
|
||||
|
||||
1. **最频繁的操作是什么?** 查找?插入?删除?遍历?
|
||||
2. **数据之间有什么关系?** 一对一?一对多?多对多?
|
||||
3. **数据量有多大?** 几十条和几百万条的最优选择可能完全不同
|
||||
4. **需要有序吗?** 是否需要按某种顺序遍历数据
|
||||
|
||||
<DataStructureSelectorDemo />
|
||||
|
||||
**快速决策流程:**
|
||||
|
||||
| 你的需求 | 推荐结构 | 原因 |
|
||||
|---------|---------|------|
|
||||
| 按位置快速访问 | 数组 | O(1) 随机访问 |
|
||||
| 频繁在中间插入删除 | 链表 | O(1) 插入删除,不用移动元素 |
|
||||
| 后进先出(撤销、递归) | 栈 | LIFO 语义天然匹配 |
|
||||
| 先进先出(任务队列) | 队列 | FIFO 语义天然匹配 |
|
||||
| 按键快速查找 | 哈希表 | O(1) 平均查找 |
|
||||
| 有序数据 + 快速查找 | 二叉搜索树 | O(log n) 查找且保持有序 |
|
||||
| 复杂多对多关系 | 图 | 能表达任意节点间的连接 |
|
||||
|
||||
::: tip 实际开发中的经验法则
|
||||
- **80% 的场景**用数组和哈希表就够了
|
||||
- **需要有序**时考虑树
|
||||
- **关系复杂**时考虑图
|
||||
- **不确定?** 先用最简单的,遇到性能问题再换。过早优化是万恶之源
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
> 数据结构是程序的骨架。**数组**像一排编号储物柜,按位置取东西最快;**链表**像寻宝线索链,插入删除最灵活;**哈希表**像图书馆索引,按名字找东西最快;**树**像家族族谱,表达层级关系且保持有序;**图**像地铁线路图,表达任意复杂的网状关系。没有最好的数据结构,只有最合适的——关键是理解每种结构的优势和代价,根据实际需求做出权衡。
|
||||
|
||||
---
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- **数据结构实现**:自己动手实现各种数据结构,加深理解
|
||||
- **高级数据结构**:学习跳表、布隆过滤器、并查集等
|
||||
- **数据库索引**:了解 B+ 树在数据库中的应用
|
||||
- **缓存设计**:学习 LRU 缓存如何结合哈希表和链表
|
||||
| 主题 | 推荐资源 |
|
||||
|------|---------|
|
||||
| 数据结构可视化 | [VisuAlgo](https://visualgo.net/) - 动画演示各种数据结构和算法 |
|
||||
| 算法与数据结构 | 《算法图解》- Aditya Bhargava,图文并茂适合入门 |
|
||||
| 深入理解 | 《数据结构与算法分析》- Mark Allen Weiss |
|
||||
| 刷题练习 | [LeetCode](https://leetcode.cn/) - 按数据结构分类练习 |
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
现在你已经掌握了数据结构的核心知识。接下来可以继续学习:
|
||||
|
||||
- **[算法思维](./algorithm-thinking.md)**:学会用排序、搜索、递归、动态规划等算法思维解决问题
|
||||
- **[编程语言](./programming-languages.md)**:了解不同编程语言如何实现这些数据结构
|
||||
|
||||
@@ -52,47 +52,7 @@ CPU 接收到复位信号后,把内部所有寄存器和缓存清零,从一
|
||||
|
||||
## 2. BIOS/UEFI:硬件的自检
|
||||
|
||||
### 2.1 什么是 BIOS/UEFI?
|
||||
|
||||
**BIOS(Basic Input/Output System)** 是电脑启动后第一个运行的程序,存储在主板的一个**只读芯片**中。
|
||||
|
||||
**UEFI(Unified Extensible Firmware Interface)** 是 BIOS 的升级版,更安全、更现代。现在的电脑大多使用 UEFI。
|
||||
|
||||
### 2.2 BIOS/UEFI 做了什么?
|
||||
|
||||
1. **硬件自检(POST)**:检查内存、显卡、键盘等部件是否正常
|
||||
2. **初始化硬件**:设置硬件工作模式
|
||||
3. **启动顺序**:按照设定顺序,尝试从硬盘/U 盘/网络启动
|
||||
|
||||
```
|
||||
BIOS/UEFI 工作流程:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 1. 硬件自检 (POST) │
|
||||
│ - 检查内存是否正常 │
|
||||
│ - 检查显卡是否正常 │
|
||||
│ - 检查键盘/鼠标是否正常 │
|
||||
├─────────────────────────────────────┤
|
||||
│ 2. 初始化硬件 │
|
||||
│ - 设置硬件工作模式 │
|
||||
│ - 配置中断向量表 │
|
||||
├─────────────────────────────────────┤
|
||||
│ 3. 寻找启动设备 │
|
||||
│ - 按启动顺序查找可启动设备 │
|
||||
│ - 读取启动扇区 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
如果发现问题,主板会发出**蜂鸣声**(不同次数代表不同错误)。
|
||||
|
||||
### 2.3 启动顺序
|
||||
|
||||
BIOS/UEFI 会按照设定的**启动顺序**查找启动设备:
|
||||
|
||||
1. 硬盘(最常见)
|
||||
2. U 盘/光盘(重装系统时用)
|
||||
3. 网络( PXE 启动,企业批量部署用)
|
||||
|
||||
找到第一个可启动设备后,读取它的**启动扇区(Boot Sector)**,把控制权交给操作系统。
|
||||
<BiosUefiInteractiveDemo />
|
||||
|
||||
---
|
||||
|
||||
@@ -104,94 +64,7 @@ BIOS/UEFI 会按照设定的**启动顺序**查找启动设备:
|
||||
|
||||
## 3. 操作系统启动:从内核到桌面
|
||||
|
||||
### 3.1 什么是操作系统?
|
||||
|
||||
**操作系统(Operating System,简称 OS)** 是管理计算机硬件和软件资源的程序集合。它就像一个"大管家",帮我们管理内存、CPU、文件等资源,让我们不需要直接和硬件打交道。
|
||||
|
||||
常见的操作系统:
|
||||
|
||||
| 操作系统 | 特点 | 典型设备 |
|
||||
|---------|------|---------|
|
||||
| **Windows** | 生态丰富,兼容性好 | 桌面电脑、笔记本 |
|
||||
| **macOS** | 苹果生态,流畅稳定 | Mac 电脑 |
|
||||
| **Linux** | 开源免费,服务器首选 | 服务器、嵌入式设备 |
|
||||
| **Android** | 移动端 Linux | 手机、平板 |
|
||||
| **iOS** | 苹果移动端 | iPhone、iPad |
|
||||
|
||||
### 3.2 操作系统的启动过程
|
||||
|
||||
当你从硬盘启动时,操作系统的启动过程如下:
|
||||
|
||||
<BootProcessDemo />
|
||||
|
||||
#### 第一步:引导程序(Bootloader)
|
||||
|
||||
硬盘的第一个扇区存放着**引导程序(Bootloader)**,它的任务是把操作系统内核加载到内存中。
|
||||
|
||||
- **Windows**:Bootloader 叫 `bootmgr`
|
||||
- **Linux**:常见的引导程序有 `GRUB`、`rEFInd` 等
|
||||
|
||||
```
|
||||
引导程序工作流程:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 1. 读取硬盘分区表 │
|
||||
│ 2. 找到操作系统分区 │
|
||||
│ 3. 加载操作系统内核到内存 │
|
||||
│ 4. 跳转到内核入口点 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 第二步:内核加载(Kernel)
|
||||
|
||||
操作系统**内核(Kernel)** 是操作系统的核心,负责管理内存、CPU、进程等核心功能。
|
||||
|
||||
```
|
||||
内核的主要功能:
|
||||
┌─────────────────────────────────────┐
|
||||
│ • 进程管理 - 创建/调度进程 │
|
||||
│ • 内存管理 - 分配/回收内存 │
|
||||
│ • 文件系统 - 管理文件存储 │
|
||||
│ • 设备驱动 - 控制硬件设备 │
|
||||
│ • 网络通信 - 处理网络协议 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 第三步:系统服务启动
|
||||
|
||||
内核加载后,会启动各种**系统服务**:
|
||||
|
||||
- **Windows 服务**:更新服务、安全中心、打印机服务
|
||||
- **Linux 服务**:SSH 服务、网络服务、图形界面(GNOME、KDE)
|
||||
|
||||
```
|
||||
Windows 启动过程:
|
||||
BIOS → MBR → bootmgr → winload.exe → ntoskrnl.exe → 系统服务 → 桌面
|
||||
|
||||
Linux 启动过程:
|
||||
BIOS → GRUB → vmlinuz (内核) → systemd → 系统服务 → 桌面环境
|
||||
```
|
||||
|
||||
#### 第四步:显示桌面
|
||||
|
||||
最后,操作系统启动**图形界面(GUI)**,显示桌面:
|
||||
|
||||
- **Windows**:explorer.exe(资源管理器)显示桌面
|
||||
- **Linux**:GNOME、KDE、XFCE 等桌面环境
|
||||
- **macOS**:Finder 显示桌面
|
||||
|
||||
```
|
||||
桌面出现的过程:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 1. 显卡驱动加载 │
|
||||
│ 2. 显示服务器启动 │
|
||||
│ (Windows: Desktop Window Manager)│
|
||||
│ (Linux: X Server / Wayland) │
|
||||
│ 3. 桌面环境启动 │
|
||||
│ 4. 显示桌面背景和图标 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
<DesktopDemo />
|
||||
<OSBootInteractiveDemo />
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ SELECT name FROM users WHERE active = true
|
||||
- **JavaScript(弱类型)**:`"11"` — 悄悄帮你转了
|
||||
- **Python(强类型)**:`TypeError` — 让你自己想清楚
|
||||
|
||||
想深入了解类型系统?→ [类型系统与编译原理入门](./type-systems-compilers)
|
||||
想深入了解类型系统?→ [类型系统入门](./type-systems) | [编译原理入门](./compilers)
|
||||
|
||||
---
|
||||
|
||||
@@ -147,6 +147,7 @@ SELECT name FROM users WHERE active = true
|
||||
:::
|
||||
|
||||
**下一步学习**:
|
||||
- [类型系统与编译原理入门](./type-systems-compilers) - 深入理解类型系统和编译过程
|
||||
- [编译原理入门](./compilers) - 深入理解编译过程和代码优化
|
||||
- [类型系统入门](./type-systems) - 深入理解类型系统和类型安全
|
||||
- [数据结构](./data-structures) - 理解数据的组织方式
|
||||
- [算法思维入门](./algorithm-thinking) - 学习解决问题的方法
|
||||
|
||||
Reference in New Issue
Block a user