最近公共祖先(朴素法、倍增法、(递归法))

news2025/7/20 9:35:14

目录

一、前言

二、题一:二叉树的最近公共祖先

1、上链接

2、基本思路

(1)朴素法

(2)LCA倍增法。

3、朴素法代码

(1)C++(AC)

(2)python(29/31,时间超限了但算法正确性是可以保证的)

4、LCA倍增法

(1)C++(不提交,因为不想补全力扣给的入口函数,但是能保证算法正确性)

5、其他递归法

三、题二:【模板】最近公共祖先(LCA)

1、上链接

2、基本思路

3、倍增法代码

(1)C++ (AC)

(2)python(70分)


一、前言

又是一个复习的算法题目,下面直接看题吧。

二、题一:二叉树的最近公共祖先

1、上链接

236. 二叉树的最近公共祖先 - 力扣(Leetcode)

2、基本思路

有两种方法可解。

(1)朴素法

假设给定两个节点p,q,那么我们应该找出两条路径并存起来,路径是从根节点到给定节点,怎么找呢?利用栈进行递归查找即可。

(2)LCA倍增法。

为什么倍增法可行,因为一个整型数永远可以化成一个二进制数(二进制转十进制)。

3、朴素法代码

(1)C++(AC)

顺便也复习了一下怎么建树。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

///**
// * Definition for a binary tree node.
// */
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
	int* nums;
public:
	Solution(){
		nums=new int[11];
		int n[]={3,5,1,6,2,0,8,-1,-1,7,4};
		for(int i=0;i<11;++i)
			nums[i]=n[i];

//		for(int i=0;i<11;++i)
//			cout<<nums[i]<<" ";
//		cout<<endl;
	}

	void display(TreeNode* node){
		if(node){
			display(node->left);
			cout<<node->val<<endl;
			display(node->right);
		}
	}

	TreeNode* createTree(int idx){
		if(idx>10)
			return NULL;
		//cout<<idx<<endl;
		int num=nums[idx];
		TreeNode* t;
		if(num==-1){
			t = NULL;
		}else{
			t=new TreeNode(num);
			t->left=createTree(2*idx+1);
			t->right=createTree(2*idx+2);
		}
		return t;
	}

	void dfs_search(TreeNode* node,TreeNode* target,vector<TreeNode *> &stack,vector<TreeNode*> &path){
		if(node==NULL)
			return;
		stack.push_back(node);
//        cout<<node->val<<endl;
		if(node->val==target->val){
//			path=stack;
			path.assign(stack.begin(),stack.end());
			return;
		}
//        cout<<"2"<<endl;
		dfs_search(node->left,target,stack,path);
		dfs_search(node->right,target,stack,path);
		stack.pop_back();
	}

	//朴素法  用时击败16.68%,内存击败7.59%
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode *> p_path;
        vector<TreeNode *> q_path;
        vector<TreeNode *> stack;

        dfs_search(root,p,stack,p_path);
        stack.clear();
        dfs_search(root,q,stack,q_path);
//
//        for(int i=0;i<p_path.size();++i)
//            cout<<p_path[i]->val<<" ";
//        cout<<endl;
//        for(int i=0;i<q_path.size();++i)
//            cout<<q_path[i]->val<<" ";
//        cout<<endl;

        TreeNode* common;
        int i=0;
        while(i<p_path.size() && i<q_path.size()){
        	if(p_path[i]==q_path[i]){
        		common=p_path[i];
			}
			i++;
		}
		return common;
    }
};

int main(){
	Solution Tree;
	TreeNode* root=Tree.createTree(0);
//	Tree.display(root);
	int p,q;
	cin>>p>>q;
	TreeNode* pp=new TreeNode(p);
	TreeNode* qq=new TreeNode(q);

	TreeNode* ans=Tree.lowestCommonAncestor(root,pp,qq);
	cout<<ans->val<<endl;

	return 0;
}

(2)python(29/31,时间超限了但算法正确性是可以保证的)

import copy

