📋 摘要
  • C++ 数据结构面试高频问答,每题保留回答重点与扩展知识
  • 详细原理解析与代码示例见 03-Resources/cpp/C++ 数据结构
  • 持续补充中

1. 数组和链表有什么区别?各自适用场景?

📋 回答重点
  • 数组内存连续、链表指针串联,这是所有差异的根源。
  • 数组随机访问 O(1),插入/删除(中间)O(n);链表相反,访问 O(n),已知节点处增删 O(1)。
  • 数组缓存友好(cache line 命中),链表指针跳跃 cache miss 频繁。
  • 数组空间紧凑无额外指针,链表每节点多 1~2 个指针。
  • 数组适合频繁按下标读、元素稳定;链表适合频繁头/中部增删、规模动态不可预估。

扩展知识

  • C++ 对应容器:std::vector(数组)、std::list(双向链表)、std::forward_list(单向链表)。
  • vector 尾部插入均摊 O(1),扩容时迭代器全部失效。
  • 现代 CPU 下 vector 遍历远快于 list,工程上多数场景默认选 vector。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

2. 用 C++ 实现一个单链表(增删查改)?

📋 回答重点
  • 节点 struct ListNode { int val; ListNode* next; },链表类维护 head 指针。
  • 尾部插入需遍历到末尾;按值查找遍历比较;删除需找到前驱。
  • 删除用「指向指针的指针」(ListNode**)可统一处理删头节点与中间节点。
  • 插入先接新节点 next 再断前驱,避免后半段丢失。
  • 析构函数遍历释放全部节点,防内存泄漏。

扩展知识

  • 高频变体:反转链表(迭代/递归)、链表中点(快慢指针)、判断环(快慢指针相遇)、合并两个有序链表。
  • 单向链表删除给定「非尾」节点可用「值覆盖 + 删除后继」技巧,O(1)。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

3. 栈和队列有什么区别?如何用栈实现队列?

📋 回答重点
  • 栈是 LIFO(后进先出),队列是 FIFO(先进先出)。
  • C++ 中 std::stack/std::queue 都是容器适配器,默认底层 std::deque。
  • 栈操作 push/pop/top,队列操作 push/pop/front/back。
  • 两个栈实现队列:in 栈入队,out 栈出队;out 空时把 in 全倒入 out,栈顶即队首。
  • 每个元素最多搬移两次,出队均摊 O(1)。

扩展知识

  • 队列实现栈:入栈时新元素入队,再把前 size-1 个元素出队再入队,使新元素总在队首。
  • 栈应用:函数调用栈、括号匹配、DFS;队列应用:BFS、任务调度、消息队列。
  • 单调栈/单调队列是高频进阶考点(如「每日温度」「滑动窗口最大值」)。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

4. 哈希表的原理?如何解决哈希冲突?

📋 回答重点
  • 哈希函数把 key 映射到数组下标,理想 O(1) 定位。
  • 不同 key 映射到同一下标即哈希冲突,冲突不可避免。
  • 链地址法:每槽挂链表(unordered_map),冲突追加到链表。
  • 开放定址法:冲突时按探测序列找下一个空槽,缓存友好但删除需墓碑。
  • 平均 O(1) 前提是哈希均匀 + 负载因子受控,最坏退化 O(n)。

扩展知识

  • 探测序列:线性探测、二次探测、双重哈希。
  • 链过长可转红黑树(Java)或 rehash(C++),保最坏 O(log n) 或摊平。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

5. unordered_map 的底层实现原理?

📋 回答重点
  • 采用链地址法:桶数组(vector)+ 每个桶一条单向链表。
  • 查找流程:hash(key) → 取模定位桶 → 桶内链表顺序查找。
  • 负载因子 = size / bucket_count,超过 max_load_factor(默认 1.0)触发 rehash。
  • rehash 时桶数翻倍(常取质数),重哈希全部元素,O(n)。
  • 平均 O(1) 但无序、内存大、rehash 后迭代器失效。

扩展知识

  • 与 map 对比:map 红黑树有序 O(log n),unordered_map 无序平均 O(1)。
  • 已知规模可 reserve(n) 预留桶数,避免频繁 rehash。
  • 自定义 key 需提供 std::hash<T> 特化和 operator==。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

