当前位置:   article > 正文

LCA总结_lca复杂度

lca复杂度

LCA(Least Common Ancestors) ,即最近公共祖先,在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先。

LCA可以分为在线算法和离线算法

  • 在线算法:指程序可以以序列化的形式一个一个输入,也就是说一开始并不知道所有输入。
  • 离线算法:指一开始就知道问题的所有数据,而在解决问题后立即输出结果。

 

 

1.LCA倍增算法 (在线)

时间复杂度:O((n+q)logn) 

转自https://www.cnblogs.com/FuTaimeng/p/5655616.html

倍增算法可以在线求树上两个点的LCA,时间复杂度nlogn

预处理:通过dfs遍历,记录每个节点到根节点的距离dist[u],深度d[u]

init()求出树上每个节点u的2^i祖先p[u][i]

求最近公共祖先,根据两个节点的的深度,如不同,向上调整深度大的节点,使得两个节点在同一层上,如果正好是祖先结束,否则,将连个节点同时上移,查询最近公共祖先。

  1. void dfs(int u){
  2. for(int i=head[u];i!=-1;i=edge[i].next){
  3. int to=edge[i].to;
  4. if(to==p[u][0])continue;
  5. d[to]=d[u]+1;
  6. dist[to]=dist[u]+edge[i].w;
  7. p[to][0]=u; //p[i][0]存i的父节点
  8. dfs(to);
  9. }
  10. }
  11. void init(){
  12. for(int j=1;(1<<j)<=n;j++){
  13. for(int i=1;i<=n;i++){
  14. p[i][j]=p[p[i][j-1]][j-1];
  15. }
  16. }
  17. }
  18. int lca(int a,int b){
  19. if(d[a]>d[b])swap(a,b); //b在下面
  20. int f=d[b]-d[a];//f是高度差
  21. for(int i=0;(1<<i)<=f;i++){//(1<<i)&f找到f化为2进制后1的位置,移动到相应的位置
  22. if((1<<i)&f)b=p[b][i];//比如f=5(101),先移动2^0祖先,然后再移动2^2祖先
  23. }
  24. if(a!=b){
  25. for(int i=(int)log2(N);i>=0;i--){
  26. if(p[a][i]!=p[b][i]){//从最大祖先开始,判断a,b祖先,是否相同
  27. a=p[a][i]; b=p[b][i];//如不相同,a b同时向上移动2^j
  28. }
  29. }
  30. a=p[a][0];//这时a的father就是LCA
  31. }
  32. return a;
  33. }

 

 

2.Tarjan算法(离线)

时间复杂度:O(n+q) 

转自:https://blog.csdn.net/riba2534/article/details/77181121

首先来介绍一下 Tarjan 算法的基本思路:

  1. 任选一个节点为根节点,从根节点开始
  2. 遍历该点 u 的所有子节点 v ,并标记 v 已经被访问过
  3. 若 v 还有子节点,返回 2 ,否则下一步
  4. 合并 v 到 u 所在集合
  5. 寻找与当前点 u 有询问关系的点 e
  6. 若 e 已经被访问过,则可以确定 u、e 的最近公共祖先为 e 被合并到的父亲节点

 

 

我们以图示:

假设存在查询: LCA(T,3,4)、LCA(T,4,6)、LCA(T,2,1) 。

我们假设在如下树中模拟 Tarjan 过程

img

注意:每个节点的颜色代表它当前属于哪一个集合,橙色线条为搜索路径,黑色线条为合并路径。

img

当前所在位置为 u = 1 ,未遍历孩子集合 v = {2,5} ,向下遍历。

img

当前所在位置为 u = 2 ,未遍历孩子集合 v = {3,4} ,向下遍历。

img

当前所在位置为 u = 3 ,未遍历孩子集合 v = {} ,递归到达最底层,遍历所有相关查询发现存在 LCA(T,3,4) ,但是节点 4 此时标记未访问,因此什么也不做,该层递归结束。

img

递归返回,当前所在位置 u = 2 ,合并节点 3 到 u 所在集合,标记 vis[3] = true ,此时未遍历孩子集合 v = {4} ,向下遍历。

img

当前所在位置 u = 4 ,未遍历孩子集合 v = {} ,遍历所有相关查询发现存在 LCA(T,3,4) ,且 vis[3] = true ,此时得到该查询的解为节点3 所在集合的首领,即 LCA(T,3,4) = 2 ;又发现存在相关查询 LCA(T,4,6) ,但是节点 6 此时标记未访问,因此什么也不做。该层递归结束。

img

递归返回,当前所在位置 u = 2 ,合并节点 4 到 u 所在集合,标记 vis[4] = true ,未遍历孩子集合 v = {} ,遍历相关查询发现存在LCA(T,2,1) ,但是节点 1 此时标记未访问,因此什么也不做,该层递归结束。

img

递归返回,当前所在位置 u = 1 ,合并节点 2 到 u 所在集合,标记 vis[2] = true ,未遍历孩子集合 v = {5} ,继续向下遍历。

img

当前所在位置 u = 5 ,未遍历孩子集合 v = {6} ,继续向下遍历。

img

当前所在位置 u = 6 ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,4,6) ,且 vis[4] = true ,因此得到该查询的解为节点 4所在集合的首领,即 LCA(T,4,6) = 1 ,该层递归结束。

img

递归返回,当前所在位置 u = 5 ,合并节点 6 到 u 所在集合,并标记 vis[6] = true ,未遍历孩子集合 v = {} ,无相关查询因此该层递归结束。

img

递归返回,当前所在位置 u = 1 ,合并节点 5 到 u 所在集合,并标记 vis[5] = true ,未遍历孩子集合 v = {} ,遍历相关查询发现存在LCA(T,2,1) ,此时该查询的解便是节点 2 所在集合的首领,即 LCA(T,2,1) = 1 ,递归结束。

至此整个 Tarjan 算法便结束。

 

代码:

  1. void find(int x)
  2. {
  3. if(pre[x]!=x)
  4. pre[x]=find(pre[x]);
  5. return pre[x];
  6. }
  7. void union1(int x,int y)
  8. {
  9. int fx,int fy;
  10. if(fx!=fy)
  11. {
  12. pre[fx]=ty;
  13. }
  14. }
  15. void Tarjan(int u,int fa)
  16. {
  17. for(int i=head[u];i!=-1;i=side[i].next)
  18. {
  19. int v=side[i].id;
  20. if(v==fa) continue;
  21. Tarjan(v,u);
  22. union1(u,v);
  23. }
  24. book[u]=1;
  25. for(int i=0;i<v[u].size();i++)//v[u][i]表示查询u与v[u][i]的LCA
  26. {
  27. if(book[v[u][i]])
  28. printf("%d 和 %d 的LCA是 %d\n",u,v[u][i],find(v[u][i]));
  29. }
  30. }

 

 

3.DFS + ST 算法(在线)

时间复杂度:O(nlogn)

//带填坑

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/455178
推荐阅读
相关标签
  

闽ICP备14008679号