引言:为什么学习数据结构与算法?
之前,我们必须明白其重要性:

(图片来源网络,侵删)
- 写出高效的代码:同样的功能,用不同的数据结构和算法实现,其运行效率可能相差几个数量级,这是衡量程序员水平的关键指标。
- 通过技术面试:几乎所有知名科技公司的技术面试都会考察数据结构和算法,这是筛选候选人的基本门槛。
- 理解复杂系统:操作系统、数据库、编译器等底层系统,其核心都是精妙的数据结构和算法。
- 培养计算思维:学习如何将现实世界的问题抽象化,并设计出最优的解决方案。
第一部分:基础准备
在学习具体的数据结构之前,你需要具备以下C语言基础:
- 指针:这是C语言的精髓,也是实现数据结构的关键,你必须熟练掌握指针的定义、使用、指针与数组、指针与函数、指针的指针等。
- 内存管理:理解栈、堆的区别,熟练使用
malloc,calloc,realloc,free来动态分配和释放内存。 - 结构体:用于将不同类型的数据组合成一个整体,是构建复杂数据结构(如链表节点、树节点)的基础。
- 函数指针:在实现一些高级算法(如回调函数、排序算法的比较函数)时会用到。
第二部分:核心数据结构
我们将按照从简单到复杂的顺序来学习数据结构。
线性表
线性表是最基本、最常用的数据结构,其元素之间存在一对一的线性关系。
a. 数组
- 定义:在内存中一块连续的空间,存储相同类型的数据。
- 特点:
- 优点:随机访问速度快(根据下标
O(1)时间复杂度)。 - 缺点:大小固定,插入和删除元素效率低(需要移动大量元素,平均
O(n)时间复杂度)。
- 优点:随机访问速度快(根据下标
- C语言实现:C语言原生支持数组,动态数组需要通过
malloc等函数在堆上分配内存,并手动维护其大小和容量。 - 核心操作:
- 遍历
- 按索引访问/修改
- 插入(在头部、中间、尾部)
- 删除(在头部、中间、尾部)
- 查找(线性查找、二分查找 - 要求数组有序)
b. 链表
- 定义:由一系列节点组成,每个节点包含数据域和指向下一个节点的指针,内存空间不连续。
- 特点:
- 优点:插入和删除效率高(只需修改指针,平均
O(1)时间复杂度,前提是已知操作位置)。 - 缺点:随机访问速度慢(必须从头节点开始遍历,平均
O(n)时间复杂度)。
- 优点:插入和删除效率高(只需修改指针,平均
- C语言实现:使用
struct定义节点,用指针将它们串联起来。 - 核心操作:
- 遍历
- 头部/尾部插入
- 头部/尾部删除
- 在指定节点后插入/删除
- 查找
- 常见变种:
- 单链表:每个节点只有一个
next指针。 - 双链表:每个节点有
prev和next两个指针,可以双向遍历。 - 循环链表:尾节点的
next指针指向头节点,形成一个环。
- 单链表:每个节点只有一个
栈与队列
它们是两种特殊的线性表,对操作的位置有严格限制。

(图片来源网络,侵删)
a. 栈
- 定义:后进先出 的数据结构。
- 特点:只允许在一端(栈顶)进行插入(入栈
push)和删除(出栈pop)操作。 - C语言实现:
- 基于数组:用一个数组和一个
top指针模拟。 - 基于链表:用链表的头节点作为栈顶。
- 基于数组:用一个数组和一个
- 应用:函数调用栈、表达式求值、括号匹配、浏览器的前进/后退。
b. 队列
- 定义:先进先出 的数据结构。
- 特点:在一端(队尾)进行插入(入队
enqueue),在另一端(队头)进行删除(出队dequeue)。 - C语言实现:
- 基于数组:需要一个循环数组来避免假溢出。
- 基于链表:用链表的头节点作为队头,尾节点作为队尾。
- 应用:任务调度、消息缓冲、广度优先搜索。
树
树是层次化的非线性数据结构,由节点和边组成。
a. 二叉树
- 定义:每个节点最多有两个子节点(左子节点和右子节点)的树。
- C语言实现:用
struct定义节点,包含数据、左孩子指针、右孩子指针。 - 核心操作:
- 遍历:这是树操作的核心。
- 前序遍历:根 -> 左 -> 右
- 中序遍历:左 -> 根 -> 右 (对二叉搜索树来说,结果是升序的)
- 后序遍历:左 -> 右 -> 根
- 层序遍历:按从上到下、从左到右的顺序访问(通常使用队列实现)。
- 查找、插入、删除
- 遍历:这是树操作的核心。
b. 二叉搜索树
- 定义:一种特殊的二叉树,满足:
- 任意节点的左子树所有节点的值都小于该节点的值。
- 任意节点的右子树所有节点的值都大于该节点的值。
- 左右子树也都是二叉搜索树。
- 特点:查找、插入、删除的平均时间复杂度为
O(log n),最坏情况(树退化为链表)为O(n)。 - C语言实现:在二叉树的基础上,插入和删除时维护其性质。
- 变种:为了解决最坏情况,出现了平衡二叉搜索树,如 AVL树 和 红黑树,它们通过旋转操作来保持树的平衡,确保操作的时间复杂度稳定在
O(log n)。
c. 堆
- 定义:一种特殊的完全二叉树。
- 最大堆:父节点的值总是大于或等于其子节点的值。
- 最小堆:父节点的值总是小于或等于其子节点的值。
- 特点:根节点是堆中的最大值(最大堆)或最小值(最小堆)。
- C语言实现:通常使用数组来表示,对于一个节点
i,其左孩子在2*i+1,右孩子在2*i+2,父节点在(i-1)/2。 - 核心操作:
- 建堆
- 插入:将元素放在数组末尾,然后向上调整。
- 删除堆顶:将末尾元素放到堆顶,然后向下调整。
- 应用:优先队列、堆排序。
哈希表
- 定义:通过哈希函数将键映射到数组中的一个位置,以实现快速的数据访问。
- 核心思想:
index = hash(key) - 特点:理想情况下,查找、插入、删除的时间复杂度可以达到
O(1)。 - C语言实现:
- 哈希函数:设计一个能将键均匀分布到数组索引的函数。
- 冲突解决:当两个不同的键映射到同一个位置时,需要解决冲突。
- 链地址法:每个数组槽(桶)对应一个链表,所有哈希到该槽的元素都存入链表中。
- 开放地址法:当发生冲突时,按照某种规则(如线性探测、二次探测)在数组中寻找下一个空槽。
- 应用:数据库索引、缓存(如LRU缓存)、字典。
图
- 定义:由顶点 和边 组成的非线性数据结构,用于表示多对多的关系。
- C语言实现:
- 邻接矩阵:用一个二维数组表示。
matrix[i][j] = 1表示顶点i和j之间有边。- 优点:判断两个顶点是否相邻快(
O(1))。 - 缺点:空间复杂度高(
O(V^2)),稀疏图浪费空间。
- 优点:判断两个顶点是否相邻快(
- 邻接表:用一个数组(或链表)存储所有顶点,每个顶点对应一个链表,存储其所有邻接点。
- 优点:空间复杂度低(
O(V+E)),适合稀疏图。 - 缺点:判断两个顶点是否相邻慢(
O(V))。
- 优点:空间复杂度低(
- 邻接矩阵:用一个二维数组表示。
- 核心算法:
- 遍历:
- 深度优先搜索:使用栈(或递归)实现。
- 广度优先搜索:使用队列实现。
- 最短路径:
- Dijkstra算法:求解单源最短路径,要求图中边权非负。
- Floyd-Warshall算法:求解任意两点间的最短路径。
- 最小生成树:
- Prim算法:从一个顶点开始,逐步扩展。
- Kruskal算法:按边的权重从小到大选择,不形成环。
- 遍历:
第三部分:算法分析
学习数据结构的同时,必须学会分析算法的效率。
复杂度分析
- 大O表示法:描述算法运行时间或空间增长的上限,关注算法的渐进行为(当输入规模
n趋近于无穷大时)。O(1)- 常数时间O(log n)- 对数时间O(n)- 线性时间O(n log n)- 线性对数时间O(n^2)- 平方时间O(2^n)- 指数时间
- 分析步骤:
- 找到基本操作(如循环体中的代码)。
- 计算基本操作执行的次数
T(n)。 - 用大O表示法简化
T(n),去掉常数项和低阶项。
排序算法
排序是算法学习的经典内容,也是检验各种数据结构性能的试金石。
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 备注 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 简单,但效率低 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 每次找最小/大值 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 对小规模或基本有序数据高效 |
| 希尔排序 | O(n log n)~O(n²) | O(n²) | O(1) | 不稳定 | 插入排序的改进版 |
| 归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 | 分治法,稳定高效,但需要额外空间 |
| 快速排序 | O(n log n) | O(n²) | O(log n) (栈空间) | 不稳定 | 平均最快,实际应用首选 |
| 堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 | 利用堆数据结构,空间复杂度低 |
第四部分:学习路径与建议
- 打好基础:确保你对C语言的指针和内存管理了如指掌。
- 循序渐进:严格按照 线性表 -> 栈/队列 -> 树 -> 图 的顺序学习,后面的结构往往建立在前面之上。
- 理论与实践结合:
- 理论:理解每个数据结构的定义、特点、优缺点和适用场景。
- 实践:亲手用C语言实现每一个数据结构和核心算法,不要只看不练,敲代码是最好的学习方式。
- 多做算法题:
- 平台:LeetCode、牛客网、HackerRank等。
- 方法:从“简单”题开始,用学到的数据结构和算法去解决,尝试用不同的方法解决同一个问题,并分析其优劣。
- 阅读经典书籍:
- 《数据结构与算法分析:C语言描述》:本书是经典教材,内容全面,讲解清晰。
- 《C程序设计语言》:巩固C语言基础。
- 《算法导论》:更深入、更学术,适合进阶。
《数据结构与算法分析(C语言描述)》这门课程的核心在于“选择”和“权衡”,你需要根据具体问题的需求,选择最合适的数据结构,并设计出高效的算法,在时间效率、空间效率和代码复杂度之间做出权衡。

(图片来源网络,侵删)
这是一个需要耐心和毅力的过程,但一旦掌握,你将拥有解决复杂问题的强大能力,这会让你在编程的道路上走得更远、更稳,祝你学习顺利!
