首页 > 编程语言 >[算法学习笔记] Tarjan LCA

[算法学习笔记] Tarjan LCA

时间:2023-06-24 11:12:32浏览次数:54  
标签:Tarjan LCA merge 算法 回溯 节点

在讲解之前,我们先来看一道模板题:Luogu P3379 最近公共祖先(LCA)

What is LCA

LCA,即最近公共祖先。什么意思呢,我们举个例子:
image
将就着看吧qwq

这棵树中,0为根节点。若规定\(LCA(x,y)\)为\(x,y\)的最近公共祖先,则\(LCA(5,6)=2;LCA(4,3)=1;LCA(5,3)=0\)。还有很多,这里不一一列举了。

最近公共祖先,顾名思义,是指两个节点最近的公共祖先。求解它有很多种方法,包括但不限于暴力,倍增,RMQ。这里主要介绍Tarjan算法。

Tarjan

  • Tarjan算法是一种离线算法,跑一遍就能将所有需求的LCA计算完,因此效率比较高。
  • Tarjan实现应用了DFS搜索树+并查集 (相信来看的都懂吧

算法步骤
1.从根节点开始搜索树,如果搜到的点还有子节点则继续搜(DFS)

2.若搜到的节点没有子节点或子节点已经搜索过了,将父节点和子节点合并(并查集)

3.查找与当前节点\(now\)有询问关系的节点\(q_i\),若\(q_i\)已经回溯过则
\(LCA(now,q_i)=find(q_i)\) (\(find(q_i)\)指并查集操作,查找\(q_i\)的父节点)

需要注意,在STEP3中,\(q_i\)必须是已经回溯过而不是搜过,只有回溯过才有merge操作。(如果不理解可以先看下文模拟)

整个算法的过程非常巧妙,笔者水平有限,无法给出具体证明( 我太菜了ww),这里带着大家模拟一遍Tarjan流程吧,模拟完相信大家对Tarjan的理解就会更加深刻啦

模拟样例

我们还是模拟最先给出的那棵树:
image

为了简化题意,我们假设需求\(LCA(5,6)\)以及\(LCA(5,4)\)

显然,从0根节点开始DFS,先搜2,2还有子节点,先搜5,发现没有子节点。则merge(2,5)

此时,查找与5有询问关系的点:

  • 关系1:6号点。显然没有回溯过,不管
  • 关系2:4号点。显然也没有回溯过,不管
    此时回溯,返回2号,搜索6号。发现6没有子节点。则merge(2,6)

此时,查找与6有询问关系的点:

  • 关系1:5号点。已经回溯过,则\(LCA(5,6)=find(6)=2\)

此时回溯,返回0号,并且merge(2,0),搜索1号,1号有子节点,先搜4,merge(1,4)。
查找与4号有询问关系的点:

  • 关系1:5号点。已经回溯过,则\(LCA(4,5)=find(5)=0\)

显然还会继续搜索下去,和上边同理,相信大家对Tarjan算法已经有了深刻的理解,这里不继续模拟了。

具体实现

通过Tarjan,我们可以求出所有需要求的LCA,但是如何存储答案,去重,输出呢?

我们可以用vector存树(注意一定是双向边),另开一个vector存储询问顺序实现按顺输出+去重。当然去重也可以用map,只不过浪费空间。

我们来看一下Tarjan模板:

void tarjan(int now)
{
    vis[now] = 1; //标记已访问
    for(int i=0;i<v[now].size();i++) //这里使用了vector存树
    {
        if(!vis[v[now][i]]) //如果子节点没访问过
        {
            tarjan(v[now][i]); //dfs继续搜
            fa[v[now][i]] = now; //merge操作
        }
        
    }
    for(int i=0;i<q[now].size();i++) //查找与now有询问关系的点
    {
      int p =find(q[now][i]);
        if(vis[q[now][i]] == 2) //如果询问的点回溯过
        {
            ans[id[now][i]] = p; //id存答案编号,即按序输出
        }
    }
    vis[now] = 2;//所有操作完毕后标记now已回溯
}

至于刚开始给出的模板题,由于已经给出了Tarjan板子,其余部分自己实现一下吧!

标签:Tarjan,LCA,merge,算法,回溯,节点
From: https://www.cnblogs.com/SXqwq/p/17500828.html

相关文章

  • 代码随想录算法训练营第十六天| 找树左下角的值 路径总和 从中序与后序遍历序列
    找树左下角的值1,层序遍历,较为简单:1intfindBottomLeftValue_simple(TreeNode*root){2intresult=0;3if(!root)returnresult;4queue<TreeNode*>selected;5selected.push(root);67while(!selected.empty())8{9......
  • 快速排序算法
    快速排序(QuickSort)是一种常用的排序算法,它的时间复杂度为O(nlogn),是一种效率比较高的排序算法。但是快速排序不是稳定的排序算法。稳定排序算法是指,如果排序前两个元素相对顺序相同,那么排序后它们的相对顺序仍然相同。而快速排序并不保证相同元素的顺序不发生改变,所以它不是稳......
  • 21.动态规划算法
    人工智能时代,各国都在大力研究机器人技术,也制造出各种各样的机器人,比如:为了解决男女失衡而制造的美女机器人,假如你参与了某美女机器人的研发,你在这个项目中要求实现一个统计算法:如果美女机器人一次可以上1级台阶,也可以一次上2级台阶。求美女机器人走一个n级台阶总共有多少种走法。......
  • LLE算法在自然语言生成中的应用:实现文本到语音的情感迁移
    目录自然语言生成(NaturalLanguageGeneration,NLP)是人工智能领域的一个分支,它利用大量的文本数据训练出语言模型,从而实现对自然语言的生成和理解。在NLP中,情感迁移(EmotionTransfer)是一个重要的任务,它可以通过对已有文本的情感分析,将其转化为语音,从而实现文本到语音的情感迁......
  • 文心一言 VS 讯飞星火 VS chatgpt (45)-- 算法导论6.1 3题
    三、证明:在最大堆的任一子树中,该子树所包含的最大元素在该子树的根结点上。文心一言:在最大堆中,根结点是整个堆中最大元素的孩子,因此它包含的最大元素是在该子树的根结点上。我们可以通过递归的方式证明任意子树中的最大元素都在该子树的根结点上。假设我们已经证明了深度为d-......
  • 基础算法
    枚举枚举是一种常见的算法,通过各种技巧可以优化枚举次数。【POJ3061】Subsequence(尺取法):给定一个长度为\(n\)的数列\(a\),找到最短的区间\([l,r]\),使得\(\sum\limits_{i=l}^{r}a_i\geS\)。\(1\len\le10^5,1\leS\le10^8\)。这道题的朴素做法是枚举所有区间,......
  • 垃圾识别系统Python+TensorFlow+Django+卷积神经网络算法【完整代码系统】
    一、介绍垃圾识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对5种垃圾数据集进行训练,最后得到一个识别精度较高的模型。并基于Django,开发网页端操作平台,实现用户上传一张垃圾图片识别其名称。二、效果展示三、演示视频+代码视......
  • 算法
      #include<iostream>#include<bits/stdc++.h>usingnamespacestd;map<string,int>na_mo;intmain(){stringname[15],Zname,Pname;intn,mony,m;cin>>n;for(inti=0;i<n;i++){cin>>name[i];}......
  • 交通标志识别系统Python+TensorFlow+Django+卷积神经网络算法实现【完整代码】
    一、介绍使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Django,开发网页端操作平台,实现用户上传一张图片识别其名称。二、效果展示三、演示视频视频+完整代码:https://www.yuque.......
  • 基于深度学习的文本分类6大算法-原理、结构、论文、源码打包分享
    导读:文本分类是NLP领域一项基础工作,在工业界拥有大量且丰富的应用场景。传统的文本分类需要依赖很多词法、句法相关的human-extractedfeature,自2012年深度学习技术快速发展之后,尤其是循环神经网络RNN、卷积神经网络CNN在NLP领域逐渐获得广泛应用,使得传统的文本分类任务变得更加容......