结论放在前面
| 数据结构 | 底层实现 | 查找 | 插入 | 删除 | 排序 |
|---|---|---|---|---|---|
| 数组 | 连续内存 | O(1) 访问 / O(n) 搜索 | O(n) | O(n) | O(n log n)(快排/归并/堆排) |
| 动态数组 | 连续内存 + 扩容 | O(1) 访问 / O(n) 搜索 | O(1) (末尾)/O(n) | O(n) | O(n log n)(原地排序,同数组) |
| 链表 | 节点 + 指针 | O(n) | O(1) | O(1) | O(n log n)(归并排序,链表友好) |
| 栈 | 数组或链表 | O(n) | O(1) | O(1) | O(n log n)(需先转存到数组) |
| 队列 | 链表或循环数组 | O(n) | O(1) | O(1) | O(n log n)(需先转存到数组) |
| 哈希表 | 数组 + 链表/树 | 平均 O(1) | 平均 O(1) | 平均 O(1) | O(n log n)(提取键/值到数组排序) |
| 二叉搜索树 | 节点 + 左右指针 | 平均 O(log n) | 平均 O(log n) | 平均 O(log n) | 中序遍历 O(n)(本身就是有序结构) |
| 平衡树 | 节点 + 旋转/变色 | O(log n) | O(log n) | O(log n) | 中序遍历 O(n)(本身有序) |
| 堆 | 数组(完全二叉树) | O(1) 看最值 | O(log n) | O(log n) | O(n log n)(堆排序:建堆 O(n) + 逐个出堆 O(n log n)) |
| B+ 树 | 多路节点 + 叶子链表 | O(log_m n) | O(log_m n) | O(log_m n) | 叶子遍历 O(n)(叶子节点已有序串联) |
| Trie | 多叉树 + 字符映射 | O(k) | O(k) | O(k) | O(总字符数)(DFS 遍历收集即可有序输出) |
| 跳表 | 多层有序链表 | O(log n) | O(log n) | O(log n) | O(n)(本身有序,直接遍历即可) |
| 邻接表(图) | 数组 + 链表 | O(degree) | O(1) 加边 | O(degree) | 不适用(图无全局序概念) |
排序列的关键解读
本身就有序的数据结构(排序时间就是遍历时间 O(n)):
- 二叉搜索树 / 平衡树 / B+ 树:中序遍历即可得到有序序列,不需要额外排序操作
- 跳表:底层链表已按 key 有序,从头到尾遍历就是排序结果
- Trie:DFS 按字母序访问就能得到有序的字符串列表
需要额外排序算法的数据结构(排序时间 O(n log n)):
- 数组 / 动态数组:可以直接应用快排、归并排序等,O(n log n) 是比较排序的极限
- 链表:归并排序天然适合链表(不需要额外空间),时间复杂度 O(n log n)
- 哈希表:元素无序,需提取所有键/值到数组后再排序
- 栈 / 队列:同样需要先取出所有元素放入数组再排序
特别的排序方式:
- 堆 :堆排序本身就是基于堆的排序算法------建堆 O(n),然后逐个弹出堆顶元素 O(n log n),总计 O(n log n)。而且可以原地排序,不需要额外空间
- B+ 树:叶子节点之间通过指针串联成链表,直接遍历叶子链即可得到有序结果,是数据库范围查询如此高效的原因
数组(Array)
底层实现: 连续内存块,元素按索引顺序紧邻排列。每个元素占用固定大小的字节,通过 base_address + index * element_size 直接计算出地址。
| 操作 | 时间复杂度 | 原因 |
|---|---|---|
| 随机访问 | O(1) | 直接通过地址计算公式定位 |
| 搜索 | O(n) | 需遍历比较 |
| 末尾插入/删除 | O(1) | 直接操作最后一个位置 |
| 中间插入/删除 | O(n) | 需移动后续所有元素 |
有序数组的二分搜索可达 O(log n),但插入/删除仍需 O(n) 移动元素。
动态数组(ArrayList / Vector)
底层实现: 底层仍然是连续数组,但额外维护容量和长度两个变量。当元素个数达到容量上限时,申请更大的新数组(通常为原容量的 1.5 倍或 2 倍),将旧数据逐一拷贝过去,再释放旧数组。
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 随机访问 | O(1) | 同数组 |
| 末尾插入 | 摊还 O(1) | 未满时直接写入 O(1);满时扩容拷贝 O(n),但扩容次数少,摊还下来为 O(1) |
| 中间插入/删除 | O(n) | 需移动元素 |
| 搜索 | O(n) | 线性遍历 |
链表(Linked List)
底层实现: 非连续存储,每个节点包含数据和指向下一节点的指针(单向链表)或前驱/后继指针(双向链表)。节点在内存中分散,通过指针串联。
| 类型 | 底层结构 | 查找 | 插入/删除(已知位置) |
|---|---|---|---|
| 单向链表 | 节点 + next 指针 | O(n) | O(1) |
| 双向链表 | 节点 + prev + next 指针 | O(n) | O(1) |
| 循环链表 | 单向/双向 + 首尾相连 | O(n) | O(1) |
关键特性: 链表不需要连续内存,插入/删除只需修改指针,因此是 O(1)。但随机访问必须从头遍历,没有下标计算,所以是 O(n)。
栈(Stack)与队列(Queue)
底层实现: 既可以用数组实现(连续存储),也可以用链表实现。两种实现的时间复杂度一样。
| 数据结构 | 底层常用实现 | 入(push/enqueue) | 出(pop/dequeue) | 查看队首/栈顶 |
|---|---|---|---|---|
| 栈 | 数组或链表 | O(1) | O(1) | O(1) |
| 队列 | 链表或循环数组 | O(1) | O(1) | O(1) |
队列的循环数组实现:维护头指针(front)和尾指针(rear),在数组逻辑上形成环,避免数组头部删除后大量移动元素。当数组满时同样需要扩容(摊还 O(1))。
哈希表(Hash Table)
底层实现: 底层是数组(桶数组),通过哈希函数将键映射到数组下标。关键是解决哈希冲突的两种策略:
| 解决冲突方式 | 底层结构 | 平均查找 | 最坏查找 | 说明 |
|---|---|---|---|---|
| 拉链法 | 数组 + 链表/红黑树 | O(1) | O(n) / O(log n) | Java 8+ 在链表长度 > 8 时转为红黑树优化 |
| 开放寻址法 | 纯数组(线性探测/二次探测) | O(1) | O(n) | 需装载因子控制,如 Java ThreadLocalMap |
装载因子(load factor) :当元素个数 / 桶数量超过阈值时触发扩容(rehash),重新分配更大的数组并将所有元素重新哈希,摊还成本为 O(1)。
树形结构
二叉搜索树(BST)
底层实现: 节点结构(数据 + 左孩子指针 + 右孩子指针),递归定义:左子树所有节点 < 根节点 < 右子树所有节点。
| 操作 | 平均 | 最坏(退化为链表) |
|---|---|---|
| 查找 | O(log n) | O(n) |
| 插入 | O(log n) | O(n) |
| 删除 | O(log n) | O(n) |
平衡二叉搜索树(AVL / 红黑树)
底层实现: 在 BST 节点基础上增加平衡因子 (AVL)或颜色标记 (红黑树),在插入/删除后通过旋转 (左旋/右旋)和变色恢复平衡。
| 特性 | AVL 树 | 红黑树 |
|---|---|---|
| 平衡标准 | 左右子树高度差 ≤ 1 | 最长路径不超过最短路径的 2 倍 |
| 查找 | O(log n) | O(log n) |
| 插入 | O(log n) | O(log n) |
| 删除 | O(log n) | O(log n) |
| 旋转次数(插入) | 至多 1 次 | 至多 2 次 |
| 实际应用 | 适用于查询频繁的场景 | 适用于插入/删除频繁的场景(如 TreeMap、TreeSet) |
B 树 / B+ 树
底层实现: 多路平衡搜索树,一个节点可以存储多个键值 和多个子节点指针。B+ 树的所有数据集中在叶子节点,叶子节点之间通过指针串联成有序链表。
| 操作 | 时间复杂度 | 应用场景 |
|---|---|---|
| 查找 | O(log_m n) | 数据库索引(InnoDB)、文件系统 |
| 插入 | O(log_m n) | 机械硬盘/SSD 友好(减少磁盘 I/O) |
| 删除 | O(log_m n) | 范围查询效率极高(B+ 树) |
m 为树的阶数,通常为几百到上千,因此树的高度很低(通常 3~4 层),磁盘 I/O 次数极少。
堆(Heap)
底层实现: 通常用数组 实现(完全二叉树存储在数组中,下标 i 的左右孩子分别为 2i+1 和 2i+2)。堆化操作通过上浮(swim) 和下沉(sink)完成。
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 查看最值 | O(1) | 堆顶即根节点,数组下标 0 |
| 插入 | O(log n) | 将元素放末尾,上浮调整 |
| 删除最值 | O(log n) | 将堆顶与末尾元素交换,下沉调整 |
| 建堆 | O(n) | Floyd 算法,从最后一个非叶子节点下沉 |
图(Graph)
底层实现: 两种经典方式:
| 存储方式 | 底层结构 | 空间 | 查边 | 遍历所有邻边 | 适用场景 |
|---|---|---|---|---|---|
| 邻接矩阵 | 二维数组 V×V | O(V²) | O(1) | O(V) | 稠密图 |
| 邻接表 | 数组 + 链表/动态数组 | O(V + E) | O(degree) | O(degree) | 稀疏图 |
遍历时间复杂度:DFS 和 BFS 均为 O(V + E)(邻接表)或 O(V²)(邻接矩阵)。
Trie(前缀树 / 字典树)
底层实现: 多叉树结构,每个节点包含一个字符映射表(数组或哈希表)和一个结束标记。根节点为空,路径上的字符拼接起来组成存储的字符串。
| 操作 | 时间复杂度 | 空间 |
|---|---|---|
| 插入 | O(k) | O(总字符数) |
| 查找 | O(k) | --- |
| 前缀搜索 | O(k) | --- |
k 为字符串长度。Trie 的复杂度与数据量 n 无关,只与字符串长度相关,这是它相比哈希表做前缀查询时的核心优势。
跳表(Skip List)
底层实现: 在有序链表的基础上,随机抽取部分节点建立多层索引。每一层都是下一层的一个"快速通道",从顶层开始逐层向下搜索。
| 操作 | 时间复杂度 | 空间 |
|---|---|---|
| 查找 | 平均 O(log n) | O(n) |
| 插入 | 平均 O(log n) | --- |
| 删除 | 平均 O(log n) | --- |
实际应用 :Redis 的 Sorted Set 底层使用了跳表加哈希表的组合。跳表实现简单,且支持范围查询(相较于哈希表)。