核心思想:为什么学习数据结构与算法?
之前,首先要明白学习的目的:

(图片来源网络,侵删)
- 编写高效的代码:同样的功能,不同的数据结构和算法,其运行时间可能相差几个数量级,算法分析能帮你评估代码的性能。
- 解决复杂问题:很多现实世界的问题(如路径规划、资源调度、数据检索)都需要通过精巧的数据结构来建模和解决。
- 通过技术面试:数据结构与算法是几乎所有科技公司面试的必考内容。
第一部分:算法分析
这是学习一切的基础,教你如何科学地衡量算法的好坏。
1 什么是算法分析?
我们主要关注算法的时间复杂度 和空间复杂度,即算法运行所需的时间和内存空间与输入规模 n 的关系。
2 大O表示法
这是描述算法复杂度的核心工具,它描述的是算法增长率的上界。
- O(1) - 常数时间:操作时间与输入规模无关。
- 例子:数组通过索引访问元素
arr[5]。
- 例子:数组通过索引访问元素
- O(log n) - 对数时间:每次操作都将问题规模减半。
- 例子:二分查找。
- O(n) - 线性时间:操作时间与输入规模成正比。
- 例子:遍历一个数组或链表。
- O(n log n) - 线性对数时间:非常高效的排序算法复杂度。
- 例子:归并排序、快速排序。
- O(n²) - 平方时间:通常由嵌套循环导致。
- 例子:简单的排序算法(冒泡排序、选择排序)。
- O(2ⁿ) - 指数时间:通常由递归的、不优化的解法导致。
- 例子:斐波那契数列的朴素递归解法。
关键点:我们关心的是当 n 非常大时,算法的增长趋势,低阶项和常数系数在大O表示法中被忽略。

