在数据结构中,复杂度(通常指时间复杂度和空间复杂度)是衡量算法效率的核心指标,用于分析算法在不同输入规模下的资源消耗情况。
1. 时间复杂度(Time Complexity)
衡量算法执行所需的操作次数(或时间)随输入规模 ( n ) 的增长趋势。常用 大O符号(O) 表示最坏情况下的上界。
常见时间复杂度(从低到高):
- O(1) :常数时间
- O(log n) :对数时间
- 每次操作将问题规模减半(如二分查找、平衡二叉树操作)。
- O(n) :线性时间
- O(n log n) :线性对数时间
- O(n²) :平方时间
- O(2ⁿ) :指数时间
- O(n!) :阶乘时间
示例:
- 线性搜索:O(n)(最坏需遍历整个数组)。
- 二分查找:O(log n)(每次排除一半元素)。
- 快速排序:平均 O(n log n),最坏 O(n²)(当数组已有序时)。
2. 空间复杂度(Space Complexity)
衡量算法执行过程中占用的额外存储空间随输入规模 ( n ) 的增长趋势。
常见空间复杂度:
- O(1) :常数空间
- O(n) :线性空间
- 需要存储与输入规模成正比的数据(如哈希表、递归栈)。
- O(n²) :平方空间
示例:
- 迭代实现:通常 O(1) 空间(如迭代版斐波那契数列)。
- 递归实现:可能 O(n) 空间(如递归版斐波那契数列,因递归栈深度为 ( n ))。
- 归并排序:O(n) 空间(需额外数组存储中间结果)。
3. 复杂度分析的关键点
- 最坏情况 vs 平均情况 :
- 大O符号通常表示最坏情况(如快速排序的 O(n²)),但有时也分析平均情况(如快速排序的 O(n log n))。
- 忽略常数和低阶项 :
- O(3n + 2) 简化为 O(n),O(n² + n) 简化为 O(n²)。
- 递归算法的复杂度 :
- 使用递归树或主定理(Master Theorem)分析(如二分查找的递归式 ( T(n) = 2T(n/2) + O(1) ) 解为 O(n log n))。
4. 数据结构与复杂度的关系
不同数据结构的操作复杂度差异显著,选择时需权衡:
| 操作 |
数组 |
链表 |
哈希表 |
二叉搜索树 |
堆(优先队列) |
| 访问元素 |
O(1) |
O(n) |
O(1) |
O(log n) |
O(1)(取极值) |
| 插入/删除 |
O(n) |
O(1) |
O(1) |
O(log n) |
O(log n) |
| 搜索 |
O(n) |
O(n) |
O(1) |
O(log n) |
O(n) |
5. 实际应用中的优化
- 时间换空间:如用哈希表缓存结果(空间 O(n) 换搜索 O(1))。
- 空间换时间:如动态规划预计算子问题(空间 O(n²) 换时间 O(n²))。
- 算法选择:根据问题规模选择合适算法(如小规模用插入排序 O(n²),大规模用快速排序 O(n log n))。
总结
- 时间复杂度:关注操作次数随 ( n ) 的增长趋势。
- 空间复杂度:关注额外存储空间的需求。
- 选择数据结构:根据操作频率(如频繁搜索用哈希表,频繁插入用链表)。
以下是常见数据结构及其核心操作的 时间复杂度 和 空间复杂度 的整理表格,按 线性表、串、堆栈、树、图 分类,方便直观记忆:
1. 线性表(数组 & 链表)
| 操作 |
数组(静态) |
数组(动态扩容) |
单链表 |
双向链表 |
| 访问元素 |
O(1) |
O(1) |
O(n) |
O(n) |
| 搜索元素 |
O(n) |
O(n) |
O(n) |
O(n) |
| 头部插入 |
O(n)(需移动) |
O(n)(可能扩容) |
O(1) |
O(1) |
| 尾部插入 |
O(1) |
O(1)(均摊) |
O(n) |
O(1) |
| 任意位置插入 |
O(n) |
O(n) |
O(n) |
O(n) |
| 头部删除 |
O(n)(需移动) |
O(n)(可能扩容) |
O(1) |
O(1) |
| 尾部删除 |
O(1) |
O(1)(均摊) |
O(n) |
O(1) |
| 空间复杂度 |
O(n) |
O(n)(可能扩容) |
O(n) |
O(n) |
2. 串(字符串)
| 操作 |
朴素匹配(Brute-Force) |
KMP算法 |
Boyer-Moore算法 |
| 搜索子串 |
O(n×m)(最坏) |
O(n+m) |
O(n/m)(最好) |
| 空间复杂度 |
O(1) |
O(m) |
O(m)(预处理表) |
注:
- ( n ) 为主串长度,( m ) 为子串长度。
- KMP 和 Boyer-Moore 是高效字符串匹配算法。
3. 堆栈(Stack)
| 操作 |
数组实现 |
链表实现 |
| 压栈(Push) |
O(1)(均摊) |
O(1) |
| 弹栈(Pop) |
O(1) |
O(1) |
| 查看栈顶 |
O(1) |
O(1) |
| 空间复杂度 |
O(n) |
O(n) |
4. 队列(Queue)
| 操作 |
数组实现(循环队列) |
链表实现 |
| 入队(Enqueue) |
O(1)(均摊) |
O(1) |
| 出队(Dequeue) |
O(1) |
O(1) |
| 查看队首 |
O(1) |
O(1) |
| 空间复杂度 |
O(n) |
O(n) |
5. 树(Tree)
二叉搜索树(BST)
| 操作 |
平均情况 |
最坏情况(退化为链表) |
| 搜索 |
O(log n) |
O(n) |
| 插入 |
O(log n) |
O(n) |
| 删除 |
O(log n) |
O(n) |
| 空间复杂度 |
O(n) |
O(n) |
平衡二叉搜索树(AVL/红黑树)
| 操作 |
所有情况 |
| 搜索 |
O(log n) |
| 插入 |
O(log n) |
| 删除 |
O(log n) |
| 空间复杂度 |
O(n) |
堆(优先队列)
| 操作 |
二叉堆(数组实现) |
| 插入(Insert) |
O(log n) |
| 删除极值(Extract-Min/Max) |
O(log n) |
| 查看极值 |
O(1) |
| 建堆(Heapify) |
O(n) |
| 空间复杂度 |
O(n) |
6. 图(Graph)
邻接矩阵表示
| 操作 |
时间复杂度 |
空间复杂度 |
| 访问顶点 |
O(1) |
O(n²) |
| 访问边 |
O(1) |
|
| 添加/删除顶点 |
O(n) |
|
| 添加/删除边 |
O(1) |
|
邻接表表示
| 操作 |
时间复杂度 |
空间复杂度 |
| 访问顶点 |
O(1) |
O(n + m) |
| 访问边 |
O(deg(v))(顶点 ( v ) 的度) |
|
| 添加/删除顶点 |
O(1)(链表实现) |
|
| 添加/删除边 |
O(1)(链表实现) |
|
图遍历算法
| 算法 |
时间复杂度(邻接表) |
空间复杂度 |
| DFS(深度优先) |
O(n + m) |
O(n)(递归栈) |
| BFS(广度优先) |
O(n + m) |
O(n)(队列) |
最短路径算法
| 算法 |
时间复杂度(邻接表) |
空间复杂度 |
| Dijkstra(普通队列) |
O(nm)(最坏) |
O(n) |
| Dijkstra(优先队列) |
O((n+m) log n) |
O(n) |
| Bellman-Ford |
O(nm) |
O(n) |
| Floyd-Warshall |
O(n³) |
O(n²) |
最小生成树算法
| 算法 |
时间复杂度(邻接表) |
空间复杂度 |
| Prim(普通队列) |
O(nm)(最坏) |
O(n) |
| Prim(优先队列) |
O((n+m) log n) |
O(n) |
| Kruskal |
O(m log m)(排序边) |
O(n + m) |
总结表格(精简版)
| 数据结构 |
核心操作 |
时间复杂度(平均/最坏) |
空间复杂度 |
| 数组 |
访问/修改 |
O(1) |
O(n) |
| 链表 |
插入/删除(头部) |
O(1) |
O(n) |
| 二叉搜索树 |
搜索/插入/删除 |
O(log n)/O(n) |
O(n) |
| 平衡树 |
搜索/插入/删除 |
O(log n) |
O(n) |
| 堆 |
插入/删除极值 |
O(log n) |
O(n) |
| 哈希表 |
搜索/插入/删除 |
O(1)(均摊) |
O(n) |
| 图(邻接表) |
遍历(DFS/BFS) |
O(n + m) |
O(n) |
| Dijkstra |
最短路径 |
O((n+m) log n) |
O(n) |
记忆技巧
- 线性结构(数组/链表):操作与 ( n ) 成正比(O(n) 或 O(1))。
- 树结构:平衡时为 O(log n),退化为链表时为 O(n)。
- 图算法:遍历和最短路径通常为 O(n + m),优先队列优化可降复杂度。
- 哈希表:均摊 O(1),但需考虑哈希冲突。