最近公共祖先

本文深入分析了最近公共祖先(LCA)问题的解决方法,包括针对普通二叉树和二叉查找树的算法,以及离线算法如Tarjan算法的高效实现。重点介绍了LCA问题在计算机科学领域的广泛应用场景,特别强调了在线算法与离线算法的区别及各自优势。此外,文章还详细解释了如何利用并查集优化LCA问题的求解过程,最终通过代码实例展示了算法的具体实现。

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

主要是按照offer上的例子,

1.若是二叉搜索树

大小就可以判定

2.若是节点有指针指向父节点

转换成链表找公共节点第一个

3.若是一般的树,无多余指针,不止二叉

先是找到两条path,然后从paths中找到第一个两个节点相等,但是下一个不等的位置

bool GetPath(TreeNode* root, TreeNode* node, vector<TreeNode* >& path )
{
	if(root == node)
		return true;

	path.push_back(root);
	bool found = false;
	vector<TreeNode* >:: iterator it = root->children.begin();
	while(!found && it != root->children.end())
	{
		GetPath(*it,node,path);
	}
	if(!found)
		path.pop_back();
	return found;
}


另有方法:

node* getLCA(node* root, node* node1, node* node2)  
{  
    if(root == null)  
        return null;  
    if(root== node1 || root==node2)  
        return root;  
  
    node* left = getLCA(root->left, node1, node2);  
    node* right = getLCA(root->right, node1, node2);  
  
    if(left != null && right != null)  
        return root;  
    else if(left != null)  
        return left;  
    else if (right != null)  
        return right;  
    else   
        return null;  
}  

一次询问O(N),两次2*O(N)

然不论是针对普通的二叉树,还是针对二叉查找树,上面的解法有一个很大的弊端就是:如需N 次查询,则总体复杂度会扩大N 倍,故这种暴力解法仅适合一次查询,不适合多次查询。

    接下来的解法,将不再区别对待是否为二叉查找树,而是一致当做是一棵普通的二叉树。总体来说,由于可以把LCA问题看成是询问式的,即给出一系列询问,程序对每一个询问尽快做出反应。故处理这类问题一般有两种解决方法:

  • 一种是在线算法,相当于循序渐进处理;
  • 另外一种则是离线算法,如Tarjan算法,相当于一次性批量处理,一开始就知道了全部查询,只待询问。

4.更快的算法Tarjan离线算法

    最近公共祖先lca 离线算法/Tarjan算法
    
    方法举例说明:
                1
               / \
              2   3
             / \
            4   5
           /   /
          7   8
         / 
        9  
    查询(4,9):到4时,由于vis[9]=0,所以继续;到9后,最近公共祖先就是f[4]=4(回溯的时候记录父节点);
    查询(9,8):深度优先搜索,所以到8时才询问;要到8必须回溯到2,在进到8这个子树,所以以记录f[9]=7;f[7]=4;f[4]=2;f[2]=2;所以find(9)=2;
    查询(8,3):跟上条相似,必须回溯1才能到3,而find(8)=1就是最近公共祖先;
    我们可以发现,对于查询的两点,都要在先查询到的点开始,回溯到最近公共祖先,才查询相对应的点。这也就是离线算法的思路了。。这是我自己理解的。。
    
    下面是专业说明,反正我是没看懂。。
    
    利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。
    Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,
    再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。
    其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。
    之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,
    同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,
    且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,
    而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。

