1.算法进阶——Vector底层原理与倍增扩容方法 详解

本文详细介绍了C++ STL中vector的倍增扩容方法,通过实例展示了size和capacity如何线性和倍增变化。分析了为什么选择倍增策略,以及与静态数组的比较,指出理想的扩容倍增率接近1.618,而在Java的ArrayList中实际值为1.5。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开新专栏啦,该系列是 极客时间-黄清昊-算法实战高手课 的学习笔记,以此为媒介深挖我们之前提到但没展开的内容。希望大家一起学习,一起进步。

回顾

我们在之前的内容,分享过vector作为顺序容器的一部分内容:
c++stl
我把之前的内容挪一部分来:
动态数组,用于 O(1) 的随机读取.
实现原理:
由三个指针实现,
第一个指针指向vector起始位置
第二个指针指向目前有数据的末尾
第三个指针指向整个vector容器所占内存的末尾。
在这里插入图片描述
由此实现:

template<class _Ty,
    class _Ax>
    class vector
        : public _Vector_val<_Ty, _Ax>
    {
   
      // varying size array of values
public:
    /********/
protected:
    pointer _Myfirst;   // pointer to beginning of array
    pointer _Mylast;    // pointer to current end of sequence
    pointer _Myend; // pointer to end of array
    };

由此:
注意:
end 指向第二个指针
size 是2-1 而capacity 是3-1(不懂的话举个例子,水杯里有半杯水,半杯水是其size,而一杯水是其capacity)
注意resize的时候,是新在堆中开辟内存,赋值之后删除旧数组

