LCA 最近公共祖先

首先,介绍何谓“最近公共祖先”,其实就是对于一颗二叉或者多叉树来说,每个节点都有祖先节点(根节点除外),对于任意两个点,a,b,它们可能有多个公共的祖先点c,即c为a的祖先且c为b的祖先,我们定义深度最大的那个公共祖先C为a,b的最近公共祖先,这个点是唯一的。

对于求最近公共祖先的算法有不少,著名的是LCA的在线算法和离线算法,看了这么多网上的代码,很少能找到我中意的,而且有些在线算法时间复杂度实在太高,因此我觉得有必要把自己想的一些东西拿出来给大家分享分享。

我们想想普通的方法来求a,b两个点的最近公共祖先C:

View Code
int LCA(int a,int b)//求a,b的LCA
{
    while(a!=b)//找到a,b的LCA
    {
        if(p[a].deep>p[b].deep)
        {
            a=p[a].father;
        }
        else
        {
            b=p[b].father;
        }
    }
    return a;
}

意思就是一步一步向上寻找a,b点的父节点,知道找到他们的父节点相同,那么此时的点就是点C;但是如果这棵树很大,那么这个算法实在太慢了,因此我们就优化这个地方。

我们来看看这样一棵树:

    0              -------deep=0

          /  \

        1     2          -------deep=1

      /   \

    3      4              -------deep=2

          /    \ 

        5       6          ------deep=3

                   \

                      7     -------deep=4

我们以深度为界限把数分段,比如我把深度为0和1的点作为第一段,深度2和3的点作为第二段,然后查看a和b是否在同一段,若不是同一段,则向上一段去寻找,直到a和b为同一段,然后再依照父节点去寻找最近公共祖先C:

View Code
int LCA(int a,int b)//求a,b的LCA
{
    while(p[a].sec!=p[b].sec)//找到a,b亮点所在的段
    {
        if(p[a].deep>p[b].deep)
        {
            a=p[a].sec;
        }
        else
        {
            b=p[b].sec;
        }
    }
    while(a!=b)//找到a,b的LCA
    {
        if(p[a].deep>p[b].deep)
        {
            a=p[a].father;
        }
        else
        {
            b=p[b].father;
        }
    }
    return a;
}

代码中p[u].sec为点u所归属的段,也就是归属的集合,也就是我们常说的并查集来分段。具体怎么分,方法有很多,可以以一个分叉点到下一个分叉点之间的所有点作为一个集合,而且网上大多数算法都是这么做的,这样做起来最好的时间复杂度是o(lgn),但是最复杂的情况将是o(n)。在这里我介绍一种特殊的分段方法,最好和最复杂的时间复杂度都是o(sqrt(n)),即依照节点的深度deep来分段,每一段的长度为sqrt(max(deep)),比如一棵树最大深度为100,那么深度为1~9的点为集合0,深度10~19的点为集合1,依次类推。

补充:

当然如果我们这样来分段,在下面的代码中:

p[u].deep%sec==0;时,我们应该把p[u].sec=p[p[u].father].sec+1;但是这样子的复杂度还是有点高,为了让复杂度再降低,我们把段分的更多,也就是当p[u].deep>=sqrt(max(deep))的时候,我们把段标记为它的父节点,这样深度相同的节点也可以在不同的段里面,可以让我们更快的查询最近公共祖先。(这里感谢一楼的提醒,一开始忘记说了)

 

View Code
void setsection(int u,int sec)//构建点u属于的段集合,每一段深度为sec
{
    if(p[u].deep<sec)
    {
        p[u].sec=0;
    }
    else
    {
        if(p[u].deep%sec==0)
        {
            p[u].sec=p[u].father;
        }
        else
        {
            p[u].sec=p[p[u].father].sec;
        }
    }
    for(unsigned i=0;i<p[u].next.size();i++)
    {
        int k=p[u].next[i];
        if(p[k].father==u)
        {
            setsection(k,sec);
        }
    }
}

说到这里,整个LCA的算法也就差不多讲完了,并查集分段的方法无外乎这两种,我的代码是后者,毕竟还是比较好理解的,如果不懂也可以留言,下面我把全部的代码都贴上来。

我们一开始给的是一个图,但是我们要把这个图变为一棵树,以任意节点作为根节点建树(此处以点0为root),建树过程标记父节点和深度,然后给节点分段,最后可以做任意次数的查询:

步骤:1.为图建树,标记节点的父节点和深度

   2.为树上的节点分段,使用并查集

       3.直接查询(a,b)两个节点的LCA即可。