#include<iostream>
#include<vector>
using namespace std;
vector< int> f;//每个节点所属集合
vector< int> r;//每个节点的秩
vector< int> indegree;//每个节点的入度
vector<int> visit;//只有0,1表示节点是否已处理完毕
vector<vector<int> > tree,Qes;//前者是树的边集合,后者是查询节点集合
vector<int> ancestor;//祖先集合

 void init(int n)
 {
	vector<int> temp;
	f.clear();
	r.clear();
	indegree.clear();
	visit.clear();
	ancestor.clear();
	if(tree.size() != 0)
	{
		
		int x = tree.size()-1;//第二个错误点,size_t是无符号整型,所以不能表示-1,x>=0在实际-1处任然执行
		for(; x >= 0 ; -- x)
			tree[x].clear();//tree是vector<vector<int> >所以除了内部clear(),外部也clear();
		tree.clear();
	}

	if(Qes.size() != 0)
	{
		int x = Qes.size()-1;
		for(; x >= 0 ; -- x)
			Qes[x].clear();
		Qes.clear();
	}

	for(int i = 0; i <= n; ++ i)
	{
		f.push_back(i);
		r.push_back(1);
		indegree.push_back(0);
		visit.push_back(0);
		tree.push_back(temp);
		Qes.push_back(temp);
		ancestor.push_back(i);
	}
 }

 int find(int n)
 {
	 if(n == f[n])
		 return n;
	 else
		 f[n] = find(f[n]);
	 return f[n];
 }

 int Union(int x, int y)
 {
	 int a = f[x];
	 int b = f[y];
	 if(a == b)
		 return 0;
	 else if(r[a] < r[b])
	 {
		 f[a] = b;
		 r[b] += r[a];
	 }
	 else 
	 {
		 f[b] = a;
		 r[a] += r[b];
	 }
	 return 1;
 }

void LCA(int u, vector<int> &res)//Tarjan
{
	for(size_t i = 0; i < tree[u].size(); ++ i)
	{
		LCA(tree[u][i],res);//dfs,后序
		Union(u, tree[u][i]);
		ancestor[find(u)] = u;//每次Union更新的都是集合的代表,也就是会按照秩形成并查集的树,需要在每次对子树进行处理后更新他的代表的祖先为根节点
	}

	visit[u] = 1;

	for(size_t i = 0; i < Qes[u].size(); ++ i)
	{
		if(1 == visit[Qes[u][i]])
			res.push_back(ancestor[find(Qes[u][i])]);
	}
}

int main()

{
	int testnum = 0;
	cin>>testnum;//输入测试数目
	vector<int> results;
	while(testnum -- )//进入第一个测试的输入集合
	{
		int nodeNum = 0;
		cin>>nodeNum;//输入测试的节点个数
		init(nodeNum);
		int i=0, j=0, k = nodeNum-1;//第一点错误nodeNum因为下面还要使用所以先应该赋值给一个临时变量
		while(k--)//依次输入条边
		{
			cin>>i>>j;
			tree[i].push_back(j);
			++ indegree[j];
		}
		cin>>i>>j;//输入要判断最近公共祖先的两个节点
		Qes[i].push_back(j);
		Qes[j].push_back(i);
		
		for(int x = 1; x <= nodeNum; ++x )
		{
			if(0 == indegree[x])//入度为0是根节点
			{
				LCA(x, results);//因为最后输出所有结果,results保存中间结果
				break;
			}
		}
	}
	for(size_t x = 0; x < results.size(); ++ x)
		cout<<results[x]<<endl;

}

5. RMQ区间树见july

http://blog.youkuaiyun.com/v_july_v/article/details/18312089

内容概要:该研究通过在黑龙江省某示范村进行24小时实地测试,比较了燃煤炉具与自动/手动进料生物质炉具的污染物排放特征。结果显示,生物质炉具相比燃煤炉具显著降低了PM2.5、CO和SO2的排放(自动进料分别降低41.2%、54.3%、40.0%;手动进料降低35.3%、22.1%、20.0%),但NOx排放未降低甚至有所增加。研究还发现,经济性和便利性是影响生物质炉具推广的重要因素。该研究不仅提供了实际排放数据支持,还通过Python代码详细复现了排放特征比较、减排效果计算和结果可视化,进一步探讨了燃料性质、动态排放特征、碳平衡计算以及政策建议。 适合人群:从事环境科学研究的学者、政府环保部门工作人员、能源政策制定者、关注农村能源转型的社会人士。 使用场景及目标:①评估生物质炉具在农村地区的推广潜力;②为政策制定者提供科学依据,优化补贴政策;③帮助研究人员深入了解生物质炉具的排放特征和技术改进方向;④为企业研发更高效的生物质炉具提供参考。 其他说明:该研究通过大量数据分析和模拟,揭示了生物质炉具在实际应用中的优点和挑战,特别是NOx排放增加的问题。研究还提出了多项具体的技术改进方向和政策建议,如优化进料方式、提高热效率、建设本地颗粒厂等,为生物质炉具的广泛推广提供了可行路径。此外,研究还开发了一个智能政策建议生成系统,可以根据不同地区的特征定制化生成政策建议,为农村能源转型提供了有力支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值