关于LCA的dfs的一些问答
void dfs(int x,int fa) { dep[x] = dep[fa] + 1; f[x][0] = fa; for(int i = 1; i <= 20; i++){ int y = f[x][i - 1]; f[x][i] = f[y][i - 1]; //这个状态转移需要简单示例演示过程 } for(int i = 0; i < g[x].size(); i++) { int y = g[x][i]; if(y != fa) //为什么y不是x父节点就可以继续dfs,不会导致重复搜索吗 dfs(y,x); } }
关于倍增LCA算法的DFS函数详解
1. 状态转移部分解析(核心难点)
for(int i = 1; i <= 20; i++){ int y = f[x][i - 1]; f[x][i] = f[y][i - 1]; }
动态规划思想:
-
f[x][i]表示节点x向上跳2^i步到达的祖先 -
状态转移方程:
f[x][i] = f[f[x][i-1]][i-1](跳2^i步 = 先跳2^(i-1)步,再跳2^(i-1)步)
具体示例:
假设树结构:
A / \ B C / \ D E
计算节点D的倍增数组:
-
f[D][0] = B(2^0=1步父节点) -
f[D][1] = f[B][0] = A(2^1=2步祖先) -
f[D][2] = f[A][1] = null(2^2=4步超出树高)
2. 递归条件疑问解答
if(y != fa) // 为什么这个条件能避免重复搜索? dfs(y,x);
原因分析:
-
树的性质决定:树是无向无环图,父子关系是我们DFS时人为规定的方向
-
当从x访问邻居y时:
-
如果y是x的父节点(fa),说明这条边是"回头路"
-
其他情况都是向下探索新分支
-
-
示例:
A → B → D → C-
当从B访问A时(A是B的父节点),应该停止
-
从B访问D时(D不是父节点),继续搜索
-
3. 完整执行流程演示
以如下树结构为例:
1 / \ 2 3 / \ 4 5
DFS调用栈:
-
dfs(1, null)
-
设置dep[1]=1
-
计算f[1][0]=null, f[1][1]=null...
-
访问子节点2和3
-
-
dfs(2, 1)
-
dep[2]=2
-
f[2][0]=1, f[2][1]=f[1][0]=null...
-
访问子节点4和5
-
-
dfs(4, 2)
-
dep[4]=3
-
f[4][0]=2, f[4][1]=f[2][0]=1, f[4][2]=f[1][1]=null...
-
无子节点返回
-
-
dfs(5, 2) 类似过程...
4. 关键问题解答
Q1:为什么不会重复搜索?
-
因为树的无环特性,每个节点只通过非父方向向下搜索
-
访问顺序形成严格的父子关系链
Q2:为什么i从1开始循环?
-
i=0的情况已经在
f[x][0]=fa中处理 -
更高次的倍增基于已计算的低次结果
Q3:为什么上限是20?
-
因为2^20 ≈ 1e6,足以处理大多数树的问题
-
可根据最大深度调整:
log2(max_depth)+1
5. 实际应用时的注意事项
-
初始化要求:
memset(f, 0, sizeof(f)); // 清空倍增数组 dep[0] = 0; // 虚拟根节点的深度
-
LCA查询函数示例:
int lca(int x, int y) { if(dep[x] < dep[y]) swap(x,y); // 跳到同一深度 for(int i=20; i>=0; i--) if(dep[f[x][i]] >= dep[y]) x = f[x][i]; if(x == y) return x; // 一起向上跳 for(int i=20; i>=0; i--) if(f[x][i] != f[y][i]) x=f[x][i], y=f[y][i]; return f[x][0]; }

浙公网安备 33010602011771号