`
384444165
  • 浏览: 255063 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

LCA离线算法Tarjan(1)算法介绍和最近公共祖先计算

阅读更多

之前小试的看过一些关于最近公共祖先LCA的离线算法,个人感觉很多博文说的还是不够清晰,一直没搞太懂,不知道是不是最近智商退化导致的,今天花时间细致了解了Tarjan,这篇文章主要说下算法和树结构最近公共祖先的计算,另外一些扩展应用在后续的帖子再说。

下面这篇博客中的伪码对我帮助很大,希望也能对不太明白的童鞋有帮助,后面还会提到。

http://blog.csdn.net/cxllyg/article/details/7635992

 

这里默认了解了并查集,并查集还是比较简单,很多的博客也都说的非常清楚,具体的一个非常生动的例子:http://blog.csdn.net/ljfbest/article/details/6642769

这里给出一个基本实现的代码版本:

 

public class DisjointSet<T> {
	
	public static class Node<T>{  
	    T data;         //Node的数据
	    int rank = 0;   //祖先节点的rank,rank小的节点表示孩子少,合并的时候加入到rank大的树下
	    Node<T> father; //父节点/祖先节点
	      
	    public Node(T data){  
	        this.data = data;  
	        this.father = this;
	        this.rank = 0;
	    }
	}  
	    
	/**
	 * 找到祖先节点
	 * @param x
	 * @return
	 */
	public Node<T> find(Node<T> x){  
		//当自己是祖先的时候直接返回
		if (x == x.father){
			return x;
		}
		
		//当自己的父节点不是祖先的时候,压缩树直接连接到祖先节点
		x.father = find(x.father);
		
		return x.father;
	}  
	  
	/**
	 * x和y节点之间有连接,将其所属集合连接。rank值小的树加到rank值大的树下面。相同时y加到x下。
	 * @param x
	 * @param y
	 */
	public void Union(Node<T> x, Node<T> y){ 
		Node<T> xFather = find(x);
		Node<T> yFather = find(y);
		//当两个结合不联通的时候根据rank值,将rank值小的树加到rank值大的树下面
		if(xFather==yFather){
			return;
		}else if(xFather.rank >yFather.rank)
			yFather.father = xFather;
		else if(xFather.rank < yFather.rank)
			xFather.father = yFather;
		else{
			yFather.father = xFather;
			xFather.rank++;
		}
	}  
}

 


进入正题看Tarjan,LCA最本质的应用就是查找两个节点最近的公共节点。比如

 2和3的最近公共节点是1,4和3也是1,4和5是2。

 

伪码直接摘用的博客上的,主要在后面做一些补充。

 

LCA(u){   
     Make-Set(u)   
     ancestor[Find-Set(u)]=u   
     对于u的每一个孩子v{   
         LCA(v)   
         Union(u)   
         ancestor[Find-Set(u)]=u   
     }   
     checked[u]=true  
     对于每个(u,v)属于P{   
         if checked[v]=true  
        then {   
             回答u和v的最近公共祖先为 ancestor[Find-Set(v)]   
         }   
     }   
} 

 

其中,makest就是建立一个集合,makeset(u )就是建立一个只含U的集合。

findset(u)是求跟U一个集合的一个代表,一般此集合用并查集表示,也就是当前树的root节点。

union()就是把 V节点生成的子树并入U中。

ancestor就是找跟节点,一直往上找,直至某节点的父节点是自己为止。

 

对于上面显示的并查集的基本demo而言,MakeSet和ancestor操作都不需要做,因此稍微简化一下,给出段代码,重要是关注这个流程,以上面的例子来说明。

 

LCA(u)   {   
     对于u的每一个孩子v   {   
         LCA(v)   
         Union(u,v)   
     }   
     checked[u]=true  
     对于每个(u,v)属于P   {   
         if checked[v]=true  then  回答u和v的最近公共祖先为 ancestor[Find-Set(v)]     
     }   
} 

 

 

1.    首先考虑下用并查集,很容易做最远公共祖先(当然,树的时候就是根),然后按这个思路需要做的就是怎么找到4、5的公共节点是2

2.    开始写了很多,但觉着这个伪码就比较清楚了,就简单说一下关键。这里可以看到是深度优先的计算,并且每个子集都优先计算子树,之后把全部的父节点都指向集合的父节点,这样的作用是比如一个二叉树,左子树单独进行处理,使得两点都在左子树上的节点的最近公共节点是左子树的根节点,当处理完后和根节点组合,进入右节点的时候,可以看到所有的节点和左子树的最近公共节点就是根节点,但是右子树内部的都单独在一个集合中,另外更右的子树没有处理过,不进行计算。按照这个方式,不断的这样处理,就通过递归从左向右不断处理,完成整个过程。

3.    还有需要注意的一点就是(u,v)这个顺序很重要,因此每个uv的组合需要先做两次的存储,实际uv和vu只会处理一个

 

 因为伪码比较清楚了,搜了很多帖子,看到这段伪码想想就大概清楚了,这里就贴上代码,很多算法大牛们的代码都太不易懂了,我还是来点工程版的

 

/**
 * 对于树结构的LCA_Tarjan
 * 
 * @author Jason_wbw
 *
 */

public class LCA_Tarjan<T> {
	
	public static class Node<T>{  
	    T data;         //Node的数据
	    Node<T> father; //父节点/祖先节点
	    List<Node<T>> children;
	    
	    public Node(){}
	    
	    public Node(T data){  
	        this.data = data;  
	        this.father = this;
	        children = new ArrayList<Node<T>>();
	    }
	}  
	
	public static class Pair<T>{
		Node<T> p, q;
		Node<T> lca;
		public Pair(Node<T> p, Node<T> q){
			this.p = p;
			this.q = q;
		}
	}

	List<Pair<T>> list;                   //结果
	Map<Node<T>,List<Node<T>>> pairs;     //对应于(u,v),对于一个元组需要存储uv和vu
 	Map<Node<T>,Boolean> checked;         //对应于checked[]
	
	public List<Pair<T>> getLCA(Node<T> root, List<Pair<T>> list){
		checked = new HashMap<Node<T>,Boolean>();
		this.list = new LinkedList<Pair<T>>();
		
		//构建所有需要查询的元组
		pairs = new HashMap<Node<T>,List<Node<T>>>();
		for(Pair<T> p : list){
			if(pairs.get(p.p)==null)
				pairs.put(p.p, new LinkedList<Node<T>>());
			pairs.get(p.p).add(p.q);
			
			if(pairs.get(p.q)==null)
				pairs.put(p.q, new LinkedList<Node<T>>());
			pairs.get(p.q).add(p.p);
		}
		
		//进入实际算法
		LCA(root);
		return this.list;
	}
	
	private void LCA(Node<T> u){
		//完全依照伪码部分实现
		for(Node<T> v:u.children){
			LCA(v);
			union(u, v);
		}
		checked.put(u, true);
		if(pairs.get(u)!=null){
			for(Node<T> n : pairs.get(u)){
				Boolean b = checked.get(n);
				if(b!=null && b){
					Pair<T> pair = new Pair<T>(u,n);
					pair.lca = find(n);
					list.add(pair);
				}
			}
		}
	}
	
	//--------------------------------------------------------------------
	//并查集操作
	    
	/**
	 * 找到祖先节点
	 * @param x
	 * @return
	 */
	public Node<T> find(Node<T> x){  
		//当自己是祖先的时候直接返回
		if (x == x.father){
			return x;
		}
		
		//当自己的父节点不是祖先的时候,压缩树直接连接到祖先节点
		x.father = find(x.father);
		
		return x.father;
	}  
	  
	/**
	 * x和y节点之间有连接,将其所属集合连接。rank值小的树加到rank值大的树下面。相同时y加到x下。
	 * @param x
	 * @param y
	 */
	public void union(Node<T> x, Node<T> y){ 
		Node<T> xFather = find(x);
		Node<T> yFather = find(y);
		//当两个结合不联通的时候根据rank值,将rank值小的树加到rank值大的树下面
		if(xFather==yFather){
			return;
		}else{
			yFather.father = xFather;
		}
	}  
}

 

 

  • 大小: 1.4 KB
分享到:
评论

相关推荐

    Tarjan 的 LCA 算法

    c++写的Tarjan 的 LCA 算法,最近公共祖先算法,可供算法学习参考

    LCA (最近公共祖先) Tarjan & 倍增

    LCA Tarjan: 实现原理 理解:离线算法,建好树后再查询,一次DFS 吧所有查询解决完。 时间复杂度:O(n+q); n个点 q次询问 补一下:链式向前星,并查集 ,Tarjan 代码 #include #include #include #include #...

    LCA.zip_lca Tarjan pascal_lca pascal_tarjan lca pascal

    Tarjan算法求最近公共祖先,输入树和询问,按询问顺序回答

    Tarjan算法

    最近公共祖先LCA Tarjan算法

    contest_tarjan_LCA_源码

    tarjan离线算法求最近公共祖先。对于有根树T的两个结点u、v,最近公共祖先LCA(T

    Tarjan算法讲义

    Tarjan 算法是图论中非常实用 / 常用的算法之一,能解决强连通分量,双连通分量,割点和桥,求最近公共祖先(LCA)等问题。 关于 Tarjan 算法,笔者将用一系列文章系统介绍 Tarjan 算法的原理以及其主要解决的问题...

    常用算法代码

    | RMQ 离线算法 O(N*LOGN)+O(1)求解 LCA 19 | LCA 离线算法 O(E)+O(1) 20 | 带权值的并查集 20 | 快速排序 20 | 2 台机器工作调度 20 | 比较高效的大数 20 | 普通的大数运算 21 | 最长公共递增子序列 O(N^2) ...

    Pro 俩顶点之间的长度

    求树中多个两点间距离,实质求LCA(最近公共祖先),(在之前Reference Book中有过论述,CCW在文中也有论述,希望大家有时间好好研读) 常用的是Tarjan 算法或者 倍增算法 或者DFS+ST算法 这道题结果输出所有问询距离...

    lyq-algorithms-lib:lyq算法库,涉及到相关数据挖掘,解压缩,模式匹配,图算法等多领域算法

    lyq-algorithms-liblyq算法库,涉及到相关数据...##LCA最近公共祖先的离线算法。LZWlzw系列解压缩算法。QQNewsCrawler腾新闻数据爬取工具。RandomForest随机森林算法。Tarjan有向图强连通分量算法。Trie字典查找树算法

    ACM竞赛代码整理 v0.6.pdf

    ACM竞赛代码整理 ...LCA 最近公共祖先(TARJAN) 16 最大流17 最小费用最大流18 求割点和桥19 无向图的块20 极大双连通分量21 极大强连通分量22 极大强连通分量缩点23 2-SAT 判定24 第五章计算几何25 三维凸包25

    ACM巨全模板 .pdf

    16.求两个节点的最近公共祖先 (LCA) 17.限制顶点度数的MST(k度限制生成树) 18.多源最短路(spfa,floyd) 19.最短路 (输出字典序最小) 20.最长路 图论题目简述 字符串: 1.字典树(多个字符串的前缀) 2.KMP(关键字搜索) ...

    欧拉公式求圆周率的matlab代码-CP-Template:竞争性编程的C++模板

    强连接组件(SCC):Tarjan算法 常用技术 欧拉巡回压缩 重光分解(HLD) 最低共同祖先(LCA) 欧拉巡回方法:O(log n)查询 深度方法:O(log n)查询 稀疏表:O(1)查询但很长 质心分解:求解穿过电流质心的所有...

Global site tag (gtag.js) - Google Analytics