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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章