View Code
  1 //====================================================================
  2 //Name       :LCA最近公共祖先
  3 //Author     :hxf
  4 //copyright  :http://www.cnblogs.com/Free-rein/
  5 //Description:
  6 //Data       :2012.8.20
  7 //========================================================================
  8 #include<iostream>
  9 #include<algorithm>
 10 #include<stdio.h>
 11 #include<math.h>
 12 #include<string>
 13 #include<cstring>
 14 #include<vector>
 15 #include<stack>
 16 #include<queue>
 17 #define MAXN 1040
 18 #define inf 10100
 19 #define pi 3.141592653589793239
 20 using namespace std;
 21 struct Tree{
 22     vector<int> next;//子节点
 23     int father;//父节点
 24     int deep;//深度
 25     int sec;//属于的段
 26 }p[50050];
 27 int visit[50050];
 28 int maxdeep;//最大深度
 29 void dfs(int u)//构建多叉树,找到父节点和深度
 30 {
 31     visit[u]=1;
 32     for(unsigned i=0;i<p[u].next.size();i++)
 33     {
 34         int k=p[u].next[i];
 35         if(visit[k]==0)
 36         {
 37             p[k].deep=p[u].deep+1;
 38             p[k].father=u;
 39             dfs(k);
 40         }
 41     }
 42     maxdeep=max(maxdeep,p[u].deep);
 43 }
 44 void setsection(int u,int sec)//构建点u属于的段集合,每一段深度为sec
 45 {
 46     if(p[u].deep<sec)
 47     {
 48         p[u].sec=0;
 49     }
 50     else
 51     {
 52         if(p[u].deep%sec==0)
 53         {
 54             p[u].sec=p[u].father;
 55         }
 56         else
 57         {
 58             p[u].sec=p[p[u].father].sec;
 59         }
 60     }
 61     for(unsigned i=0;i<p[u].next.size();i++)
 62     {
 63         int k=p[u].next[i];
 64         if(p[k].father==u)
 65         {
 66             setsection(k,sec);
 67         }
 68     }
 69 }
 70 void preLCA()
 71 {
 72     maxdeep=0;
 73     dfs(0);
 74     setsection(0,(int)sqrt(maxdeep));//每一段深度为sqrt(maxdeep)
 75 }
 76 int LCA(int a,int b)//求a,b的LCA
 77 {
 78     while(p[a].sec!=p[b].sec)//找到a,b亮点所在的段
 79     {
 80         if(p[a].deep>p[b].deep)
 81         {
 82             a=p[a].sec;
 83         }
 84         else
 85         {
 86             b=p[b].sec;
 87         }
 88     }
 89     while(a!=b)//找到a,b的LCA
 90     {
 91         if(p[a].deep>p[b].deep)
 92         {
 93             a=p[a].father;
 94         }
 95         else
 96         {
 97             b=p[b].father;
 98         }
 99     }
100     return a;
101 }
102 int main()
103 {
104     int n;//n个节点
105     while(scanf("%d",&n)!=EOF)
106     {
107         for(int i=0;i<n;i++)
108         {
109             p[i].next.clear();
110             p[i].father=0;
111             p[i].deep=0;
112             p[i].sec=0;
113         }
114         for(int i=0;i<n-1;i++)
115         {
116             int x,y;
117             scanf("%d %d",&x,&y);
118             p[x].next.push_back(y);
119             p[y].next.push_back(x);//建边
120         }
121         memset(visit,0,sizeof(visit));
122         preLCA();//建树
123         ///////////////////////
124         int m;//做m次查询
125         scanf("%d",&m);
126         while(m--)
127         {
128             int a,b;
129             scanf("%d %d",&a,&b);
130             int lcaab=LCA(a,b);//得到a,b的LCA
131             printf("%d\n",lcaab);
132         }
133     }
134     return 0;
135 }

 

 

转载于:https://www.cnblogs.com/Free-rein/archive/2012/08/24/2654519.html

LCA——最近公共祖先 对于每个查询,从查询节点开始,向上遍历树,将遍历到的节点加入并查集中,直到找到一个已经在并查集中的节点,这个节点就是查询节点的最近公共祖先。当我们找到两个节点的路径时,我们可以比较路径中的节点,找到它们的最近公共祖先。树上倍增算法的核心思想是将每个节点的跳跃步长翻倍,以便在查询时能够快速跳到更高层的祖先节点。从两个路径的末尾开始,比较路径中的节点,直到找到它们的最近公共祖先。从根节点开始,进行深度优先搜索,记录每个节点的父节点和祖先节点。从根节点开始,进行深度优先搜索,记录每个节点的父节点。 阅读详情

相关推荐

Allegro中调整差分对等长的步骤介绍

【代码】Allegro中调整差分对等长的步骤介绍。

qq_30882617的博客 3833

多叉树最近公共祖先问题(LCA)

