核心思想与重要性
之前,首先要理解这本书的核心思想:

(图片来源网络,侵删)
- 抽象:隐藏实现的复杂性,只暴露必要的接口,我们使用栈时,只需要知道
push和pop操作,而不需要关心它是用数组还是链表实现的。 - 封装:将数据(结构)和操作数据的函数(算法)捆绑在一起,形成一个独立的单元(如 C 语言中的结构体和函数)。
- 性能分析:算法的好坏不是看它写得多漂亮,而是看它执行得有多快,占多少内存,这主要通过时间复杂度和空间复杂度来衡量。
- 权衡:没有完美的数据结构或算法,选择哪个取决于具体的应用场景,数组在随机访问时快,但在插入和删除时慢;链表则相反。
核心内容概览(按照书籍典型章节结构)
这本书通常分为几个主要部分,循序渐进地介绍各种数据结构和算法。
第一部分:基础
- 算法分析
- 核心概念:大O表示法、Ω表示法、Θ表示法,这是衡量算法效率的“通用语言”。
- 重点:理解如何计算一个算法的基本操作次数(如循环、递归),并将其简化为大O表示法。
- 示例:
O(1):常数时间,如访问数组元素。O(log N):对数时间,如二分查找。O(N):线性时间,如遍历数组。O(N log N):线性对数时间,如高效排序算法(归并、堆排序)。O(N²):平方时间,如简单的排序算法(冒泡、选择)。
第二部分:基本数据结构
-
线性表
- 数组:
- 特点:连续的内存空间,大小固定,支持随机访问(
O(1))。 - 缺点:大小固定,插入和删除元素需要移动大量元素(
O(N))。
- 特点:连续的内存空间,大小固定,支持随机访问(
- 链表:
- 特点:不连续的内存空间,通过指针连接,大小动态可变。
- 类型:单链表、双链表、循环链表。
- 优点:插入和删除元素快(
O(1),已知位置)。 - 缺点:不支持随机访问,访问元素慢(
O(N))。
- 实现要点:在 C 语言中,通常使用
struct定义节点,用指针连接,需要熟练掌握指针操作。
- 数组:
-
栈
- 特点:后进先出。
- 实现:可以用数组或链表作为底层结构。
- 应用:函数调用栈、表达式求值(中缀转后缀)、括号匹配、浏览器历史记录。
-
队列
(图片来源网络,侵删)- 特点:先进先出。
- 实现:通常用链表实现,如果用数组实现,需要处理“循环队列”以避免假溢出。
- 应用:任务调度、广度优先搜索、消息缓冲区。
第三部分:高级数据结构
-
树
- 二叉树:每个节点最多有两个子节点(左、右)。
- 遍历:前序、中序、后序、层序,理解递归和迭代两种实现方式。
- 应用:表达式树、文件系统。
- 二叉搜索树:
- 特点:左子树所有节点值 < 根节点值 < 右子树所有节点值,这使得查找、插入、删除的平均时间复杂度为
O(log N)。 - 问题:可能退化成链表(最坏情况
O(N)),例如插入有序数据。
- 特点:左子树所有节点值 < 根节点值 < 右子树所有节点值,这使得查找、插入、删除的平均时间复杂度为
- 平衡二叉搜索树:
- 目的:解决 BST 可能退化的问题,保证树的高度始终为
O(log N)。 - 代表:AVL树(通过旋转保持平衡)、红黑树(通过颜色和旋转保持平衡,应用更广,如 C++ STL 的
map、Java 的TreeMap)。
- 目的:解决 BST 可能退化的问题,保证树的高度始终为
- 堆:
- 特点:一种特殊的完全二叉树,分为最大堆(父节点 >= 子节点)和最小堆。
- 实现:通常用数组实现,利用下标关系计算父子节点。
- 应用:优先队列、堆排序(
O(N log N))、Top K 问题。
- 二叉树:每个节点最多有两个子节点(左、右)。
-
散列表
- 核心思想:通过一个“散列函数”将关键字映射到数组的一个索引上,实现近乎
O(1)的查找、插入和删除。 - 关键问题:
- 散列函数:设计一个能将关键字均匀分布到数组中的函数。
- 冲突解决:当两个关键字映射到同一位置时怎么办?
- 分离链接法:每个数组元素是一个链表,冲突的元素挂在链表上。
- 开放地址法:通过探测序列寻找下一个空槽,如线性探测、二次探测。
- 应用:字典、缓存、数据库索引。
- 核心思想:通过一个“散列函数”将关键字映射到数组的一个索引上,实现近乎
-
图
- 表示方法:
- 邻接矩阵:用一个二维数组表示,
matrix[i][j] = 1表示有边,适合稠密图。 - 邻接表:用一个数组,每个元素是一个链表,存储该顶点的所有邻接点,适合稀疏图。
- 邻接矩阵:用一个二维数组表示,
- 遍历算法:
- 深度优先搜索:类似树的先序遍历,可以用递归或栈实现,常用于寻找路径、检测环。
- 广度优先搜索:类似树的层序遍历,用队列实现,常用于寻找无权图的最短路径。
- 应用:社交网络、地图导航、网络拓扑。
- 表示方法:
第四部分:算法
-
排序
(图片来源网络,侵删)- 简单排序:冒泡、选择、插入,平均/最坏
O(N²),适合小规模数据。 - 高效排序:
- 归并排序:
O(N log N),稳定排序,但需要额外空间。 - 堆排序:
O(N log N),原地排序,不稳定。 - 快速排序:平均
O(N log N),实践中最快,但最坏O(N²),原地排序,不稳定。
- 归并排序:
- 线性时间排序:
- 桶排序、计数排序、基数排序,当数据满足特定条件时,可以达到
O(N)。
- 桶排序、计数排序、基数排序,当数据满足特定条件时,可以达到
- 简单排序:冒泡、选择、插入,平均/最坏
-
查找
- 顺序查找:
O(N)。 - 二分查找:
O(log N),但要求数据有序。 - 基于树的查找:BST、AVL、红黑树的查找操作。
- 顺序查找:
-
高级算法
- 贪心算法:每一步都选择当前最优解,期望得到全局最优解,如最短路径问题、最小生成树。
- 动态规划:将问题分解为子问题,存储子问题的解以避免重复计算,如背包问题、最长公共子序列。
- 图算法:
- 最短路径:Dijkstra 算法(非负权)、Bellman-Ford 算法(可处理负权)。
- 最小生成树:Prim 算法、Kruskal 算法。
C语言实现要点
使用 C 语言实现这些数据结构和算法,有几个关键点需要注意:
-
指针是灵魂:
- 动态内存分配(
malloc,calloc,realloc,free)是构建动态数据结构(如链表、树、图)的基础。 - 必须熟练掌握指针的解引用、指针的指针(用于修改头指针等)以及指针的算术运算。
- 动态内存分配(
-
struct的使用:- 用
struct定义节点,链表节点:typedef struct Node { int data; struct Node* next; } Node;
- 用
-
内存管理:
- 谁分配,谁释放,在创建节点时使用
malloc,在不再需要时必须使用free,否则会导致内存泄漏。 - 这是一个常见的错误,也是面试官经常考察的点。
- 谁分配,谁释放,在创建节点时使用
-
函数指针:
- 在实现一些通用算法时(如 qsort 函数),函数指针非常有用。
qsort需要一个比较函数作为参数。
- 在实现一些通用算法时(如 qsort 函数),函数指针非常有用。
-
抽象与封装:
- 虽然没有 C++ 的
class,但可以通过将操作数据结构的函数集中在一个.c文件中,并在.h文件中声明接口,来实现类似封装的效果。
- 虽然没有 C++ 的
学习路径与实践建议
- 打好基础:确保你对 C 语言的指针、内存管理、
struct和函数有扎实的理解。 - 从简单开始:不要一上来就挑战红黑树,先从数组、链表、栈、队列开始,亲手实现它们的基本操作(增删改查)。
- 边学边敲:“纸上得来终觉浅,绝知此事要躬行”,对于每一个数据结构和算法,都亲自用 C 语言实现一遍,这是最重要的一步。
- 分析复杂度:在实现每个算法后,都要分析其时间和空间复杂度,并思考为什么是这个复杂度。
- 画图辅助理解:对于树、图等结构,画图是理解其操作(如遍历、插入、删除)的最好方式。
- 阅读优秀代码:在 GitHub 上搜索相关数据结构的 C 语言实现,对比自己的代码,学习别人的优点。
- 做 LeetCode 题目:将学到的数据结构和算法应用到实际问题中。
- 数组/链表:两数之和、反转链表。
- 栈/队列:有效的括号、用队列实现栈。
- 树:二叉树的前序遍历、验证二叉搜索树。
- 图:克隆图、课程表(拓扑排序)。
- 排序/查找:合并两个有序数组、寻找峰值。
推荐资源
- 核心教材:
《数据结构与算法分析:C语言描述》 - Mark Allen Weiss
- 在线课程:
- Coursera / edX 上的 "Data Structures and Algorithms" 课程(通常使用 Java,但思想是通用的)。
- B站、YouTube 上有大量关于 C 语言数据结构的视频教程。
- 练习平台:
- LeetCode (力扣)
- HackerRank
- 牛客网
- 参考实现:
GitHub: 搜索 "data structures in C",可以找到很多开源项目。
祝你学习顺利!掌握数据结构和算法是成为一名优秀程序员的必经之路,虽然过程艰辛,但回报巨大。