template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
   
   
public:
    iterator begin() {
   
   return _Myfirst;}
    iterator end() {
   
   return _Mylast;}
    size_type size() const {
   
   return size_type(end() - begin());}
    size_type capacity() const {
   
   return size_type(_Myend - begin());}
    bool empty() const {
   
   
<think>首先,用户的问题是关于C++实现LCA(最低公共祖先)算法倍增方法。我需要提供详细的解释和代码实现。 从系统指令中,我必须: - 使用$...$格式行内数学表达式。 - 使用$$...$$格式独立公式。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾添加引用标识。 - 结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考引用: - 引用[1]是关于C++的LCA倍增法实现。 - 引用[2]解释了倍增法,并提供了ST表的代码。 - 引用[3]讨论了LCA的预处理。 - 引用[4]提供了LCA函数的具体实现。 用户意图是了解如何使用C++实现LCA倍增法,所以我需要基于这些引用构建回答。 关键点: - 倍增法是一种优化方法,将线性处理转化为对数级。 - LCA问题:找到树中两个节点的最低公共祖先。 - 实现步骤包括: 1. 预处理:计算每个节点的深度和倍增祖先表。 2. 查询:通过倍增法快速找到LCA。 从引用[4]中,LCA函数包括: - 调整深度:使两个节点在同一深度。 - 然后,使用倍增法找到LCA。 步骤分解: 1. **预处理**: - 计算每个节点的深度(depth)。 - 构建一个表(例如,parent[i][j]),表示节点i的第2^j个祖先。 - 这通常在DFS遍历树后完成。 2. **查询LCA**: - 如果节点a深度小于b,则交换。 - 将深度较大的节点向上移动,直到同一深度。 - 然后,同时向上移动a和b,直到找到LCA。 从引用[4]的代码: ```cpp int lca(int a, int b) { if (deep[a] < deep[b]) swap(a, b); for (int i = 0; (1 << i) <= deep[a]; i++); i--; for (int j = i; j >= 0; j--) if (deep[a] - (1 << j) >= deep[b]) a = p[a][j]; if (a == b) return a; for (int j = i; j >= 0; j--) if (p[a][j] != -1 && p[a][j] != p[b][j]) { a = p[a][j]; b = p[b][j]; } return p[a][0]; } ``` 在回答中,我需要: - 解释概念。 - 提供完整的C++代码示例。 - 确保数学表达式正确:例如,深度差、倍增原理。 - 生成相关问题。 结构回答: 1. **介绍LCA和倍增法**:简要解释问题和算法。 2. **预处理步骤**:描述如何构建深度和祖先表。 3. **查询步骤**:详细解释倍增查询过程。 4. **完整代码实现**:提供基于引用的完整C++代码。 5. **生成相关问题**:在最后添加相关问题。 数学表达式: - 行内:如深度差 $ \text{depth}[a] - 2^j \geq \text{depth}[b] $。 - 独立公式:如倍增原理 $ \text{parent}[i][j] = \text{parent}[\text{parent}[i][j-1]][j-1] $。 使用中文回答。 最后,添加引用标识:在引用段落末尾加[^n],但引用是基于提供的文本,所以我会在解释时引用它们。 相关问题生成: - 如:如何优化LCA算法?其他实现方法?应用场景?</think>### C++实现LCA(最低公共祖先)倍增详解 LCA(最低公共祖先)是树结构中两个节点的最近共同祖先节点。倍增法通过预处理每个节点的祖先信息,将查询时间复杂度优化到 $O(\log n)$,适用于静态树结构。下面我将逐步解释实现原理C++代码。 #### 1. **倍增法核心思想** 倍增法利用二进制分解原理:任何整数可表示为 $2^0 + 2^1 + \cdots + 2^k$。预处理时,为每个节点存储其 $2^j$ 级祖先($j \geq 0$)。查询时,通过“跳步”方式(每次跳 $2^j$ 层)快速调整节点深度并找到LCA。时间复杂度: - 预处理:$O(n \log n)$ - 查询:$O(\log n)$ 数学上,祖先表定义如下: $$ \text{parent}[u][j] = \begin{cases} \text{直接父节点} & \text{if } j = 0 \\ \text{parent}[\text{parent}[u][j-1]][j-1] & \text{if } j > 0 \end{cases} $$ 这表示节点 $u$ 的第 $2^j$ 个祖先可通过递归查询得到[^4]。 #### 2. **实现步骤** ##### (1) 预处理:构建深度和祖先表 - **输入**:树的邻接表表示(节点从1开始编号)。 - **步骤**: 1. 通过DFS计算每个节点的深度 $\text{depth}[u]$。 2. 初始化祖先表 $\text{parent}[u][j]$,其中 $j$ 最大为 $\lfloor \log_2 n \rfloor$。 3. 递归填充祖先表:$\text{parent}[u][j] = \text{parent}[\text{parent}[u][j-1]][j-1]$。 ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 100010; // 最大节点数 const int MAXLOG = 20; // 最大对数级别: log2(MAXN) vector<int> tree[MAXN]; // 树的邻接表 int depth[MAXN]; // 节点深度 int parent[MAXN][MAXLOG]; // 祖先表 // DFS遍历树,初始化深度和祖先表 void dfs(int u, int p) { depth[u] = depth[p] + 1; parent[u][0] = p; for (int j = 1; j < MAXLOG; j++) { if (parent[u][j-1] != -1) { parent[u][j] = parent[parent[u][j-1]][j-1]; } else { parent[u][j] = -1; // 无效祖先标记 } } for (int v : tree[u]) { if (v != p) dfs(v, u); } } // 预处理入口 void preprocess(int root, int n) { depth[root] = 0; for (int i = 0; i < MAXLOG; i++) { parent[root][i] = -1; // 根节点无祖先 } for (int v : tree[root]) { dfs(v, root); } } ``` ##### (2) 查询LCA:倍增跳步 - **输入**:两个节点 $a$ 和 $b$。 - **步骤**: 1. **深度对齐**:如果 $\text{depth}[a] < \text{depth}[b]$,交换 $a$ 和 $b$。然后将 $a$ 向上跳步,直到 $\text{depth}[a] = \text{depth}[b]$。跳步时,从最大步长 $2^j$ 开始尝试。 2. **查找LCA**:如果此时 $a = b$,则 $b$ 是LCA;否则,同时向上跳 $a$ 和 $b$,直到它们的祖先不同。最终,$\text{parent}[a][0]$ 是LCA。 ```cpp int lca(int a, int b) { // 确保a深度 ≥ b深度 if (depth[a] < depth[b]) swap(a, b); // 计算最大跳步指数 j: 满足 2^j <= depth[a] int j = 0; while ((1 << j) <= depth[a]) j++; j--; // 对齐深度:a向上跳,直到 depth[a] = depth[b] for (int k = j; k >= 0; k--) { if (depth[a] - (1 << k) >= depth[b]) { a = parent[a][k]; } } if (a == b) return a; // b是a的祖先 // 同时跳步a和b,找LCA for (int k = j; k >= 0; k--) { if (parent[a][k] != -1 && parent[a][k] != parent[b][k]) { a = parent[a][k]; b = parent[b][k]; } } return parent[a][0]; // 返回直接父节点作为LCA } ``` #### 3. **完整代码示例** 以下是一个完整的C++程序,包括树的构建、预处理和查询: ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> using namespace std; const int MAXN = 100010; const int MAXLOG = 20; vector<int> tree[MAXN]; int depth[MAXN], parent[MAXN][MAXLOG]; void dfs(int u, int p) { depth[u] = depth[p] + 1; parent[u][0] = p; for (int j = 1; j < MAXLOG; j++) { parent[u][j] = (parent[u][j-1] == -1) ? -1 : parent[parent[u][j-1]][j-1]; } for (int v : tree[u]) { if (v != p) dfs(v, u); } } void preprocess(int root, int n) { fill(depth, depth + n + 1, 0); for (int i = 0; i <= n; i++) { for (int j = 0; j < MAXLOG; j++) parent[i][j] = -1; } depth[root] = 0; for (int v : tree[root]) dfs(v, root); } int lca(int a, int b) { if (depth[a] < depth[b]) swap(a, b); int j = log2(depth[a]) + 1; for (int k = j; k >= 0; k--) { if (depth[a] - (1 << k) >= depth[b]) a = parent[a][k]; } if (a == b) return a; for (int k = j; k >= 0; k--) { if (parent[a][k] != -1 && parent[a][k] != parent[b][k]) { a = parent[a][k]; b = parent[b][k]; } } return parent[a][0]; } int main() { int n = 5; // 节点数 // 示例树: 1-2, 1-3, 2-4, 2-5 tree[1].push_back(2); tree[2].push_back(1); tree[1].push_back(3); tree[3].push_back(1); tree[2].push_back(4); tree[4].push_back(2); tree[2].push_back(5); tree[5].push_back(2); preprocess(1, n); // 根节点为1 cout << "LCA(4,5): " << lca(4,5) << endl; // 输出2 cout << "LCA(3,4): " << lca(3,4) << endl; // 输出1 return 0; } ``` - **代码说明**: - `preprocess` 初始化深度和祖先表,需在查询前调用一次。 - `lca` 函数处理查询,时间复杂度 $O(\log n)$。 - 示例树中,节点4和5的LCA是2,节点3和4的LCA是1[^4]。 #### 4. **关键优化注意事项** - **空间优化**:祖先表使用 $O(n \log n)$ 空间,可通过稀疏表减少开销。 - **边界处理**:根节点的祖先标记为-1,避免越界。 - **性能**:预处理 $O(n \log n)$ 是主要开销,适合查询密集场景。 - **适用场景**:静态树(结构不变),如网络路由、基因树分析[^2]。 倍增法通过二进制分解实现高效跳步,是LCA问题的经典解法。实际应用中,可结合Tarjan或RMQ进一步优化[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值