数据结构与算法#
本文介绍数据结构与算法的基本原理(概念、复杂度、常见数据结构的原理与适用场景、算法设计思想), 各主题的刷题模板按模块拆分在
ds/子目录中,见文末模块导航。
一、基本概念#
数据结构:计算机中组织和存储数据的方式。选择合适的数据结构,直接影响算法的效率。
算法:解决特定问题的有限、确定、有效的操作步骤。
抽象数据类型(ADT):只关心”支持哪些操作”而不关心底层实现的数据类型,如栈、队列。同一 ADT 可以有多种实现(数组或链表实现栈)。
刷题时通常先想清楚数据规模与操作要求,再选择数据结构与算法,最后落实到代码模板。
二、复杂度分析#
2.1 时间复杂度与大 O 记号#
大 O 记号描述算法运行时间随输入规模 \(n\) 增长的渐进上界:若存在正常数 \(c\) 与 \(n_0\),使得对所有 \(n \geq n_0\) 有 \(T(n) \leq c f(n)\),则记 \(T(n) = O(f(n))\)。
分析规则:
忽略常数与低阶项:\(3n^2 + 5n + 1 = O(n^2)\);
顺序结构相加,循环结构相乘:两段循环嵌套为 \(O(n \cdot m)\),先后执行为 \(O(n + m)\);
只关注最坏情况或题目要求的均摊/平均情况。
常见复杂度从低到高:
复杂度 |
典型算法 |
|---|---|
\(O(1)\) |
数组随机访问、哈希表查找、栈/队列进出 |
\(O(\log n)\) |
二分查找、平衡二叉搜索树操作 |
\(O(n)\) |
线性扫描、滑动窗口 |
\(O(n\log n)\) |
归并排序、快速排序、堆排序 |
\(O(n^2)\) |
冒泡/选择排序、朴素 DP、Floyd |
\(O(2^n)\) |
子集枚举、状压 DP |
\(O(n!)\) |
全排列枚举 |
2.2 空间复杂度#
空间复杂度衡量算法运行所需的额外存储空间(不含输入本身):
原地操作(如原地快排、堆排)辅助空间为 \(O(1)\);
归并排序需要 \(O(n)\) 辅助数组;
递归算法还要计入递归调用栈深度:如快排平均 \(O(\log n)\)、最坏 \(O(n)\)。
2.3 复杂度估算技巧#
刷题时根据 \(n\) 的规模反推可行算法:
\(n \leq 20\):可枚举子集 / 状压 DP(\(O(2^n)\));
\(n \leq 500\):\(O(n^3)\)(如 Floyd、区间 DP);
\(n \leq 5000\):\(O(n^2)\)(朴素 DP、双层循环);
\(n \leq 10^5 \sim 10^6\):\(O(n \log n)\) 或 \(O(n)\)(排序、二分、线性扫描、堆)。
三、常见数据结构原理#
数据结构 |
核心原理 |
主要操作复杂度 |
典型场景 |
|---|---|---|---|
数组 |
连续内存,按下标随机访问 |
访问 \(O(1)\);中间插入/删除 \(O(n)\) |
随机访问、动态数组( |
链表 |
节点 + 指针串联,非连续存储 |
查找 \(O(n)\);已知位置插入/删除 \(O(1)\) |
频繁增删、LRU 缓存 |
栈 |
后进先出(LIFO) |
进出 \(O(1)\) |
函数调用、括号匹配、单调栈(下一个更大元素) |
队列 |
先进先出(FIFO) |
进出 \(O(1)\) |
BFS、任务调度;双端队列用于滑动窗口 |
哈希表 |
哈希函数映射 + 冲突解决(链地址/开放寻址) |
均摊 \(O(1)\) 查找/插入/删除 |
去重、计数、快速查找、缓存 |
二叉搜索树 |
左小右大,中序有序 |
平衡时 \(O(\log n)\),最坏 \(O(n)\) |
有序集合、动态排名 |
堆 |
完全二叉树,父节点优于子节点(大/小根堆) |
插入/删除 \(O(\log n)\),取最值 \(O(1)\) |
优先队列、TopK、堆排序 |
字典树 |
按字符串前缀共享节点 |
插入/查找 \(O(\text{长度})\) |
前缀匹配、单词统计 |
并查集 |
记录元素所属集合,路径压缩 + 按秩合并 |
近似 \(O(\alpha(n))\) |
连通性判断、Kruskal |
图 |
顶点 + 边,邻接表 / 邻接矩阵存储 |
遍历 \(O(V + E)\)(邻接表) |
路径、连通性、网络流 |
四、算法设计思想#
枚举 / 暴力:直接尝试所有可能,适用于规模小或作为兜底思路;
双指针:有序数组相向/同向移动,或链表快慢指针(判环、找中点);
滑动窗口:维护窗口内状态,解决子数组/子串问题(最小覆盖子串、最长无重复子串);
前缀和 / 差分:\(O(1)\) 求区间和、区间增量;
分治:问题拆成规模更小的子问题分别解决再合并——归并排序、快速排序、二分;
贪心:每步取当前最优,需证明局部最优能推出全局最优(区间调度、哈夫曼编码);
动态规划:适用于重叠子问题 + 最优子结构,自底向上填表,避免重复计算(背包、LIS、区间 DP);
回溯:DFS 搜索所有解并剪枝,用于组合、排列、子集、N 皇后、数独等;
二分:在单调序列中查找,或对答案二分(最小化最大值 / 最大化最小值 / 第 K 小);
位运算:\(O(1)\) 底层操作,用于优化枚举、状态压缩(状压 DP 的 mask)、按位统计贡献(拆位);
图算法:BFS(最短路、层序)、DFS(连通分量、拓扑)、最短路径(Dijkstra、Bellman-Ford、Floyd)、最小生成树(Kruskal、Prim)。
五、模块导航#
模块 |
内容 |
文件 |
|---|---|---|
刷题模板与工具函数 |
C++/Java 模板、类型转换、最值、运算符重载、大数计算 |
|
基础数据结构 |
数组、字符串、链表、栈、双端队列、单调队列、单调栈、优先队列、哈希表/集合、有序表 |
|
滑动窗口与双指针 |
相向双指针、定长/不定长滑动窗口、双序列双指针 |
|
前缀和与差分 |
一维/二维前缀和、差分数组、前缀和 + 哈希表 |
|
位运算 |
基础操作、异或恒等式、lowbit/置位技巧、枚举子集、拆位统计 |
|
树与字典树 |
二叉树递归/前中后序/层序遍历、最大深度、对称判定、BST 操作、树状数组、字典树 |
|
回溯算法 |
组合、组合总和、非递减子序列、全排列、N 皇后、解数独 |
|
动态规划 |
基本思想、解题思路、0-1/完全背包、常见 DP 分类(划分/状态机/区间/树形/状压/数位等) |
|
排序与查找 |
堆/归并/插入/快速/希尔排序、二分查找、二分答案、KMP |
|
图论 |
存图方式、DFS/BFS、Bellman-Ford、Dijkstra、Floyd、并查集、生成树、拓扑排序、强连通(Kosaraju/2SAT) |
|
线段树 |
单点更新 + 区间查询、懒标记区间更新 |