class TreeNode():   # python 没有结构体,但可以用类实现相同效果
    def __init__(self,val:int,left=None,right=None):
        self.val=val
        self.left=left
        self.right=right

def Create_Tree(node,vals,idx):
    if idx>10:
        return node
    if vals[idx]=='#':
        node=None
    else:
        node=TreeNode(int(vals[idx]))
        node.left=Create_Tree(node.left,vals,2*idx+1)  
        node.right=Create_Tree(node.right,vals,2*idx+2)
    return node

def display(root):
    if root:
        display(root.left)
        print(root.val)
        display(root.right)

class Solution:
    def __init__(self):
        self.p_path=[]
        self.q_path=[]
        self.stack=[]

    def dfs_search(self,node:'TreeNode',target:'TreeNode',flg):
        if node==None:
            return
        self.stack.append(node)
        if node.val == target.val:
            if flg==0:
                self.p_path=copy.deepcopy(self.stack)
            else:
                self.q_path=copy.deepcopy(self.stack)
            return
        self.dfs_search(node.left,target,flg)
        self.dfs_search(node.right,target,flg)
        self.stack.pop()
    
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        self.dfs_search(root,p,0)
        self.stack.clear()
        self.dfs_search(root,q,1)

        common=TreeNode(None);
        i=0
        while i<len(self.p_path) and i<len(self.q_path):
            if self.p_path[i].val==self.q_path[i].val:
                common=copy.deepcopy(self.p_path[i])
            i+=1
        return common

   
if __name__=='__main__':
    node=None
    vals=list("3516208##74")
    root=Create_Tree(node,vals,0)
##    display(root)
    n1,n2=map(int,input().split())
    p=TreeNode(n1)
    q=TreeNode(n2)

    sol=Solution()
    ans=sol.lowestCommonAncestor(root,p,q)
    print(ans.val)
    
    
    

4、LCA倍增法

(1)C++(不提交,因为不想补全力扣给的入口函数,但是能保证算法正确性)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int N=10010;
int n,m,cnt=1;
struct Edge{
	int to,w,next;
}edge[N];
int head[N],dep[N],fa[N][N];

void Add(int u,int v){
	edge[cnt].to=v;
	edge[cnt].next=head[u];
//	edge[cnt].w=w;
	head[u]=cnt;
//	cout<<"对于"<<u<<" "<<v<<"这条边"<<cnt<<" "<<edge[cnt].next<<" "<<edge[cnt].to<<" "<<head[u]<<endl;
	cnt++;
}

//int k=0; 
void dfs(int v,int father){  //填深度表和跳步表 
	dep[v]=dep[father]+1;
	fa[v][0]=father;
	for(int i=1;i<=19;++i)
		fa[v][i]=fa[fa[v][i-1]][i-1];
	
	for(int i=head[v];i;i=edge[i].next){
		int p=edge[i].to;
//		cout<<k<<" "<<i<<" "<<p<<endl;
//		k++;
		if(p!=father)
			dfs(p,v);	
	}
}

int lca(int x,int y){
//	cout<<dep[0]<<" "<<dep[x]<<" "<<dep[y]<<endl;
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=19;i>=0;i--)   //先跳到同一层 
		if(dep[fa[x][i]]>=dep[y]){
			x=fa[x][i];
//			cout<<"* "<<fa[x][i]<<endl;
		}
			
//	cout<<"11"<<endl;
	if(x==y)
		return y;
//	cout<<"22"<<endl;
	for(int i=19;i>=0;i--){  //再跳到lca的下一层 
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i],y=fa[y][i];
		}		
	}
	return fa[x][0];
}


