`
yiminghe
  • 浏览: 1461843 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

LCA In Javascript 演示

阅读更多

理论:

LCA 即 Least Common Ancestor 问题 ,用于在树结构中查找任意两个结点的最小公共祖先,很容易的算法是O(logn),但是对于查询很大的情况,可能并不完美,完美的当然就是O(1)。

那么就采用空间换时间的方法,及可转化为 RMQ (Range Minimum/Maximum Query) 问题 。

 

效果示例 下载   ):

 

演示地址@google code

 

可以方便的查看任意两个节点的最小公共祖先

 

 

 

 

LCA In HTML

HTML DOM 树是天生的树结构,可以很方便的做演示,那么就写段 html 用 javascript 演示下 LCA 算法。


html 代码:

 

<div id="a" style="width:600px;height:550px;border:1px solid green;padding:50px;">
			a
			<div id="b" style="width:300px;height:450px;border:1px solid green;padding:20px;margin:20px 0;">
				b
				<div id="d" style="width:100px;height:100px;border:1px solid green;padding:20px;margin-bottom:20px;">
					d
					<div id="f" style="width:20px;height:20px;border:1px solid green;padding:10px;margin-bottom:10px;">
						f
					</div>
					<div id="i" style="width:20px;height:20px;border:1px solid green;padding:10px;">
						i
					</div>
				</div>
				<div id="e" style="width:100px;height:100px;border:1px solid green;padding:20px;">
					e
					<div id="g" style="width:20px;height:20px;border:1px solid green;padding:10px;margin-bottom:10px;">
						g
					</div>
					<div id="h" style="width:20px;height:20px;border:1px solid green;padding:10px;">
						h
					</div>
				</div>
			</div>
			
			<div id="c" style="width:10px;height:10px;border:1px solid green;padding:10px;margin-top:10px;">
				c
			</div>
		</div>

 

则对应树结构为 :

 

 



1.按先序遍历整棵树,记下两个信息:结点访问顺序和结点深度


则上述为


a,b,d,f,d,i,d,b,e,g,e,h,e,b,a,c,a


结点深度:


0,1,2,3,2,3,2,1,2,3,2,3,2,1,0,1,0



2. 则可首先找到两节点在访问序列中的位置,并在深度序列中找到之间最小的值的位置,再找到该位置对应的结点即为最小公共祖先了。


3. 而找到序列中任意子序列最小值,即是 RMQ 问题,采用 ST(Sparse Table)算法 ,在预处理( O(NlogN) )完成的前提下,可以保证在 O(1) 的时间内找到任意子序列间的最小值。



这样既可保证在预先处理后,则在查询频繁时,保证最快( O(1) )的反映速度。


 


代码示例 (  下载   ):

 

 

LCA javascript :

 

/***
	对于频繁计算树中任意两个节点最小公共祖先问题的最优解法.
	***/


function Lca(root) {
    //记录节点访问顺序与深度
    this.sequence = [];
    //RMQ sparse table
    this.matrix = [];
    this.preprocessForLca(root, 0);
    this.processForRmq();

}


/*
	将树转化为对应的先序访问顺序的层次队列,利用 rmq 算法快速求解
	*/
Lca.prototype.preprocessForLca = function(node, l) {
    //第一次访问记录节点与节点深度
    this.sequence.push({
        node: node,
        level: l
    });
    var childs = node.childNodes;
    if (childs) {
        for (var i = 0, len = childs.length; i < len; ++i) {
            var c = childs[i];
            if (c.nodeType == 3) continue;
            this.preprocessForLca(c, l + 1);
            //子节点子树处理完毕,子树父节点即当前节点顺序记录。
            this.sequence.push({
                node: node,
                level: l
            });

        }
    }
};


/*
	利用动态规划算法计算 sparse table matrix , M[0, N-1][0, logN] ,
	log 以 2 为底
	M[i][j] 是从原数组i开始的子数组中最小元素在原数组中的下标,
	这个子数组的长度为2^j
	可以再 O(1) 的时间复杂度中得到 数组中任意子数组的最小值
	*/
Lca.prototype.processForRmq = function() {
    var sl = this.sequence.length;
    var col = Math.ceil(Math.log(sl) / Math.log(2));
    for (var i = 0; i < sl; i++) {
        this.matrix[i] = [];
    }
    // initialize M for the intervals with length 1
    for (var i = 0; i < sl; i++)
    this.matrix[i][0] = i;
    // 从小间隔计算,直到最后的整个数组间隔 ,即 M[0][sl]
    for (var j = 1; (1 << j) <= sl; j++) {
        for (i = 0; i + (1 << j) - 1 < sl; i++) {
            if (this.sequence[this.matrix[i][j - 1]].level <
            this.sequence[this.matrix[i + (1 << (j - 1))][j - 1]].level)
            this.matrix[i][j] = this.matrix[i][j - 1];
            else
            this.matrix[i][j] = this.matrix[i + (1 << (j - 1))][j - 1];
        }
    }
};

Lca.prototype.equalNode = function(n1, n2) {
    if (n1 == n2) return true;
    //if(n1.id == n2.id) return true;
};
/*
	利用matrix得到 n1,n2 在树中的最小祖先
	每次时间复杂度为 O(1)
	*/
Lca.prototype.getCommonParent = function(n1, n2) {

    if (this.equalNode(n1, n2))
    return n1;
    if (n1.parentNode == n2)
    return n2;
    if (n2.parentNode == n1)
    return n1;

    var sl = this.sequence.length;
    var n1Index = -1;
    var n2Index = -1;
    //找到节点对应于访问顺序的下标
    for (var i = 0; i < sl; i++) {
        var t = this.sequence[i].node;
        if (n1 == t) {
            n1Index = i;
            break;
        }
    }

    for (var i = 0; i < sl; i++) {
        var t = this.sequence[i].node;
        if (n2 == t) {
            n2Index = i;
            break;
        }
    }

    if (n1Index == -1 || n2Index == -1) {
        alert("-1");
        return;
    }

    //确保n1Index小
    if (n1Index > n2Index) {
        var t = n1Index;
        n1Index = n2Index;
        n2Index = t;
    }



    // 2^k*2>= |n1Index-n2Index|.
    //这样就可以在
    //子数组 n1Index ~ n1Index+2^k
    //子数组 n2Index-2^k ~ n2Index
    //两个子数组中的深度最小值比较得到
    //n1Index ~ n2Index
    //的深度最小值,即 n1 n2最小公共祖先节点在访问顺序数组中的下标
    var k = Math.ceil(Math.log(Math.abs(n1Index - n2Index))
    / Math.log(2)) - 1;

    var n1Min = this.matrix[n1Index][k];
    //2^k == 1 << k
    var n2Min = this.matrix[n2Index - (1 << k)][k];

    if (this.sequence[n1Min].level > this.sequence[n2Min].level)
    return this.sequence[n2Min].node;
    return this.sequence[n1Min].node;

};
 


使用代码:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<script type="text/javascript">
//<![CDATA[

		/***
		对于频繁计算树中任意两个节点最小公共祖先问题的最优解法.
		***/


		function Lca(root){
		//记录节点访问顺序与深度
		this.sequence=[];
		//RMQ sparse table
		this.matrix=[];
		this.preprocessForLca(root,0);
		this.processForRmq();

		}


		/*
		将树转化为对应的先序访问顺序的层次队列,利用 rmq 算法快速求解
		*/
		Lca.prototype.preprocessForLca = function(node,l){
		//第一次访问记录节点与节点深度
		this.sequence.push({
		node:node,
		level:l
		});
		var childs=node.childNodes;
		if(childs) {
		for(var i=0 , len=childs.length;i<len;++i) {
				var c=childs[i];
				if(c.nodeType == 3) continue;
				this.preprocessForLca(c,l+1);
				//子节点子树处理完毕,子树父节点即当前节点顺序记录。
				this.sequence.push({
					node:node,
					level:l
				});
				
		}
		}
		};


		/*
		利用动态规划算法计算 sparse table matrix , M[0, N-1][0, logN] ,
		log 以 2 为底
		M[i][j] 是从原数组i开始的子数组中最小元素在原数组中的下标,
		这个子数组的长度为2^j
		可以再 O(1) 的时间复杂度中得到 数组中任意子数组的最小值
		*/
		Lca.prototype.processForRmq = function () {
		var sl=this.sequence.length;
		var col = Math.ceil(Math.log(sl) / Math.log(2));
		for(var i=0;i<sl;i++) {
		this.matrix[i] = [];
		}   
		// initialize M for the intervals with length 1
		for (var i = 0; i < sl; i++)
		this.matrix[i][0] = i;
		// 从小间隔计算,直到最后的整个数组间隔 ,即 M[0][sl]
		for (var j = 1; (1 << j) <= sl; j++) {
		for (i = 0; i + (1 << j) - 1 < sl; i++) {
			if (this.sequence[this.matrix[i][j - 1]].level < 
			this.sequence[this.matrix[i + (1 << (j - 1))][j - 1]].level)
				this.matrix[i][j] = this.matrix[i][j - 1];
			else
				this.matrix[i][j] = this.matrix[i + (1 << (j - 1))][j - 1];
		}
		}
		};

		Lca.prototype.equalNode =function(n1,n2) {
		if(n1 == n2 ) return true;
		//if(n1.id == n2.id) return true;
		};
		/*
		利用matrix得到 n1,n2 在树中的最小祖先
		每次时间复杂度为 O(1)
		*/
		Lca.prototype.getCommonParent = function(n1,n2) {

		if (this.equalNode(n1,n2))
		return n1;
		if (n1.parentNode == n2)
		return n2;
		if (n2.parentNode == n1)
		return n1;

		var sl=this.sequence.length;
		var n1Index=-1;
		var n2Index=-1;
		//找到节点对应于访问顺序的下标
		for(var i=0;i<sl;i++) {
		var t=this.sequence[i].node;
		if(n1==t){
			n1Index=i;
			break;
		}
		}

		for(var i=0;i<sl;i++) {
		var t=this.sequence[i].node;
		if(n2==t){
			n2Index=i;
			break;
		}
		}

		if(n1Index == -1 || n2Index == -1) {
		alert("-1");
		return;
		}

		//确保n1Index小
		if(n1Index > n2Index) {
		var t=n1Index;
		n1Index=n2Index;
		n2Index=t;
		}



		// 2^k*2>= |n1Index-n2Index|.
		//这样就可以在 
		//子数组 n1Index ~ n1Index+2^k
		//子数组 n2Index-2^k ~ n2Index
		//两个子数组中的深度最小值比较得到
		//n1Index ~ n2Index
		//的深度最小值,即 n1 n2最小公共祖先节点在访问顺序数组中的下标

		var k = Math.ceil(Math.log(Math.abs(n1Index - n2Index))
		  / Math.log(2)) - 1;

		var n1Min = this.matrix[n1Index][k];
		//2^k == 1 << k
		var n2Min = this.matrix[n2Index - (1<<k) ][k];

		if(this.sequence[n1Min].level > this.sequence[n2Min].level )
		return this.sequence[n2Min].node;
		return this.sequence[n1Min].node;

		};
		//]]>
		</script>
		<title>
			Test Lca In Javascript
		</title>
	</head>
	<body>
		<p>
			node1 的 id :<input id="node1" />
		</p>
		<p>
			node2 的 id :<input id="node2" />
		</p>
		<p>
			<input id="btn" type="button" value="点击得到公共祖先id" />
		</p>
		<div id="a" style="width:600px;height:550px;border:1px solid green;padding:50px;">
			a
			<div id="b" style="width:300px;height:450px;border:1px solid green;padding:20px;margin:20px 0;">
				b
				<div id="d" style="width:100px;height:100px;border:1px solid green;padding:20px;margin-bottom:20px;">
					d
					<div id="f" style="width:20px;height:20px;border:1px solid green;padding:10px;margin-bottom:10px;">
						f
					</div>
					<div id="i" style="width:20px;height:20px;border:1px solid green;padding:10px;">
						i
					</div>
				</div>
				<div id="e" style="width:100px;height:100px;border:1px solid green;padding:20px;">
					e
					<div id="g" style="width:20px;height:20px;border:1px solid green;padding:10px;margin-bottom:10px;">
						g
					</div>
					<div id="h" style="width:20px;height:20px;border:1px solid green;padding:10px;">
						h
					</div>
				</div>
			</div>
			<div id="c" style="width:10px;height:10px;border:1px solid green;padding:10px;margin-top:10px;">
				c
			</div>
		</div><script type="text/javascript">
//<![CDATA[
		window.onload=function(){

		//整个子树转化为访问顺序队列数组
		var lca=new Lca(document.getElementById("a"));
		/*
		var visit="";
		var levels="";      
		for(var i=0 , len=lca.sequence.length;i<len;++i) {
				var c=lca.sequence[i];
				visit+=","+c.node.id;
				levels+=","+c.level;                
		}
		console.log(visit);
		console.log(levels);
		*/

		//O(1) 时间内得到任意两个节点的最小公共祖先


		document.getElementById("btn").onclick=function(){
			var n1=document.getElementById("node1").value;
			var n2=document.getElementById("node2").value;
			if(document.getElementById(n1) && document.getElementById(n2)){
				var cp=lca.getCommonParent(document.getElementById(n1),document.getElementById(n2));
				alert("最小公共祖先 :"+cp.id);
			}
			else {
				alert("DOM 树中没有输入id节点");
			}
			
		}
		}

		//]]>
		</script>
	</body>
</html>

 

 

 

 

  • 大小: 36.5 KB
  • 大小: 13.5 KB
2
0
分享到:
评论

相关推荐

    Pascal LCA 倍增法详解及代码

    最近公共祖先(LCA)问题在计算机科学,特别是在图论和数据结构中具有重要的应用,尤其是在树形数据结构的处理中。LCA指的是在一棵有根树中,给定两个节点u和v,找到离根最远的那个节点r,使得r既是u的祖先也是v的...

    LCA2015:LCA2015 演示幻灯片

    【标题】"LCA2015:LCA2015 演示幻灯片" 提供的信息表明这是一个关于Linux Conference Australia 2015(LCA2015)活动的演示文稿。LCA是一个年度会议,聚集了全球各地的开源技术爱好者、开发者和专家,讨论Linux及其...

    SAS+proc lca浅类别分析安装程序

    其中,PROC LCA(Latent Class Analysis)是SAS中的一个过程,用于执行潜在类别分析,这是一种统计方法,旨在从观测数据中识别出隐藏的、不可见的类别结构。PROC LCA尤其适用于处理多分类变量,它可以帮助研究人员...

    Tarjan 的 LCA 算法

    **Tarjan的LCA算法详解** 最近公共祖先(Lowest Common Ancestor,简称LCA)是图论中的一个重要概念,特别是在树形结构中。在树中,两个节点的最近公共祖先指的是距离根节点最近的那个同时是这两个节点的祖先的节点...

    LCA-AW2.2.zip

    【LCA词汇复杂度分析软件】是一款用于自然语言处理(NLP)的专业工具,它主要聚焦于语言的复杂度分析。LCA,全称为“Language Complexity Analysis”,此软件旨在帮助用户理解和评估文本的语言难度,这对于教育、...

    LCA265显示器应用

    LCA265显示器是Systeme Lauer GmbH & Co. KG公司生产的一款专业显示器,用于显示和处理用户数据。在操作这款显示器时,有时需要进行数据传输,特别是文本文件的交换,这通常涉及到与个人计算机(PC)之间的通信。...

    LCA51正式版2.06

    《51单片机开发工具LCA51正式版2.06详解》 51单片机,作为微控制器领域中的经典型号,被广泛应用于各种电子设备和控制系统中。对于51单片机的开发者而言,拥有一款高效、易用的编辑器、编译器和仿真工具至关重要。...

    RMQ与LCA ACM必备

    例如,在一个树形结构中,A是B和C的LCA,D是E和F的LCA。 1. **Tarjan离线算法**: Tarjan提出了一种离线算法来解决LCA问题,使用并查集维护树结构,并通过深度优先搜索(DFS)进行预处理。每个节点u的father[u]...

    RMQ和LCA详解

    关于RMQ和LCA的关系的知识,如何用RMQ和LCA的转换

    日立LCA电气图纸(K3500490).pdf

    这导致无法从中提取任何有关“日立LCA电气图纸(K3500490).pdf”文件的知识点。为了满足题目要求,我将基于标题和描述部分提供的信息,对“日立LCA电气图纸”进行知识性的解释。 ### LCA型微机控制变压变频调速无...

    lca51早期版本

    【LCA51早期版本】是一款专用于模拟和开发基于MCS-51(8051)微控制器的应用程序的软件工具。这个版本是2.02,可能对于一些研究历史版本、进行兼容性测试或者对旧项目进行维护的开发者来说具有一定的价值。在本文中...

    LCA.rar_LCA

    最近公共祖先(Lowest Common Ancestor,简称LCA)是图论中的一个重要概念,主要应用于树形结构的数据问题。在给定的树中,两个节点的最近公共祖先是指距离这两个节点最近的那个共同父节点。在计算机科学中,尤其是...

    中文LCA Training 2

    LCA 中文教程,内部资料--产品结构管理,还不错啊,可以看下

    lca.rar_LCA

    在IT领域,特别是数据结构和算法的设计中,"最近公共祖先"(Lowest Common Ancestor,简称LCA)是一个常见的问题。这个问题出现在许多场景中,比如计算机科学中的树形结构处理,生物信息学中的基因进化分析等。最近...

    LCA生命周期评价PPT课件.ppt

    LCA生命周期评价,LCA生命周期评价课件,LCA生命周期评价PPT

    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

    在IT领域,特别是数据结构和算法分析中,"LCA"是"Lowest Common Ancestor"(最近公共祖先)的缩写,这是一个经典的问题,常常出现在树形结构的处理中。给定一个树形结构和若干对节点,我们需要找出这些节点的最近...

    Tarjan应用LCA

    首先,定义了一些辅助变量,如f数组记录节点所属集合的根节点,r数组记录集合的秩,indegree数组记录节点的入度,visit数组标记节点是否已处理,tree数组存储树的邻接表,Qes数组存储查询对,以及ancestor数组记录每...

Global site tag (gtag.js) - Google Analytics