6. 哈希函数如何设计?什么是哈希碰撞?

📋 回答重点
  • 哈希函数把任意 key 映射为桶下标;碰撞指不同 key 得同一哈希值。
  • 好哈希三要素:分布均匀(雪崩效应)、计算快、确定性。
  • 除留余数法 key % m(m 取质数)、乘法散列、字符串多项式哈希 h = h*31 + c。
  • 碰撞不可避免(鸽巢原理),目标是最小化而非消除。
  • 雪崩效应:输入一位变化引起约一半输出位变化。

扩展知识

  • 工程常用:murmurhash、xxhash、FNV-1a。
  • std::hash 对整数基本恒等映射,字符串实现由库决定。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

7. 堆(优先队列)的原理?如何用堆求 TopK?

📋 回答重点
  • 堆是完全二叉树 + 堆序(大顶堆根最大,小顶堆根最小)。
  • 数组紧凑存储:节点 i 左子 2i+1、右子 2i+2、父 (i-1)/2。
  • push 上浮 O(log n),pop 下沉 O(log n),top 取 a[0] O(1)。
  • TopK:维护大小 K 的小顶堆,元素大于堆顶则替换,堆顶即第 K 大。
  • 复杂度 O(n log k),空间 O(k),可处理流式数据。

扩展知识

  • 「第 K 小」用大顶堆;TopK 另一路线是快速选择 quickselect(平均 O(n))。
  • 双堆(大顶 + 小顶)求流式数据中位数。
  • 应用:排行榜、Dijkstra 优先队列、定时器。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

8. priority_queue 的底层实现?

📋 回答重点
  • 容器适配器,底层默认 std::vector,用堆算法维持堆序。
  • 只暴露 top/push/pop,不支持遍历和随机访问。
  • 模板参数 priority_queue<T, Container, Compare>,默认 vector + less(大顶堆)。
  • std::greater 得小顶堆(Compare 语义与直觉相反)。
  • push/pop O(log n),top O(1)。

扩展知识

  • 自定义比较器:函数对象 / lambda(配 decltype)/ 重载 operator<。
  • 不能删任意元素、不能改优先级;需此能力改用 std::set 或手写堆 + 懒删除。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

9. 二叉树的遍历(前/中/后/层序)如何实现?

📋 回答重点
  • 前序「根左右」、中序「左根右」、后序「左右根」、层序逐层从左到右。
  • DFS 递归三行模板;迭代需显式栈模拟递归调用栈。
  • 中序非递归:一路向左压栈,弹出访问后转向右子树。
  • 层序用队列,按层计数(q.size())分批处理。
  • BST 中序遍历得到有序序列。

扩展知识

  • 后序非递归技巧:前序「根右左」再 reverse 即后序,或节点加访问标记。
  • 前序可序列化树;层序即 BFS,可求树宽度/最短路径。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

10. 二叉搜索树 BST 的特点?为什么容易退化?

📋 回答重点
  • 左子树全小于根、右子树全大于根,中序有序。
  • 查找/插入/删除平均 O(log n)。
  • 形态完全由插入顺序决定,有序插入退化成单链。
  • 退化后树高 O(n),所有操作退化 O(n)。
  • 解决退化:AVL、红黑树、Treap/Splay。

扩展知识

  • C++ std::map/std::set 用红黑树正是为避免退化。
  • 依次插入 1..5 得全右链,是退化典型例子。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

11. 平衡二叉树 AVL 的原理?

📋 回答重点
  • 自平衡 BST,任意节点左右子树高度差(平衡因子)≤1。
  • 平衡因子 = 左子树高 - 右子树高,正常取值 -1/0/1。
  • 插入/删除失衡通过旋转恢复,保证树高 O(log n)。
  • 四种失衡:LL 右旋、RR 左旋、LR 先左后右、RL 先右后左。
  • 严格平衡,查找最优但插入删除旋转频繁,适合读多写少。

扩展知识

  • 失衡节点平衡因子必为 ±2,在最近失衡祖先处旋转一次即可。
  • 相比红黑树更矮、查找更快,但写入成本高。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