struct TreeNode {
	int val;
	TreeNode *left;
	TreeNode *right;
	TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Tree{
public:
	int nums[11] = {3,5,1,6,2,9,8,-1,-1,7,4};  //因为样例中有一个节点的值为0,导致dep[0]被更改了,所以调试了很久,这个样例我把0换成了9 
//	int nums[2]={1,2};
	TreeNode* createTree(int idx){
		if(idx>10)
			return NULL;
		TreeNode* t;
		if(nums[idx]==-1){
			t=NULL;
			return t;
		}else{
			t=new TreeNode(nums[idx]);
			t->left= createTree(2*idx+1);
			t->right=createTree(2*idx+2);
		}
		return t;
	}
	void display(TreeNode* node){
		if(node){
			display(node->left);
			cout<<node->val<<endl;
			display(node->right);
		}
	}
	void add_edge(TreeNode* node){ //链式前向星加边,当然也可以根据数组下标加边,这里建树再加边的操作略显复杂 
		if(node->left!=NULL){
			Add(node->val,node->left->val);
			Add(node->left->val,node->val);
			add_edge(node->left);
		}
		if(node->right!=NULL){
			Add(node->val,node->right->val);
			Add(node->right->val,node->val);
			add_edge(node->right);
		}	
	}		
};


int main(){
	Tree* t=new Tree();
	TreeNode* root=t->createTree(0);
//	t->display(root);
	t->add_edge(root);
//	cout<<root->val<<endl;
//	cout<<"dep[0]: "<<dep[0]<<endl;
	dfs(root->val,0);
	int x,y;
	cin>>x>>y;
	int ans=lca(x,y);
	cout<<ans<<endl;
	
	return 0;
} 

5、其他递归法

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL)
            return NULL;
        if(root == p || root == q) 
            return root;
            
        TreeNode* left =  lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
       
        if(left == NULL)
            return right;
        if(right == NULL)
            return left;      
        if(left && right) // p和q在两侧
            return root;
        
        return NULL; // 必须有返回值
    }
};

三、题二:【模板】最近公共祖先(LCA)

1、上链接

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

2、基本思路

为倍增法量身定做的一道题,下面用链式前向星+倍增法搞定。

3、倍增法代码

(1)C++ (AC)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int n,m,s; //树的结点个数、询问次数、树根节点的序号 
int x,y; //x和y之间有一条直接连接的边 
int a,b; //表示询问a和b的最近公共祖先 

const int N=5000100;
int cnt=1,head[N];
struct Edge{
	int to,next;
} e[N];

int dep[N],fa[N][22];

void Add(int x,int y){
	e[cnt].to=y;
	e[cnt].next=head[x];
	head[x]=cnt++;
}

void dfs(int u,int father){  //填深度表和跳步表 
	dep[u]=dep[father]+1; 
	fa[u][0]=father;
	for(int i=1;i<=19;++i)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	
	for(int i=head[u];i;i=e[i].next){
		int p=e[i].to;
		if(p!=father)
			dfs(p,u);
	}
}

int lca(int x,int y){
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=19;i>=0;i--)    //跳到同一层 
		if(dep[fa[x][i]]>=dep[y])
			x=fa[x][i];
			
	if(x==y)
		return y;
	
	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];
	
}

int main(){
	cin>>n>>m>>s;
	for(int i=0;i<n-1;++i){
		cin>>x>>y;
		Add(x,y);
		Add(y,x);
	} 
	dfs(s,0);
//	cout<<lca(4,5)<<endl; 
	for(int i=0;i<m;i++){
		cin>>a>>b;
		cout<<lca(a,b)<<endl; 
	}
	return 0;
} 

(2)python(70分)

N=500010
# 没办法,N=5000010会全部时间超限
cnt=1
head=[0]*N
next=[0]*N
to=[0]*N

dep=[0]*N
fa=[[0]*20 for _ in range(N)]

def Add(a,b):
    global cnt
    to[cnt]=b
    next[cnt]=head[a]
    head[a]=cnt
    cnt+=1

def dfs(u:int,father:int):   # 填深度表和跳步表
    dep[u]=dep[father]+1
    fa[u][0]=father
    for i in range(1,20):
        fa[u][i]=fa[fa[u][i-1]][i-1]

    i=head[u]
    while i:
        p=to[i]
        if p!=father:
            dfs(p,u)
        i=next[i]
    