任务:设计一个算法,对于给定的树中两结点,返回它们的最近公共祖先 输入:第1行有一个正整数n,表示给定的树有n个结点。结点编号为1,2,3,...,n,编号为1的顶点是树根。接下来n行中,第i+1行描述了第i个结点的儿子情况。每行的第一个正整数k表示该结点有k个儿子,其后k个数中,每一个数表示其儿子结点的编号。当k=0时表示该结点为叶节点。 输入的第n+2行是一个正整数m,表示要计算最近公共祖...

Love_Irelia97的博客 5049

软件工程:数据字典

数据字典

永远怀着至诚之心拥抱世界 4275

最近公共祖先LCA算法

我是菜鸟ACMer(qwq),之前学图论板块的LCA算法做的一些笔记,适合小白,分享出来希望能帮助大家!

lwltql的博客 2529

别再只看图了!代谢组学OPLS-DA分析,R2Y和Q2Y到底怎么看才不踩坑?

本文深入解析代谢组学OPLS-DA分析中的关键指标R2Y和Q2Y,揭示如何避免过拟合陷阱。通过详细解读模型解释能力(R2Y)和预测能力(Q2Y),结合置换检验方法,提供完整的模型评估流程与实战建议,帮助研究者获得可靠的分析结果。

weixin_42638178的博客 196

vcenter server6.0上vmware inventory 、virtualvcenter等服务无法开启

描述:一天,vcenter访问不了,因此Windows远程到vcenter server上。相关服务未开启,开启virtualCenter server服务提示相关依赖服务未开启,于是开启依赖项服务Inventory服务报错。然后打算使用 C:\Program Files\VMware\vCenter Server\bin\servcivce-control来开启vmware服务 1....

Pespi_Co1a的博客 3069

c++最近公共祖先LCA

