LCA算法-倍增算法

前言

LCA(lowest common ancestor)问题,是初学基础数据结构——二叉树时的经典例题,对于单次求最近公共祖先我们可以在O(n)的时间复杂度内完成,但是对于多次求任意两点的最近公共祖先,显然就不是那么容易了,对于LCA问题,我们一般有两种解法——倍增算法和Tarjan算法,本文来介绍倍增算法求解LCA问题。

tarjan算法求LCA见:LCA算法-tarjan算法


前置知识——二进制拆分

任一自然数可以拆成若干2的幂之和

证明:设p的二进制表示下从低到高第i位为ki(i = 0 , 1 , 2 ……),则
p = ∑ i = 0 n 2 i ∗ k i , k i = 0 或 1 p = \sum_{i=0}^{n} 2^i * ki,ki = 0或1 p=i=0n2ikiki=01
得证

这个性质,我们可以得出对于x和y,不妨设x < y,那么x可以通过加上若干个递增且互不相同的2次幂从而得到y

二进制拆分是倍增算法解决2次幂可划分递推问题的基础。

前置知识——ST表

关于ST表的详细原理见

之所以能用ST表就在于LCA问题其实也是一种满足结合律的可重复贡献问题

倍增算法求LCA

LCA的运算性质

  • 对于LCA(x,y)我们有
  • LCA(x,x) = x
  • LCA(x,y) = x,则x为y的祖先
  • 如果x不为y的祖先并且y不为x的祖先,那么x,y分别处于 LCA (x,y)的两棵不同子树中
  • dist(x,y) = depth(x) + depth(y) - 2 * depth(LCA(x,y))

算法原理

预处理

由于LCA问题仍是一种离线查询,所以我们可以通过ST表预处理出每个节点向上2^i层的祖先

仍然是区间划分的问题,我们定义fa[i][j]为节点i向上2^j次方层的祖先那么f[i][j]可以由长度减半的子区间转移过来(结合LCA的运算性质),即
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] f[i][j] = f[f[i][j-1]][j-1] f[i][j]=f[f[i][j1]][j1]
因为f[i][j-1]为i向上2(j-1)层的祖先,那么该祖先再向上2(j-1)层的祖先的祖先就是fa[i][j]为节点i向上2^j次方层的祖先

和我们ST表预处理区间最值一样,这个过程也需要O(nlogn)的时间复杂度。

预处理代码实现
//#define N 500010
//int fa[N][20]{0}, depth[N], n, m, s, u, v;
//vector<int> edges[N];
void dfs(int x, int father)
{
    depth[x] = depth[father] + 1;
    fa[x][0] = father;
    for (int i = 1; i < 20; i++)
        fa[x][i] = fa[fa[x][i - 1]][i - 1];
    for (auto y : edges[x])
        if (y != father)
            dfs(y, x);
}
//main
//dfs(root,0);//root为根节点
查询

预处理就是要简化查询的时间复杂度,在未预处理的情况下,我们单次查询最坏可以达到O(n)的时间复杂度,那么预处理之后呢?

对于任意两个节点x,y他们的位置情况无非三种关系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 第一种情况不再赘述

  • 第二种情况下,不妨设x为y的祖先,那么由于x、y间距离的二进制可拆性,y可以通过向上跳若干次2次幂层可以到达x或者x的下一层位置,也就是说y可以到达x的位置。

  • 第三种情况下,x,y分别在LCA(x,y)的两棵子树里,如果二者深度不同,不妨设depth(x) > depth(y),那么x可以向上跳到y的深度层,然后二者一同向上跳,二者一定会在LCA(x,y)处相遇

其实向上查询的过程就是弥补二进制位的过程

查询代码实现

实现流程

  • 查询lca(x,y),取depth[x] >= depth[y]
  • 调整x到y的层次
  • 如果x == y,则直接返回
  • 否则,枚举调整层数,x和y一起向上调整,只要二者祖先节点不同就向上调
  • 最终x,y停留在lca(x,y)的孩子节点,fa[x][0]即为所求
int lca(int x, int y)
{
    if (depth[x] < depth[y])
        swap(x, y);
    for (int i = 19; i >= 0; i--)
        if (depth[fa[x][i]] >= depth[y])
            x = fa[x][i];
    if (x == y)
        return x;
    for (int i = 19; i >= 0; i--)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}

OJ链接

P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[P4211 LNOI2014] LCA - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
LCA(最近公共祖先)是指在一棵树中,找到两个节点的最近的共同祖先节点。而Tarjan算法是一种用于求解强连通分量的算法,通常应用于有向图中。它基于深度优先搜索(DFS)的思想,通过遍历图中的节点来构建强连通分量。Tarjan算法也可以用于求解LCA问题,在有向无环图(DAG)中。 具体来说,在使用Tarjan算法求解LCA时,我们需要进行两次DFS遍历。首先,我们从根节点开始,遍历每个节点,并记录每个节点的深度(即从根节点到该节点的路径长度)。然后,我们再进行一次DFS遍历,但这次我们在遍历的过程中,同时进行LCA的查找。对于每个查询,我们将两个待查询节点放入一个查询列表中,并在遍历过程中记录每个节点的祖先节点。 在遍历的过程中,我们会遇到以下几种情况: 1. 如果当前节点已被访问过,说明已经找到了该节点的祖先节点,我们可以更新该节点及其所有后代节点的祖先节点。 2. 如果当前节点未被访问过,我们将其标记为已访问,并将其加入到查询列表中。 3. 如果当前节点有子节点,我们继续递归遍历子节点。 最终,对于每个查询,我们可以通过查询列表中的两个节点的最近公共祖先节点来求解LCA。 需要注意的是,Tarjan算法的时间复杂度为O(V+E),其中V为节点数,E为边数。因此,对于大规模的树结构,Tarjan算法是一种高效的求解LCA问题的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EQUINOX1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值