def lca(x,y)->int:
    if dep[x]<dep[y]:
        x,y=y,x
    for i in range(19,-1,-1):    # 先跳到同一层
        if dep[fa[x][i]]>=dep[y]:
            x=fa[x][i]
    if x==y:
        return y
    for i in range(19,-1,-1):
        if fa[x][i]!=fa[y][i]:
            x,y=fa[x][i],fa[y][i]
    return fa[x][0]

n,m,s=map(int,input().split())
for i in range(n-1):
    x,y=map(int,input().split())
    Add(x,y)
    Add(y,x)
dfs(s,0)
for i in range(m):
    a,b=map(int,input().split())
    print(lca(a,b))

以上,最近公共祖先

祝好

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/17296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.2 极限的性质【极限】

1.2 极限的性质【极限】 1.2.1 唯一性 极限的唯一性 引入 假设警察逮捕罪犯&#xff0c;把犯人追到了悬崖边上&#xff0c;那么犯人只能在悬崖边束手就擒&#xff0c;这个时候悬崖边是犯人逃跑的极限位置&#xff0c;别无去处&#xff0c;位置唯一。 考试或比赛的时候都努…

web前端开发技术纯静态 (12306官网 1页)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#…

WebDriverManager自动管理浏览器Driver包

WebDriverManager是什么&#xff1f; WebDriverManager是一个开源 Java 库&#xff0c;它以全自动方式管理&#xff08;即下载、设置和维护&#xff09; Selenium WebDriver所需的驱动程序&#xff08;例如&#xff0c;chromedriver、geckodriver、msededriver 等&#xff09;…

【ASM】字节码操作 工具类与常用类 Printer、ASMifier、Textifier 介绍

文章目录 1.概述2. Printer2.1 class info2.2 fields2.3 constructors2.4 methods3. ASMifier与Textifier3.1 如何使用3.2 从命令行使用3.3 visit方法3.4 从代码中使用1.概述 在上一篇文章中:【ASM】字节码操作 工具类与常用类 TraceClassVisitor 介绍 我们知道了如何使用Tra…

nodejs+vue+elementui个人图书分享共享网站

本面向图书共享系统主要包括两大功能模块&#xff0c;即用户功能模块和管理员功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;首页、个人中心、图书分类管理、图书信息管理、用户管理、用户分享管理、联系我们、社区交流、系统管理。 &#xff08;2&#xff09;用…

多功能电子密码锁的设计与制作

目 录 第一章 绪论 1 1.1 课题背景和意义 1 1.2 多功能电子密码锁发展趋势 2 第二章 总体设计方案的确定 3 2.1 多功能电子密码锁设计的具体要求 3 2.2 总体设计方案选定 3 第三章 系统硬件设计 5 3.1 设计原理 5 3.2 单片机STC89C51简介 5 3.3 AT24C02存储芯片 9 3.4 LCD显示模…

《Deep learning Based Text Classification:A comprehensive Review》文本综述

介绍 深度学习综述年年有&#xff0c;今年特别多。随着深度学习在机器学习领域的快速发展&#xff0c;对每个任务进行算法的总结对于之后的发展是有益的。综述可以梳理发展脉络&#xff0c;对比算法好坏&#xff0c;并为以后的研究方向进行启发。本文是在NLP领域中重要的任务-…

KT148A语音芯片常见问题集锦|硬件|软件以及注意事项-长期更新

目录 1.1KT148A是什么&#xff1f; 具备哪些功能&#xff1f; 有什么特色&#xff1f; 1.2KT148A工作电压多少&#xff1f;电流是多少&#xff1f;控制方式是多少&#xff1f;支持多大的喇叭&#xff1f; 1.3KT148A我焊接到PCB板上面之后&#xff0c;为什么没有任何反应呢&a…

基于动态时间规整算法(DTW)的语音识别技术研究-含Matlab代码