今天来介绍一个c++中的算法:最近公共祖先LCA。最近公共祖先是什么呢?就是给出在树上的两个节点,求出它们的最近公共祖先,也可以理解成深度最深的公共祖先。这个算法用处很大,比如我们可以用它来求任意两个节点的距离。具体后面会讲。i++){//i从1开始!= edge[now][i]){//注意!!now的儿子不能再回到now的父亲了h;i++, h >>= 1){//例如h = (1011)2,那么第一次要跳,并且跳2^1也就是2^0级。

zhaoniuniu0309的博客,刚刚开始写博文的新手,有不对的地方请谅解。 2048

9、眼底图像智能分析:糖尿病视网膜病变的自动化识别与分级系统

本博客介绍了一种基于深度学习的糖尿病视网膜病变(DR)自动化识别与分级系统。该系统通过视网膜图像的预处理、数据增强、集成模型的构建与优化,实现了对DR的高效识别与分级。识别系统采用二分类任务,准确率达到97.67%,而分级系统通过四分类策略取得了95.2%的准确率。系统在基层社区和医院中具有广泛的应用前景,为DR的早期筛查和精准诊断提供了有效工具。

pea55的博客 144

详解最近公共祖先(LCA)

详解最近公共祖先(LCA)

weq2011的博客 6028

具身大小脑全栈全网独家复现|LLM链式推理+边缘VLA感知+毫秒级运动小脑、优化人形步态僵硬运动滞后、打通思考-行走-操作全链路适配居家工业多场景落地

高层决策大脑(异步10Hz,GPU算力):轻量化机器人微调LLM+RhinoVLA边缘视觉大模型,负责自然语言链式工序拆解、全局环境语义感知、目标物体筛选、跨机器人动作适配,输出粗粒度运动目标,不介入底层伺服计算;依托View Registry实现异构机器人动作对齐,适配多形态机器人切换;中层调度小脑(硬实时1kHz,CPU算力)

qq_36130719的博客 255

【2025最新版】间接平差——以水准网平差为例 (python详细过程版)

水准网间接平差的python代码实现。博客长期更新,本文最新更新时间为:2025年5月20日。

点云侠的博客 1702

树上最近公共祖先(LCA)

算法,图论,LCA

m0_74376772的博客 2198

188、LLC谐振变换器的样机调试实战(EMI测试)

本文分享了LLC谐振变换器EMI调试的实战经验。作者通过300W电源案例,指出LLC的EMI问题主要来自谐振腔共模电流和死区振铃,并提出三板斧解决方案:优化功率回路布局、合理选择共模电感、正确配置X/Y电容。文章详细分析了谐振电容位置、MOSFET走线、变压器屏蔽等关键设计要点,并列举示波器探头、滤波器接地等常见误区。最后强调EMI调试70%依赖布局优化,建议预扫测试、数据记录并保留6dB余量。全文以实际案例贯穿,为LLC电源EMI设计提供了实用指导。

Jack010169的博客 57

LCA-最近公共祖先

最近公共祖先简称。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根的那个。为了方便,我们记某点集 的最近公共祖先为LCAU1​U2​...Un​或LCAs。

weixin_42238183的博客 1795

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

最近公共祖先LCA(算法篇)

本篇将会讲解朴素和在线倍增的最近公共祖先算法,当然还有离线的算法,但是因为种种原因,Moon就更新到这了,后期可能会回归选择更新,感谢大家的喜欢

hellmorning的博客 2448

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

LCA最近公共祖先问题详解

图论和树结构中最近公共祖先(Least Common Ancestor,LCA)是一个经典的问题,本文我将详细介绍LCA问题的常见求解算法、优化策略,并结合Java代码示例,带你全面掌握这一重要算法问题的求解方法

1493

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【C++】最近公共祖先 LCA

最近公共祖先 百科名片 最近公共祖先 Lowest Common Ancestors LCA 简单引入 对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。 红色的都是是A和B的公共祖先,但只有最近的C才是最近公共祖先。 LCA问题是树上的一个经典问题,在很多方面有着广泛的应用,比如求LCP(最长公共前缀),接下来我们就来介绍...

Ljnoit 3500

『图论』LCA 最近公共祖先

转载记得告诉千千哦~同步更新:https://www.dreamwings.cn/lca/4874.html概述篇LCA (Least Common Ancestors) ,即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先。 LCA 可分为在线算法与离线算法 在线算法:指程序可以以序列化的方式一个一个处理输入,也就是说在一开始并不需要知道所有的输入。 离

小坏蛋_千千 4845

P3379 【模板】最近公共祖先(LCA)

路径压缩的基本思想是,在执行查找操作时,将访问路径上的所有节点直接连接到根节点上。这样做的目的是在下一次查找操作时,能直接访问到根节点,从而减少路径长度,提高查找效率。如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。,分别表示树的结点个数、询问的个数和树根结点的序号。行,每行包含一个正整数,依次为每一个询问的结果。行每行包含两个正整数。行每行包含两个正整数。的最近公共祖先,故为。的最近公共祖先,故为。的最近公共祖先,故为。的最近公共祖先,故为。的最近公共祖先,故为。结点的最近公共祖先。

cc_even的博客 2078

【洛谷 P3379】最近公共祖先【LCA模板】

题目描述 题目 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。 输入格式 第一行包含三个正整数 N,M,SN,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。 接下来 N−1N−1N−1 行每行包含两个正整数 x,yx,yx,y,表示 xxx 结点和 yyy 结点之间有一条直接连接的边(数据保证可以构成树)。 接下来 MMM行每行包含两个正整数 a,ba, ba,b,表示询问 aaa 结点和 bbb 结点的最近公共祖先。 输出格式 输出包含 MMM 行,每行包含一个正整数

绫 1711

最近公共祖先(LCA)

先kluskal求出最小生成树,然后根据最小生成树建一张无向图,然后用倍增法写lca,在lca预处理的时候,将d1[i][k]、d2[i][k](i上跳2^k的过程中的所经过的边的最大值和次大值)预处理出来,对所有的非树边执行lca查询,在lca查询的时候,将两个到最近公共祖先的过程中经过的所有最大值和次大值存下来,然后找出总的最大和次大,进行计算,返回结果。这里我们对于一条非树边,它们的最短路径上所有的边的值都要加1,这里可以利用差分实现,即,两点的d各加1,lca的d减2.1.遍历过还没有回溯过的点。

m0_74120503的博客 1156

【图论】—— 最近公共祖先(LCA)

【图论】—— 最近公共祖先(LCA)

forever_bryant的博客 2367

最近公共祖先 (LCA)

5. 让两个点同时往上跳,一直跳到它们最近公共祖先的下一层。4. 先将两个点跳到同一层。

Heait的博客 937

Lca 最近公共祖先

最近公共祖先 目录 简介 实例算法   最近公共祖先(Least Common Ancestors)   LCA 编辑本段简介   对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深

robertin的专栏 1149

最近公共祖先LCA

最近公共祖先LCA 若两个节点的祖先相同,则叫该节点的公共祖先 简称LCA 显然,两个节点的LCA只有一个,且一定是两个点到根的路径中重复部分最下端的点 算法1: 求出每一个结点的深度 询问两个节点是否重合,若重合,则LCA已求出 否则,选择两个点中深度较大的点,并移动到他的父亲 重复执行2,3步 上图的执行流程如下: 两个指针位于3与9。 此时9节点深度较大,向上移动到它的父...

han_yiyang的博客 338
上一篇: 经典算法之《八皇后》
下一篇: Java代码获取属性文件(.properties)中的value值
badao7116
博客等级 码龄11年 0粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值