12. 红黑树的特点?为什么 map 用红黑树不用 AVL?

📋 回答重点
  • 近似平衡 BST,五性质:非红即黑、根黑、叶子黑、红不连续、黑高一致。
  • 由性质推出最长路径 ≤ 2× 最短路径,保证 O(log n)。
  • 插入/删除最多常数次旋转 + 重着色,比 AVL 旋转少。
  • map 用红黑树因写入成本更低、整体吞吐更高(读写均衡)。
  • AVL 只在读远多于写时才更优。

扩展知识

  • 红黑树有序性支持 lower_bound、范围查询、按序遍历。
  • 应用:std::map/std::set、Linux CFS、epoll、Java TreeMap。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

13. 图的存储方式:邻接矩阵 vs 邻接表?

📋 回答重点
  • 邻接矩阵 g[i][j] 存边,邻接表每顶点一条链表存邻接点。
  • 矩阵空间 O(V²)、查边 O(1)、遍历邻居 O(V)。
  • 邻接表空间 O(V+E)、查边 O(deg)、遍历邻居 O(deg)。
  • 稠密图用矩阵,稀疏图用邻接表。
  • 工程绝大多数图稀疏,邻接表是默认选择。

扩展知识

  • 带权图邻接表存 vector<vector<pair<int,int>>>,节点存 (邻接点, 权重)。
  • 边列表 vector<tuple<u,v,w>> 适合 Kruskal。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

14. 图的遍历 BFS 和 DFS?

📋 回答重点
  • BFS 用队列逐层扩散,DFS 用栈/递归一路深入回溯。
  • BFS 天然求无权图最短路径(首次到达即最短)。
  • DFS 适合连通性、环检测、拓扑排序。
  • BFS 空间 O(层宽),DFS 空间 O(树高/递归深度)。
  • 递归 DFS 深层图可能爆栈,可改迭代或 BFS。

扩展知识

  • 三色标记:白未访问、灰在栈中、黑已结束,灰→灰即发现环。
  • 应用:BFS 最短路径、N 度人脉、爬虫;DFS 依赖解析、迷宫、拓扑排序。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

15. 跳表的原理?为什么 Redis 用跳表?

📋 回答重点
  • 在有序链表上建多层索引,第 0 层完整、上层抽样。
  • 查找从最高层跳,不能跳就下沉,O(log n)。
  • 空间换时间 + 随机化层高,实现比红黑树简单。
  • Redis ZSet 用跳表:有序 + 范围查询 + 排名(span)高效。
  • 局部修改、无复杂旋转,并发/持久化更友好。

扩展知识


16. 并查集的原理和适用场景?

📋 回答重点
  • 维护不相交集合,每集合一棵树,根为代表元。
  • find(x) 找根 + 路径压缩;unite(x,y) 合并 + 按秩。
  • 路径压缩把沿途节点直接挂根,摊平树高。
  • 按秩合并小树挂大树,控制高度增长。
  • 两者结合均摊复杂度反阿克曼函数 α(n),近似 O(1)。

扩展知识

  • 应用:图连通性判断、连通分量计数、Kruskal 判环、朋友圈/省份数量、等价关系。
  • 只路径压缩是 O(log n) 均摊,两者结合才最优。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

17. Trie 树(前缀树)的原理和适用场景?

📋 回答重点
  • 多叉树,节点存「字符→子节点」映射,路径即前缀。
  • 共享公共前缀压缩空间,前缀查询天然高效。
  • 插入/查询 O(m),m 为字符串长度,与词表规模无关。
  • 节点需标记 is_end 区分「前缀」与「完整单词」。
  • 应用:自动补全、敏感词过滤、IP 路由最长前缀匹配。

扩展知识


18. 布隆过滤器的原理?如何控制误判率?

📋 回答重点
  • 位数组 + k 个哈希函数,插入把 k 个位置置 1。
  • 查询 k 位全 1「可能存在」,任一 0「一定不存在」。
  • 只有假阳性(误判存在),无假阴性,不支持删除。
  • 误判率 p ≈ (1 - e^(-kn/m))^k,最优 k = (m/n)ln2。
  • m/n 越大误判越低,空间换精度。

