最近公共祖先(LCA) 向上标记法 & 树上倍增法 & Tarjan算法

本文详细介绍了三种求最近公共祖先(LCA)的算法:向上标记法、树上倍增法和Tarjan算法。向上标记法通过从两个节点分别向上遍历至根节点,找到第一个共同标记的节点即为LCA;树上倍增法则利用预处理数组f和深度数组,通过二进制拆分思想将节点调整至同一深度,从而高效查找LCA;Tarjan算法采用并查集优化,实现线性时间复杂度。文章提供了详细的算法步骤和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 求最近公共祖先的算法

一.向上标记法

从x向上走到根节点, 并标记路径上经过的点
从y向上走到根节点, 当遇到第一个被标记的点就找到了LCA(x, y)

二.树上倍增发

时间复杂度: O(mlogn)
    用 f[x, k] 表示x向上走2^k到达的节点, 很容易知道 , f[x, 0] 就是x的父节点, 递推关系就是, f[x, k] = f[ f[x, k - 1], k - 1]
    用depth[x], 存x的深度

步骤
1.预处理 f 数组和 depth 数组
2.实现目标函数: 保证 depth[x] > depth[y], 如果不是, 就想换x, y的值
3.用二进制拆分的思想, 把 x 调到和 y 同一个深度
4.如果此时 x == y, 那么y就是他们的公共祖先
5. 如果不相等,就把x 和 y 同时往上调, 并且保证他们不会相遇
6. 这样我们到达的就是他们的LCA的子节点, 最后结果就是f[x, 0]

对应步骤的代码

1.预处理部分
用bfs实现, 深度就是当前节点的父节点深度+1, f 数组就是利用那个递推式实现, 这里的15是取值范围的最大值的位数, 由题目而定

void bfs()  //步骤1 预处理
{
    memset(depth, 0x3f, sizeof depth);
    depth[root] = 1;
    depth[0] = 0;
    queue<int> q;
    q.push(root);
    while(q.size())
    {
        int x = q.front();
        q.pop();
        for(int i = h[x]; ~i; i = ne[i])
        {
            int y = e[i];
            if(depth[y] > depth[x] + 1)
            {
                depth[y] = depth[x] + 1;
                q.push(y);
                f[y][0] = x;
                for(int j = 1; j <= 15; j++)
                    f[y][j] = f[f[y][j - 1]][j - 1];
            }
        }
    }
}

2.lca函数部分
这里是求 x, y 的LCA

int lca(int x, int y)
{
    
    if(depth[x] > depth[y])  //步骤2
        swap(x, y);
    
    for(int i = 15; i >= 0; i--)  // 步骤3
    {
        if(depth[f[y][i]] >= depth[x])
            y = f[y][i];
    }
    if(x == y) return x; //步骤4
    
    for(int i = 15; i >= 0; i--)  //步骤5
    {
        if(depth[f[x][i]] != depth[f[y][i]])
        {
            x = f[x][i];
            y = f[y][i];
        }
        
    }
    return f[y][0]; //步骤6 返回最后结果
}

三:Tarjan 算法

    时间复杂度:O(n + m)
    该算法的本质是用并查集对向上标记法进行优化, 达到线性的时间复杂度
在实现算法的过程中, 我们把树中的节点分为3类:

  1. 正在访问的节点, 以及它的到根节点这条路径上的节点, 标记为1
  2. 已经访问完毕, 并且已经回溯的节点吗,标记为2
  3. 还没有访问的节点, 不标记

由于这是一种离线算法, 我们需要把询问一次性全部读入, 然后再执行算法

3中节点解释
在这里插入图片描述
代码实现

void tarjan(int x)
{
    vis[x] = 1;
    for(int i = h[x]; ~i; i = ne[i])
    {
        int y = e[i];
        if(!vis[y])
        {
            tarjan(y);
            f[y] = x;
        }
    }
    
    for(int i = 0; i < q[x].size(); i++)
    {
        PII t = q[x][i];
        int y = t.first;
        int id = t.second;
        if(vis[y] == 2)
        {
            int father = find(y);
           //此时这个father就是LCA答案
        }
    }
    vis[x] = 2;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值