⭕⭕ 目 录 ⭕⭕⏩ 一、引言⏩ 二、动态时间规整算法基本原理⏩ 三、语音识别实例分析⏩ 四、参考文献⏩ 五、Matlab代码获取⏩ 一、引言 在语音识别技术的发展过程中&#xff0c;动态时间规整算法&#xff08;Dynamic Time Warping&#xff0c;DTW&#xff09;一直处于重要地…

Mysql

1.约束 2.数据库设计 3.多表查询 4.事物 5.新增/删除/修改语句 6.单表条件查询 7.函数 8.创建数据库/数据表 执行顺序 第一步&#xff1a;from 指定要操作的表 第二步&#xff1a;join 连接表生成一个笛卡尔积 第三步&#xff1a;on 对笛卡尔积进行筛选 第四步&#xff1a…

Pytorch深度学习实战(1)—— 使用LSTM 自动编码器进行时间序列异常检测

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

并发编程面试题

并发 为什么要使用并发编程&#xff08;并发编程的优点&#xff09; 并发编程可以提升 CPU 的计算能力的利用率&#xff0c;通过并发编程的形式可以将多核CPU 的计算能力发挥到极致提升程序的性能&#xff0c;如&#xff1a;响应时间、吞吐量、计算机资源使用率等。并发程序可…

Golang 数组基础

数组内部实现和基础功能 了解Go的数据结构&#xff0c;一般会从数组开始&#xff0c;因为数组是切片和映射的基础数据结构。 内部实现 在Go语言中&#xff0c;数组是一个长度固定的数据类型&#xff0c;用于存储一段具有相同的类型的元素连续块。数组的类型是固定统一的&…

Vue框架插槽(第八课)

案例 组件信息的通信 自己思考一下 答案在本文章的后面插槽 v-slot 这个时候我们就可以来定义插槽slot&#xff1a; 插槽的使用过程其实是抽取共性、预留不同&#xff1b;我们会将共同的元素、内容依然在组件内进行封装&#xff1b;同时会将不同的元素使用slot作为占位&#xf…

CTC 技术介绍概述——啃论文系列

CTC 技术介绍概述——啃论文系列 文章目录CTC 技术介绍概述——啃论文系列自我介绍摘要前言知识导图1. 定义2. 诞生背景2.1 频谱紧张例子&#xff0c;wifi的5GHz2.2 通信干扰——CTI2.3 管理困难2.4 异构通信传统实现——网关桥接2.5 CTC——异构直接通信3. 包级CTC3.1 基于RSS…

个人设计web前端大作业 基于html5制作美食菜谱网页设计作业代码

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

基于PHP+MySQL长途客用汽车票订票系统的设计与实现

随着时代的变迁汽车已经成为了人们日常生活中不可或缺的一部分,虽然很多人已经拥有了私家车,但是很多时候因为离家较远等原因,很多时候人们还是会通过客用汽车来进行一些出行,但是通常情况下客用车票都需要到客用站进行购买,这极其的不便利。 为了能够让用户足不出户就可以进行…

拿稳这24道JVM面试题,要价30k都有底气

1.什么是JVM? JVM 的全称是 「Java Virtual Machine」&#xff0c;也就是我们耳熟能详的 Java 虚拟机。它能识别 .class后缀的文件&#xff0c;并且能够解析它的指令&#xff0c;最终调用操作系统上的函数&#xff0c;完成我们想要的操作。 C开发出来的程序&#xff0c;编译…

SpringBoot SpringBoot 开发实用篇 4 数据层解决方案 4.10 MongoDB 基础操作

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇4 数据层解决方案4.10 MongoDB 基础操作4.10.1 MongoDB 基础操作4.10…

(3)paddle---近视眼睛分类的例子

1主要参考 &#xff08;0&#xff09;本教程和以下教程不够详细&#xff0c;还是推荐下面这个大佬的blog看一下 计算机视觉——眼疾图片识别&#xff08;数据集iChallenge-PM&#xff09;_「已注销」的博客-CSDN博客 &#xff08;1&#xff09;blibli视频 252-06_预测病理性…