首页 > 编程语言 >【算法】LCA

【算法】LCA

时间:2024-02-03 20:33:23浏览次数:27  
标签:结点 parent 祖先 算法 LCA 倍增

什么是LCA?

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点x和y最近的公共祖先。

三种算法

有三种算法可以求解LCA问题,分别为朴素算法、倍增算法和Tarjan算法。

朴素算法

倍增算法和Tarjan算法都在建立在朴素算法的思想下,因此,了解朴素算法的思想有助于更好的理解进阶算法。
朴素算法前置知识:邻接表,dfs。
假设我们要寻找某两个节点x和y的LCA,那么我们肯定是让深度更深的那个结点跳到另一个结点深度处,然后再让这两个结点一起向上跳,直到首次相遇。
光说可以有些抽象,举个例子,就以下面这张图中的树为例。
图中结点4先跳到结点3的位置,然后两个结点一起向上跳,随后跳到结点1处相遇,所以结点2和结点4的LCA为结点1。
也就是说,我们只要记录下每个结点的深度信息和祖先信息,就能通过逐个向上跳跃直至相遇来确定两个结点的LCA。
这便是LCA朴素算法的核心支撑所在,但是由于朴素算法每次跳跃一层,因此他的时间复杂度很差,尤其是当树退化为链的时候,那么如果我们让他跳跃多层,是不是可以更好更快的解决问题呢?答案是一定的,而接下来的倍增算法就是这个思想。

倍增算法

倍增算法前置知识:邻接表,DP&倍增,dfs。
倍增算法我们将会定义一个数组fa[i][j]表示结点i向上跳2j层所到的结点,从而实现了倍增跳跃,而且,通过有限的组合跳跃,我们到达任意结点处。例如向上5层,我们可以先向上跳跃22层之后,再向上跳跃2^0次方。同时,我们可以证明又fa[x][i] = fa[fa[x][i-1]][i-1],实质就是2^(i-1) + 2^(i-1) = 2^i.
同时,倍增LCA算法中还用到了贪心的思想,假如现在有两个结点x,y,假设x深度更大,则我们要尽可能地让x在不超过y的深度的前提下,尽可能地接近y,也就是跨的步子尽可能大!这样操作过后,结点x与结点y的深度就一定相同了。
相同之后,如果已经重合,直接return,如果没有,那么现在两个结点处于一个平行的位置。接下来我们让两个结点同时向上跳,也是能跳多大就跳多大,但是肯定是有限制的,就像上一步一样,这个限制就是只有在跳完之后他们结点不重合时才跳。这个地方有点绕,不要急,我们结合代码看一下。

点击查看代码
int lca(int u, int v) {
    if(depth[u] < depth[v]) swap(u, v);
    for(int i = MAXLOG - 1; i >= 0; i--) {
        if(depth[u] - (1 << i) >= depth[v]) {
            u = parent[u][i];
        }
    }
    if(u == v) return u;
// 注意看这里,这一步是平行之后的操作
    for(int i = MAXLOG - 1; i >= 0; i--) {
        if(parent[u][i] != parent[v][i]) {
            u = parent[u][i];
            v = parent[v][i];
        }
    }
    return parent[u][0];
}
我们注意到,在平行之后,只要在parent[u][i] != parent[v][i]的前提下尽可能地大跨步就能保证跳到最近的公共祖先的前一个位置,这个位置上面一层,也就是2^0方层就是公共祖先,同时也是最近的公共祖先。

Tarjan算法

Tarjan算法前置知识:邻接表,并查集,dfs。
Tarjan巧妙地将并查集和dfs结合在一起,实现了将单次查询地时间复杂度降到了常数级别的离线算法!
具体步骤如下:

  1. 对于每个节点u,初始化u的祖先为u本身,并标记u为已访问。
  2. 对于每条边(u,v):
    • 如果v未被访问,则对v进行深度优先搜索,并将v的祖先设置为u。
    • 如果v已经被访问,那么u和v的最近公共祖先就是u的祖先和v的祖先的最小值。
  3. 在查询LCA问题时:
    • 对于每个查询(q, u, v),其中q为查询编号,u和v分别为查询的两个节点:
      • 如果(u,v)已经被访问过,那么查询结果为(u,v)的最近公共祖先。
      • 如果(u,v)未被访问过,那么查询结果为(u,v)的最近公共祖先的祖先。