(图片来源网络,侵删)
第二部分:基础数据结构
数据结构是组织和存储数据的方式。
1 数组
- 特点:在内存中是连续存储的,通过索引可以 O(1) 时间访问任意元素。
- 优点:随机访问快。
- 缺点:
- 大小固定(静态数组)或动态扩容成本高(动态数组,如 C++
vector)。 - 插入和删除元素(尤其是在中间)需要移动大量元素,时间复杂度为 O(n)。
- 大小固定(静态数组)或动态扩容成本高(动态数组,如 C++
- C 语言实现:原生数组
int arr[10];,动态数组需要手动管理内存 (malloc,realloc,free)。
2 链表
- 特点:由一系列节点组成,每个节点包含数据和指向下一个节点的指针,内存中不连续。
- 优点:
- 插入和删除元素快(在已知位置的情况下,时间复杂度为 O(1))。
- 大小动态可变。
- 缺点:
随机访问慢,必须从头开始遍历,时间复杂度为 O(n)。
- C 语言实现:需要定义
struct来表示节点。struct Node { int data; struct Node* next; }; - 常见类型:单链表、双链表、循环链表。
3 栈
- 特点:后进先出 的数据结构。
- 操作:
push(入栈),pop(出栈),peek(查看栈顶)。 - 应用:函数调用栈、表达式求值、括号匹配。
- C 语言实现:可以用数组或链表实现,数组实现更简单,需要记录栈顶指针。
4 队列
- 特点:先进先出 的数据结构。
- 操作:
enqueue(入队),dequeue(出队)。 - 应用:任务调度、广度优先搜索。
- C 语言实现:用链表实现非常自然,数组实现时,为了处理“假溢出”(队头有空闲空间但无法插入),通常采用循环队列。
第三部分:树与二叉树
树是用于处理层次关系数据的非线性结构。
1 树的基本概念
- 节点、根、边、度、叶子节点、高度/深度。
2 二叉树
- 特点:每个节点最多有两个子节点(左子节点和右子节点)。
- 五种基本形态:空树、只有一个节点的树、只有左子树的树、只有右子树的树、左右子树都存在的树。
3 二叉树的遍历
这是树的灵魂操作,有四种经典方式:

(图片来源网络,侵删)
- 前序遍历:根 -> 左 -> 右
- 中序遍历:左 -> 根 -> 右 (对二叉搜索树来说,结果是排序的)
- 后序遍历:左 -> 右 -> 根
- 层序遍历:从上到下,从左到右,逐层访问。(通常用队列实现)
4 二叉搜索树
- 特点:一种特殊的二叉树,满足:
- 任意节点的左子树所有节点的值都小于该节点。
- 任意节点的右子树所有节点的值都大于该节点。
- 优点:提供了高效的查找、插入、删除操作(平均 O(log n))。
- 缺点:在极端情况下(如插入有序数据),会退化成链表,操作变为 O(n)。
- C 语言实现:同样用
struct定义节点,包含数据、左指针、右指针。
5 堆
- 特点:一种特殊的完全二叉树,分为最大堆和最小堆。
- 最大堆:父节点的值大于或等于其子节点的值。
- 最小堆:父节点的值小于或等于其子节点的值。
- 应用:优先队列、堆排序。
- C 语言实现:通常用数组表示,对于一个节点
i,其左子节点在2i+1,右子节点在2i+2,父节点在(i-1)/2。
第四部分:高级数据结构
1 图
- 特点:由顶点 和边 组成的非线性结构,用于表示多对多的关系。
- 存储方式:
- 邻接矩阵:用一个二维数组表示,直观,但空间复杂度高 O(V²)。
- 邻接表:用一个数组 + 链表(或动态数组)表示,节省空间,是更常用的方式。
- 遍历算法:
- 广度优先搜索:使用队列,逐层遍历。
- 深度优先搜索:使用栈(或递归),一条路走到黑再回头。
2 哈希表
- 特点:通过哈希函数 将键映射到数组的一个索引上,实现近乎 O(1) 的平均查找、插入和删除。
- 核心问题:
- 哈希冲突:两个不同的键通过哈希函数得到了相同的索引。
- 解决冲突:
- 链地址法:将冲突的元素以链表形式存储在同一个哈希桶中。
- 开放地址法:发生冲突时,按照某种规则(如线性探测)寻找下一个空槽。
- 应用:数据库索引、缓存、字典等。
第五部分:算法
1 排序算法
- 简单排序 (O(n²)):
- 冒泡排序:反复交换相邻元素。
- 选择排序:每次找到最小(或最大)的元素放到已排序序列的末尾。
- 插入排序:将每个元素插入到已排序序列的合适位置。
- 高级排序 (O(n log n)):
- 归并排序:分治法,将数组分成两半,分别排序,然后合并,稳定排序。
- 快速排序:分治法,选择一个基准,将数组分为两部分,一部分小于基准,一部分大于基准,然后递归,平均最快,但最坏情况是 O(n²)。
- 堆排序:利用堆数据结构,建堆,然后反复取出堆顶元素。
2 查找算法
- 顺序查找:在无序数据中从头到尾查找,O(n)。
- 二分查找:在有序数据中,每次比较中间元素,将查找范围减半,O(log n)。
3 经典算法思想
- 分治法:将大问题分解成小问题,解决小问题,再合并结果。(如:归并排序、快速排序)
- 贪心算法:每一步都做出当前看起来最优的选择,期望最终得到全局最优解。(如:哈夫曼编码、Dijkstra最短路径)
- 动态规划:通过存储子问题的解来避免重复计算,从而解决复杂问题。(如:斐波那契数列、背包问题)
- 回溯法:像走迷宫一样,在一个路径上探索,如果走不通就“回溯”到上一步尝试其他选择。(如:八皇后问题、全排列)
学习建议与实践
- 理论与实践结合:看书上的伪代码后,一定要亲手用 C 语言实现一遍,自己实现
struct、malloc、free的过程是加深理解的关键。 - 画图辅助理解:对于链表、树、图等结构,画图是最好的学习方法,模拟插入、删除、遍历的过程。
- 分析复杂度:每写完一个算法,都强迫自己分析其时间和空间复杂度,并思考最好、最坏、平均情况。
- 刷题:在 LeetCode、牛客网等平台上刷题,是检验学习成果和提升能力的最佳方式,从“简单”题开始,逐步挑战“中等”和“困难”题。
- 阅读优秀代码:阅读别人高质量的实现,学习其编程技巧和思路。
C 语言相关的注意事项
- 内存管理:与 C++/Java 不同,C 语言需要手动管理内存,在实现动态数据结构(如链表、动态数组、哈希表)时,
malloc/calloc用于分配内存,realloc用于调整内存大小,free用于释放内存。内存泄漏是常见的错误。 - 指针:指针是 C 语言的精髓,也是实现数据结构的难点,务必熟练掌握指针的运算、指针作为函数参数和返回值。
- 结构体:用
struct来定义数据结构的节点。
希望这份详细的梳理能帮助你系统地学习《数据结构与算法分析(C语言))》,祝你学习顺利!
