LCA 问题 用 Tarjan 离线算法 求解

 题目: http://poj.org/problem?id=1330   


  思 路: LCA (最近公共祖先问题),目的是求出一棵树中,任意两个节点的最近公共祖先。
            例:              
   
则 :        4 和 7 的最近公共祖先 是 4 ; 

                9 和 1 的最近公共祖先 是 8 ;

                3 和 2 的最近公共祖先 是 10 ; 


Tarjan 算法基于  DFS + 并查集 。

分析: LCA的离线算法为Tarjan,Tarjan算法的流程如下:
  DFS遍历整棵树,节点r在遍历完成之后,退回到r在树中的父节点,然后r在 并查集 中的pre指针会指向这个父节点。即对一个节点来说, 所有通过它被遍历过 的子树,都会在并查集中被 合并到这个节点所在的集合上,并以它作为代表元。
  初始化每个节点各是一个集合,每次合并操作都是伴随着遍历中的退后行为进行的。 这样就产生了一个性质,在遍历过程中,一个被遍历过的节点的集合的代表元, 就是从树根到这个节点 的路径上,DFS 退后到了的那个节点。 

在遍历离开当前节点之前,假设我们刚刚已经完成访问的节点是v,那么我们看与其一同被询问的另外一个点 u 是否已经被访问过了,若已经被访问过了,那么这个时候最近公共祖先必然是 u 所在集合对应的祖先 c,因为我们对 v 的访问就是从最近公共祖先 c 转过来的,并且在从c的子树 u 转向 v 的时候,我们已经将u的祖先置为了c,同时这个 c 也必然是 v 的祖先,那么c必然是u、v的最近公共祖先。
 
     总的时间复杂度是O(n+q)的,因为DFS是O(n)的,对于询问的处理是O(q)。


#include<iostream>
#include<vector>

using namespace std;
const int maxN=10001;


vector<int> vec[maxN];
int    pre[maxN];     //并查集的核心表 
bool  root[maxN];     // 看哪一个节点是根节点,有且只有一个 节点的值为 true 
bool visit[maxN];     //存放节点是否被访问的表  

int u,v;


void Init(int n){     //初始化 
	for( int i = 1;i <= n;i++ ){
		  pre[i] = i; //初始化每个节点都是一个集合 
		 root[i] = true;//初始化每个节点都是根 
		visit[i] = false;//都未被访问 
		  vec[i].clear();//清除上一组数据			
	}
}

int find(int n){     //查找这个节点属于哪一个集合 
	if( pre[n] == n ) //如果相等,说明这个节点就是DFS当前回退到的那个节点,它还没有被更高的节点合并 
		return n;       
	else              //不相等,则说明 这个节点已经被 它的父节点的集合 合并了。 
		return pre[n]=find( pre[n] );//需要递归查询当前它属于哪一个集合。 
	
}
               
void Union(int a,int b){//合并集合。集合的大小,从下向上是逐渐增大的。 
	int x = find(a);
	int y = find(b);
    
    if( x == y )
    	return ;
	pre[y] = x;
		
	
}

void LCA( int r){
	for( int i = 0;i < vec[r].size();i++ ){
		LCA( vec[r][i] ); 
		Union( r,vec[r][i] );
	} 
	//遍历完当前节点 r 的每一个子树之后,才会返回到 r,即后序遍历 。
	//这个性质就是我们使用Tarjan算法解决最近公共祖先问题的核心思想。 
	visit[r] = true;
	
	if( r == u && visit[v] == true){
		cout << "LCA of " << u << " and " << v 
		     << " is :" << find(pre[v]) << endl;
		return ;
	}	
	if( r == v && visit[u] == true){
		cout << "LCA of " << u << " and " << v 
		     << " is :" << find(pre[u]) << endl;
		return ;
	}	
		
}

int main(void){
	int T,n;
	int a,b;
	
	cin >> T;
	while(T--){
		cin >> n;
		Init(n);
		for( int i = 1;i < n;i++ ){
			cin >> a >> b;
			vec[a].push_back(b);
			root[b] = false;					
		}
		cin >> u >> v;
		
		for( int i = 1;i <= n;i++ ){
			if( root[i] == true ){
				cout << "root :" << i <<endl;           
				LCA(i);	
				break;
			}					
		}		
	}	
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值