一、倍增算法核心思想
倍增算法(Binary Lifting)是一种基于二进制分解思想的预处理算法,主要用于优化树结构中的跳跃路径查询问题。其核心思想体现在两个关键阶段:
1. 预处理阶段
通过动态规划构建跳跃表(Jump Table),记录每个节点向上跳跃2^j层的祖先节点,形成二进制尺子式的快速通道。
2. 查询阶段
将目标跳跃距离分解为多个2的幂次跳跃,通过组合预存路径实现对数时间复杂度的查询。
算法优势(对比传统O(n)遍历):
- 查询时间复杂度:O(logn)
- 支持动态规划复用预处理结果
- 适用于多查询场景
二进制分解的直观解释
如同用不同面值的货币组合支付任意金额,倍增算法通过2^0, 2^1,...,2^k的跳跃步长组合,可以覆盖任意距离的跳跃需求。例如跳跃5层 = 2^2 + 2^0。
二、算法实现四步曲
步骤1:预处理表结构设计
const int MAXN = 1e5+5; // 最大节点数
const int LOG = 17; // log2(MAXN)的上界值
int up[MAXN][LOG]; // up[i][j]表示节点i的2^j级祖先
int depth[MAXN]; // 节点深度数组
参数选择原则
LOG取值应满足2^(LOG) > MAXN,例如MAXN=1e5时,log2(1e5)≈16.6,取LOG=17
步骤2:初始化基础状态
void dfs(int u, int parent) {
up[u][0] = parent; // 直接父节点即2^0级祖先
for(int v : tree[u]) {
if(v != parent) {
depth[v] = depth[u] + 1;
dfs(v, u); // 递归初始化子树
}
}
}
初始化注意事项
根节点的up[root][0]应设为-1,需在调用dfs前进行初始化
步骤3:动态规划填充跳跃表
void preprocess(int n) {
// 初始化无效祖先标记
memset(up, -1, sizeof(up));
// 动态规划填充跳跃表
for(int j=1; j<LOG; ++j) {
for(int i=1; i<=n; ++i) {
if(up[i][j-1] != -1)
up[i][j] = up[up[i][j-1]][j-1];
}
}
}
填表顺序原理
基于递推关系:2^j = 2^(j-1) + 2^(j-1),因此每个节点的2^j级祖先等于其2^(j-1)级祖先的2^(j-1)级祖先
步骤4:二进制分解查询(以LCA为例)
int lca(int u, int v) {
// 对齐深度(假设u更深)
if(depth[u] < depth[v]) swap(u, v);
// 二进制分解提升较深节点
for(int j=LOG-1; j>=0; --j) {
if(depth[u] - (1<<j) >= depth[v])
u = up[u][j];
}
// 特判直接祖先情况
if(u == v) return u;
// 同步提升寻找最近公共祖先
for(int j=LOG-1; j>=0; --j) {
if(up[u][j] != up[v][j]) {
u = up[u][j];
v = up[v][j];
}
}
return up[u][0]; // 最终父节点即为LCA
}
三、关键优化技巧
-
空间压缩
对于非二叉树,可采用链式前向星存储树结构 -
预处理加速
预先计算log2表,将位运算转换为查表操作:int log_table[MAXN]; void init_log() { log_table[1] = 0; for(int i=2; i<MAXN; ++i) log_table[i] = log_table[i/2] + 1; }
-
路径压缩
结合并查集思想优化跳跃路径,适用于特定场景
四、复杂度分析
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
预处理 | O(n logn) | O(n logn) |
单次查询 | O(logn) | O(1) |
暴力法对比 | O(n) | O(1) |
复杂度优势场景
当查询次数q达到1e5量级时,总时间复杂度从O(nq)优化为O(n logn + q logn),效率提升约10^4倍
五、算法扩展与变种
-
路径权值查询
扩展跳跃表存储路径权值极值:int max_edge[MAXN][LOG]; // 存储路径最大边权
-
动态树结构
结合欧拉序与线段树实现动态LCA查询 -
森林处理
通过增加root标记处理多棵树的情况:bool is_same_tree(int u, int v) { return root[u] == root[v]; }
-
跳跃终点预测
预处理每个节点的2^j级祖先是否超出树高,避免无效跳跃