LCA的Tarjan离线算法

LCA的Tarjan离线(offline)算法中,通过后序DFS遍历多叉树(结点数为n),利用并查集算法(disjoint sets‘ union-find operations),可以在线性时间O(n+|P|)内找到事先给定(即offline的含义)的|P|个成对结点的LCA。

具体做法如下:

1)首先所有的结点通过makeSet(x)调用放到各自独立集合中,然后在用lca(u)递归调用任何一个结点u时,按照从左到右顺序依次遍历u的所有子树v1,v2...vk,在v1被访问之后,v1的属性visited标记为true(此时u的属性visited还是false),然后将v1子树中所有结点与u结点合并,成为一个等价类集合S,接下来最重要的是需要记下S集合中所有结点的祖先结点ancestor,这里S的祖先结点就是u,而且相对后面将要访问的u的其他子树中结点而言,u是S中所有结点的最近的祖先结点!只有当u的k棵子树都访问完之后,才能后序访问u(此时标记u.visited=true)。

2)在任何一个结点u被标记为visited=true时,需要立即执行查询集合P中所有成对结点中包含u的元素Query{u,v},这里u和v是没有次序的,如果v也被访问过(即v.visited=true),那么LCA(u,v)就是v所在等价类集合S‘中所有结点的祖先结点ancestor。需要注意的是在u被访问后,应该立即查询Query{u,v}(若v.visited=true),否则,若不立即查询的话,S'的ancestor可能会被改变,得到的结果就不正确了。


在以下的实现中,假设树中结点的label值是数组A的下标索引值,利用数组B记录等价类集合关系,这样可以简化并查集算法的操作:在union-by-rank和find的路经压缩(path compression)操作中,只需要修改数组B的元素,而数组B的下标就代表结点的label。

实现:

/**
 * 
 * Tarjan's off-line LCA algorithm
 * 
 * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/)
 * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) 
 * 
 * 
 * @author ljs
 * 2011-08-16
 *
 */
public class TarjanOfflineLCA {
	// disjoint set's  array
	private int[] B;
	//the ancestor of the representative (i.e. root) of a disjoint set
	private int[] ancestor;
	//P is provided in advance (i.e. off-line)
	private Query[] P;

	public TarjanOfflineLCA(int n, Query[] queries) {
		this.B = new int[n];
		for(int i=0;i<B.length;i++)
			makeSet(i);
		ancestor = new int[n];
		
		this.P = queries;
	}

	static class Node {
		// index
		int index;
		Node[] children;

		// for TarjanLCA DFS
		boolean visited;

		public Node(int index) {
			this.index = index;
		}

		public String toString() {
			return String.valueOf(index);
		}
	}

	static class Query {
		Node u;
		Node v;
		int lca;

		public Query(Node u, Node v) {
			this.u = u;
			this.v = v;
		}
	}

	public void offlineLCA(Node root) {		
		lca(root);
	}

	private void lca(Node u) {
		if (u.children != null) {
			for (Node v : u.children) {
				lca(v);
				//u is merged with its processed subtrees
				union(u.index, v.index);	
				//the representative of equivalence class S
				int rep = find(u.index);
				//set the ancestor of S
				ancestor[rep] = u.index;
			}
		}else{
			ancestor[u.index] = u.index;
		}
		//visit u
		u.visited = true;
		
		//queried that include u should execute immediately after u is visited
		for (Query q : P) {
			Node v = null; 
			if (q.u == u && q.v.visited) {
				v = q.v;
			}else if(q.v ==u && q.u.visited){
				v = q.u;
			}
			if(v != null) {
				int rep = find(v.index);
				q.lca = ancestor[rep];				
			}
		}
	}

	public void makeSet(int x) {	
		B[x] = -1; // rank is 0
	}

	// union by rank
	public void unionSets(int root1, int root2) {
		if (B[root2] < B[root1]) {
			// root2 is deeper
			B[root1] = root2;
		} else {
			if (B[root1] == B[root2]) {
				// the same depth, set root1 as the root
				// increase depth
				//Note: only when merging two trees with the same ranks can the merged rank be altered.
				B[root1]--;
			}
			B[root2] = root1;
		}
	}

	public void union(int x, int y) {
		unionSets(find(x), find(y));
	}

	public int find(int x) {
		if (B[x] < 0)
			return x; // root
		else
			return B[x] = find(B[x]); // path compression
	}

	public static void main(String[] args) {
		int[] A = new int[] { 3, 5, 4, 2, 1, 0, 7, 15, 9, 10, 12, 8, 20, 22,
				25, 17,30,6,40,41 };

		Node n0 = new Node(0);
		Node n1 = new Node(1);
		Node n2 = new Node(2);
		Node n3 = new Node(3);
		Node n4 = new Node(4);
		Node n5 = new Node(5);
		Node n6 = new Node(6);
		Node n7 = new Node(7);
		Node n8 = new Node(8);
		Node n9 = new Node(9);
		Node n10 = new Node(10);
		Node n11 = new Node(11);
		Node n12 = new Node(12);
		Node n13 = new Node(13);
		Node n14 = new Node(14);
		Node n15 = new Node(15);
		Node n16 = new Node(16);
		Node n17 = new Node(17);
		Node n18 = new Node(18);		
		Node n19 = new Node(19);
		n0.children = new Node[] { n1, n2 };
		n1.children = new Node[] { n3, n4, n5 };
		n2.children = new Node[] { n6, n7 };
		n4.children = new Node[] { n8, n9 };
		n5.children = new Node[] { n10, n11 };
		n8.children = new Node[] { n12, n13 };
		n9.children = new Node[] { n14, n15 };
		n10.children = new Node[] { n16, n17 };
		n11.children = new Node[] { n18, n19 };

		Query[] P = { new Query(n8, n5), new Query(n12, n14),
				new Query(n14, n6), new Query(n6, n7),
				new Query(n10, n9), new Query(n3, n10),
				new Query(n14, n15), new Query(n15, n8),
				new Query(n11, n10), new Query(n13, n12),
				new Query(n5, n4),new Query(n18, n13),
				new Query(n4, n17),new Query(n16, n19),
				new Query(n9, n19),new Query(n10, n19),
				new Query(n7, n17),new Query(n17, n16),
				new Query(n18, n19)};		

		TarjanOfflineLCA tarjan = new TarjanOfflineLCA(A.length, P);
		tarjan.offlineLCA(n0);

		//report
		for(Query q:P){
			System.out.format("LCA of %d and %d is: %d%n", q.u.index,
				q.v.index, q.lca);
		}
	}

}



测试:

LCA of 8 and 5 is: 1
LCA of 12 and 14 is: 4
LCA of 14 and 6 is: 0
LCA of 6 and 7 is: 2
LCA of 10 and 9 is: 1
LCA of 3 and 10 is: 1
LCA of 14 and 15 is: 9
LCA of 15 and 8 is: 4
LCA of 11 and 10 is: 5
LCA of 13 and 12 is: 8
LCA of 5 and 4 is: 1
LCA of 18 and 13 is: 1
LCA of 4 and 17 is: 1
LCA of 16 and 19 is: 5
LCA of 9 and 19 is: 1
LCA of 10 and 19 is: 5
LCA of 7 and 17 is: 0
LCA of 17 and 16 is: 10
LCA of 18 and 19 is: 11


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值