总结

离线用Tarjan,在线用倍增。:)

标签:结点,parent,祖先,算法,LCA,倍增
From: https://www.cnblogs.com/zc-study-xcu/p/18005160

相关文章

  • AES算法:数据传输的安全保障
    在当今数字化时代,数据安全成为了一个非常重要的问题。随着互联网的普及和信息技术的发展,我们需要一种可靠的加密算法来保护我们的敏感数据。AdvancedEncryptionStandard(AES)算法应运而生。本文将介绍AES算法的优缺点、解决了什么问题以及在哪些方面可以应用。AES(Rijndael......
  • 地铁最优线路算法的求解(三)-深度优先搜索java实现
    多的不说,showmethecode,先上一段java代码1/*2*深度优先算法(DFS)算法生成所有可能路径3*startId:出发站4*endId:到达站5*graph:辅助邻接矩阵,若99站与35站相邻,6*则graph[35][99]=1,graph[99][35]=17*8*......
  • Python数据结构与算法06——树与树算法
    二叉树classNode(object):def__init__(self,val,lchild=None,rchild=None):self.val=valself.lchild=lchildself.rchild=rchildclassTree(object):def__init__(self):self.root=Nonedefadd(self,item):no......
  • [算法学习笔记] 欧拉路
    免责声明:本文定义并不严谨,笔者是从“浅显易懂”的角度出发写本文。若您需要严谨定义请移步至其他学术文章。基本定义欧拉路径,即能不重不漏经过图上所有边的路径。也可以说“一笔画问题”。特殊地,如果这条路径的起点和终点一致,则这条路径叫做“欧拉回路”。其他的定义:欧拉图......
  • 基础算法(十一)二维差分---以题为例
    输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1)和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c。请你将进行完所有操作后的矩阵输出。输入格式第一行包含整......
  • 代码随想录算法训练营第十一天| 20. 有效的括号 1047. 删除字符串中的所有相邻重复
    20.有效的括号 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类型的左括号。题目链接:20.有效的括号-力扣(LeetCode)思路:只......
  • 2024牛客寒假算法基础集训营1
    题目链接A.因为判断要素较少,直接条件模拟#include<bits/stdc++.h>usingnamespacestd;#defineintlonglongconstintN=2e5+10;voidsolve(){intn;cin>>n;strings;cin>>s;intD=0,F=0,S=0,d=0,f=0,ss=0;for(inti=0;i<s.size();i++){......
  • 算法入门:排序算法
    文章目录1.冒泡排序2.选择排序3.插入排序4.希尔排序5.归并排序6.快速排序7.堆排序 1.冒泡排序思想:比较相邻元素:从数组的第一个元素开始,比较相邻的两个元素,如果它们的顺序不对(比如前面的元素大于后面的元素),则交换它们的位置。一轮遍历:一轮比较和可能的交换后,最......
  • 基础算法(十)差分模板---以题为例
    输入一个长度为 n的整数序列。接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r]之间的每个数加上 c。请你输出进行完所有操作后的序列。输入格式第一行包含两个整数 n 和 m。第二行包含 n 个整数,表示整数序列。接下来 m 行,每行包含三个整数 l,r......
  • 抢红包随机金额算法(均衡随机)
    最优算法在文末,欢迎参考。编写抢红包随机算法功能,通常金额是红包支付后立马算好的,而不是抢一个实时随机一个红包金额,避免并发情况下降低性能。需求仿照微信发红包功能,现有n个人抢金额为m的红包,m>=0.01,n>0,m/n不能小于0.01,需保证每个人都能抢到最低为0.01的金额,金额随机,但金额相......