扩展知识

  • 典型应用:缓存穿透防护、URL 去重、垃圾邮件过滤、LSM 树判断 key 是否可能存在。
  • 需删除用计数布隆过滤器。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

19. B 树和 B+ 树的区别?为什么数据库索引用 B+ 树?

📋 回答重点
  • 都是多路平衡搜索树,矮胖、专为磁盘减少 I/O 设计。
  • B 树内部节点也存数据;B+ 树只有叶子存全部数据,内部只存索引。
  • B+ 树叶子用双向链表串联,支持高效范围查询和顺序扫描。
  • B+ 树每次查找都到叶子,路径等长、性能稳定。
  • 数据库索引用 B+ 树:磁盘友好、范围查询快、查询稳定。

扩展知识

  • MySQL InnoDB 主键索引即 B+ 树,叶子存完整行;辅助索引叶子存主键值(回表)。
  • 红黑树是二叉树,高度高、磁盘 I/O 多,不适合数据库索引。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

20. 如何实现 LRU 缓存?

📋 回答重点
  • 哈希表 + 双向链表:哈希 O(1) 定位,链表维护访问顺序(头新尾旧)。
  • get 命中移到头部(访问也算刷新);put 满时淘汰尾部(最久未用)。
  • std::list::splice 可 O(1) 移动节点到头部。
  • 用双向链表而非单向:删除尾节点时能经 prev O(1) 定位新尾。
  • get/put 均 O(1)。

扩展知识

  • 应用:页面置换、CPU 缓存、Redis allkeys-lru、InnoDB Buffer Pool。
  • 常见坑:splice/erase 后同步 map 迭代器;get 命中必须刷新顺序。
  • 详细讲解:03-Resources/cpp/C++ 数据结构


21. 线段树的原理和适用场景?

📋 回答重点
  • 线段树是平衡二叉树,把区间 [l, r] 递归二分,叶子是单点,每个节点存该区间的聚合信息(和/最值)。
  • 区间查询与区间更新都是 O(log n),建树 O(n),用数组紧凑存储,空间需开 4n。
  • 区间更新靠懒惰标记(lazy):完全覆盖时只打标记、不递归到叶子,访问子区间时再 pushdown 下传并清零。
  • 能维护任意可合并信息(和、最值、最大子段和等),功能比树状数组强,但常数和代码量更大。
  • 适合:区间求和/最值(RMQ)、区间修改、扫描线求矩形面积、主席树求区间第 K 大。

扩展知识

  • 下标从 1 起时节点 i 的左子 2i、右子 2i+1;懒标记务必在下传时累加到子节点并清零自身,否则重复计算。
  • 进阶:动态开点(值域线段树)、线段树合并、扫描线(矩形面积并/周长并)。
  • 对比树状数组:线段树支持区间最值和区间修改,BIT 只支持前缀和类可差分信息。
  • 详细讲解:03-Resources/cpp/C++ 数据结构

22. 树状数组(BIT)的原理和适用场景?

📋 回答重点
  • 树状数组(Fenwick Tree)维护前缀和,支持 O(log n) 单点更新 + O(log n) 前缀和查询,空间 O(n)、代码仅几行。
  • 核心是 lowbit:lowbit(x) = x & (-x),取 x 最低位的 1;tree[i] 存 [i-lowbit(i)+1, i] 的和。
  • 更新沿 i += lowbit(i) 向上累加,查询沿 i -= lowbit(i) 向下累加,均只经过 O(log n) 个节点。
  • 区间和 = sum(r) - sum(l-1);配合差分可做「区间更新 + 单点查询」。
  • 相比线段树:BIT 更快、更省内存,但只支持前缀和类可差分信息,不支持区间最值/区间修改。

扩展知识

  • 经典应用:离散化后求逆序对——倒序遍历,BIT 统计已出现且小于当前值的个数。
  • 实现细节:下标从 1 开始,数组大小 n+1。
  • 对比线段树:能用 BIT 解决时优先 BIT;需要区间最值/区间修改等通用操作才用线段树。
  • 详细讲解:03-Resources/cpp/C++ 数据结构