1.LCA
LCA(Lowest Common Ancestor)问题,即求树的两个结点的最近公共祖先
首先,我们解决如何查找到树结点的第K个祖先的问题,再使用接下来的算法即可
伪代码:
int LCA(int u,int v){
if u 是 v 的祖先 return u
if v 是 u 的祖先 return v
for(k:1~u的深度)
t = u 的第k个祖先
if t 是 v 的祖先 return t
return 0;//直到根节点了还不是 u 、 v 的公共祖先,根据题目处理了
}
算法的关键在于如何快速查找一个结点的第k个祖先
使用Binary Lifting
我们首先需要对每个结点进行预处理,比如每个结点用一个数组储存他的第1、2、3…个祖先
第n个结点的第k个祖先很容易的可以写成D[n][k]
但是显然这样的预处理是会超时的
Binary Lifting的思想就是,考虑k的二进制表示,比如5=4+1,那么第n个结点的第5个祖先就表示为
第n个结点的第1个祖先的第4个祖先,即:D[ D[N][1] ] [4]
这样我们需要构造的数组就从1、2、3、4、5…(N个)下降到了1、2、4、8、16…(logN个)
使用动态规划的方法构造,易得
dp[n][i]=dp[dp[n][i-1]][i-1](例如第4个祖先=第2个祖先的第二个祖先,dp[n][3]=dp[dp[n][2]][2])
vector<vector<int>> dp;
int depth;
TreeAncestor(int n, vector<int>& parent) {
for(int i=0;i<n;i++){
dp.push_back({});
dp[i].push_back(parent[i]);//dp[n][0]就是n的父亲结点
}
for(depth=1;;depth++){
bool allover=true;//记录是否所有结点都搜到根节点以上的-1了
for(int i=0;i<n;i++){
//当前已经没有祖先了,所以也不存在祖先的祖先
if(dp[i][depth-1]==-1) dp[i].push_back(-1);
//状态转移方程
else dp[i].push_back(dp[dp[i][depth-1]][depth-1]);
if(dp[i][depth]!=-1) allover=false;
}
if(allover) break;
}
构造完成后,我们可以从数组中直接读取每个结点的第1、2、4、8…个祖先了,下面开始搜索
int getKthAncestor(int node, int k) {
if(k==0) return node;//node的第0个祖先,说明是它自己
if(node==-1) return -1;//node本身是-1,它也没有祖先
int i=0;
//判断k的第i位是否为1,对应的是node的第2^i个祖先,位置是dp[node]数组的第i个元素
while(((1<<i)&k)==0) i++;
if(i>=depth) return -1;//如果i过大,直接返回-1
return getKthAncestor(dp[node][i],(1<<i)^k);//修改k后向下一层寻找
}
参考资料:
https://cp-algorithms.com/graph/lca_binary_lifting.html
https://leetcode-cn.com/problems/kth-ancestor-of-a-tree-node/solution/li-kou-zai-zhu-jian-ba-acm-mo-ban-ti-ban-shang-lai/